<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>JugglerShu.Net &#187; Objective-C</title>
	<atom:link href="http://programming.jugglershu.net/wp/?feed=rss2&#038;tag=objective-c" rel="self" type="application/rss+xml" />
	<link>http://programming.jugglershu.net/wp</link>
	<description>Nothing But Programming</description>
	<lastBuildDate>Wed, 15 Apr 2020 08:11:15 +0000</lastBuildDate>
	<language>ja</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>How to draw thick and transparent insertion point in NSTextView</title>
		<link>http://programming.jugglershu.net/wp/?p=765</link>
		<comments>http://programming.jugglershu.net/wp/?p=765#comments</comments>
		<pubDate>Sat, 17 Aug 2013 07:16:35 +0000</pubDate>
		<dc:creator><![CDATA[shu]]></dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[NSTextView]]></category>
		<category><![CDATA[Objective-C]]></category>

		<guid isPermaLink="false">http://programming.jugglershu.net/wp/?p=765</guid>
		<description><![CDATA[While I was working on drawing an insertion point in XVim project I had a really hard time to achieve my goal &#8220;drawing thick and transparent insertion point on a character&#8221;. This does not  <a class="more-link" href="http://programming.jugglershu.net/wp/?p=765">続きを読む <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>While I was working on drawing an insertion point in <a href="https://github.com/JugglerShu/XVim">XVim</a> project I had a really hard time to achieve my goal &#8220;drawing thick and transparent insertion point on a character&#8221;.</p>
<p>This does not look difficult since NSTextView has a method to override named &#8220;drawInsertionPointInRect:color:turnedOn:&#8221;. Actually just drawing &#8220;thick&#8221; insertion point  is not difficult. But just overriding the method results in strange behaviour and not beautiful at all. Here I show what I tried and how I achieve the transparent thick insertion point.</p>
<p>If you are just interested in &#8220;how&#8221;, skip the part explaining what I tried and see the section &#8220;Solution&#8221; at the end of this post. There is a code for it. I tested the code in OS X 10.8 and also I must note that the code uses NSTextView&#8217;s internal method to override. This means that you may not use the code if you want your app to be in AppStore.</p>
<h2>First attempt</h2>
<p>As everyone can think of I first tried to subclass NSTextView and override &#8220;drawInsertionPointInRect:color:turnedOn:&#8221; (will be referred as &#8220;drawIPR&#8221; in this post). The code looks like following.</p>
<pre class="brush: cpp; gutter: false">
-(void)drawInsertionPointInRect:(NSRect)rect color:(NSColor)color turnedOn:(BOOL)flag {
    // Calculate the rect of a character under the insertion point
    NSRange charRange = NSMakeRange(self.selectedRange.location, 1);
    NSRect glyphRect = [[self layoutManager] boundingRectForGlyphRange:charRange inTextContainer:[self textContainer]];
    if( glyphRect.size.width == 0 ||
       [[NSCharacterSet newlineCharacterSet] characterIsMember:[[self string] characterAtIndex:self.selectedRange.location]]){
        // This handles when the insertion point is at the end of line where we cannot find any character to cover by a caret.
        // We keep the original width (1.0)
        glyphRect = rect;
    }
    [super drawInsertionPointInRect:glyphRect color:color turnedOn:flag];
}
</pre>
<p>This works somehow. But there are unacceptable problems.</p>
<ol>
<li>No transparency. A caret completely fill the rect and we can not recognise the character under the caret.(Since the caret is blinking you still can see the character but not really beautiful)</li>
<li>When you move a cursor it draws thin(origina size) insertion point. You will see why this happens later.</li>
</ol>
<p><a href="http://programming.jugglershu.net/wp/wp-content/uploads/2013/08/Screen-Shot-2013-08-17-at-12.27.28-PM.png"><img class="aligncenter" alt="Screen Shot 2013-08-17 at 12.27.28 PM" src="http://programming.jugglershu.net/wp/wp-content/uploads/2013/08/Screen-Shot-2013-08-17-at-12.27.28-PM.png" width="498" height="326" /></a></p>
<p style="text-align: center;"><b>Character &#8216;p&#8217; is covered by a caret</b></p>
<p>OK. I have 2 problems. Solve them one by one.</p>
<h2>Transparent color</h2>
<p>So I tried to set the color&#8217;s alpha value. It&#8217;s like this.</p>
<pre class="brush: cpp; gutter: false">
-(void)drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color turnedOn:(BOOL)flag {
    // Calculate the rect of a character under the insertion point
    NSRange charRange = NSMakeRange(self.selectedRange.location, 1);
    NSRect glyphRect = [[self layoutManager] boundingRectForGlyphRange:charRange inTextContainer:[self textContainer]];
    if( glyphRect.size.width == 0 ||
       [[NSCharacterSet newlineCharacterSet] characterIsMember:[[self string] characterAtIndex:self.selectedRange.location]]){
        // This handles when the insertion point is at the end of line where we cannot find any character to cover by a caret.
        // We keep the original width (1.0)
        glyphRect = rect;
    }
    // Set alpha of the color here.
    color = [color colorWithAlphaComponent:0.5]; // &lt;--- newly added.
    [super drawInsertionPointInRect:glyphRect color:color turnedOn:flag];
}
</pre>
<p>Unfortunately this does not change anything. The reason is that inside the &#8220;drawIPR&#8221; in NSTextView it use NSRectFill to draw the insertion point and the alpha value of the color is completely ignored.</p>
<h2>Drawing a caret by myself</h2>
<p>Since the implementation of the &#8220;drawIRP&#8221; in NSTextView draws a caret by NSRectFill we can not use it to draw transparent caret. OK. We can re-implement the whole of the method. To re-implement it we have to know how the method works. It takes 3 arguments &#8220;rect&#8221;,&#8221;color&#8221; and &#8220;turnedOn&#8221;. the &#8220;rect&#8221; and &#8220;color&#8221; are apparent. The &#8220;turnedOn&#8221; argument specifies whether we should draw insertion point or clear the insertion point. The method is called with the &#8220;turnedOn&#8221; arugument set to YES and NO by turns, which results in blinking a caret. So the implementation should be like following.</p>
<pre class="brush: cpp; gutter: false">
-(void)drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color turnedOn:(BOOL)flag {
    NSRange charRange = NSMakeRange(self.selectedRange.location, 1);
    NSRect glyphRect = [[self layoutManager] boundingRectForGlyphRange:charRange inTextContainer:[self textContainer]];
    if( glyphRect.size.width == 0 ||
       [[NSCharacterSet newlineCharacterSet] characterIsMember:[[self string] characterAtIndex:self.selectedRange.location]]){
        glyphRect = rect;
    }
    
    if( flag ){
        // Draw a caret
        color = [color colorWithAlphaComponent:0.5];
        [color set];
        NSRectFillUsingOperation(glyphRect,  NSCompositeSourceOver);
    }
    else{
        // Clear a caret
        // We have to redraw the character under the caret.
        // We should use &quot;setNeedsDisplayInRect:&quot; if possible. But to do so we have to keep the rect we drew somewhere.
        // This kind of optimisation is off topic of this post so just update the whole rect.
        [self setNeedsDisplay:YES];
    }
}
</pre>
<p>Yes, this solves the problem No.1 but not No.2. ( I think NSTextView&#8217;s implementation should be changed as this code works perfectly because this is the official way to override drawing insertion point).</p>
<p>The code above draws/clears a caret(with thick and transparency) correctly but when you move a cursor you see a caret without the thickness. Why? The reason why this happens is because NSTextView uses internal method to draw a caret and it is called when you move a cursor to redraw the caret immediately. The internal method is named &#8220;_drawInsertionPointInRect:color:&#8221;. Note that it starts with &#8220;_&#8221; and does not have &#8220;turenedOn&#8221; argument. (will be referred as &#8220;_drawIPR&#8221; in this post). The code in NSTextView should be like following according to my observation of the behavior. ( This is really simplified to make the point clear though. )</p>
<pre class="brush: cpp; gutter: false">
// This is guessed code of NSTextView implementation

// Private method to draw a caret.
// Note that this method only does &quot;drawing&quot;. It does NOT have &quot;turnedOn&quot; flag.
-(void)_drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color{
       [color set];
       NSRectFill(rect);
}

- (void)drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color turnedOn:(BOOL)flag{
    if( flag ){
       // Call the internal method to draw insertion point 
       [_drawInsertionPointInRect:rect color:color]
    }else{
       [self setNeedsDisplayInRect:rect];
    }
}

// When you move a cursor, _drawRect (this is also an internal method) draws insertion point.
- (void)_drawRect:(NSRect)rect clip:(NSRect)clip{
    // At some point in this method...
    [self _drawInsertionPointInRect:NSMakeRect(x,y,1.0,height) color:_color];
}
</pre>
<p>The important points are</p>
<ul>
<li>&#8220;_drawIPR&#8221; is called from multiple methods (not only from &#8220;drawIPR&#8221; but also from _drawRect:clip:)</li>
<li>In _drawRect:clip: it calls &#8220;_drawIPR&#8221; with <b>hard coded width.</b></li>
</ul>
<h2>Overriding _drawInsertionPointInRect:color:</h2>
<p>OK. So NSTextView uses &#8220;_drawIPR&#8221; to draw insertion point anyway. How about just overriding &#8220;_drawIPR&#8221; instead of &#8220;drawIPR&#8221;. The code should be like&#8230;</p>
<pre class="brush: cpp; gutter: false">
-(void)_drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color{
    NSRange charRange = NSMakeRange(self.selectedRange.location, 1);
    NSRect glyphRect = [[self layoutManager] boundingRectForGlyphRange:charRange inTextContainer:[self textContainer]];
    if( glyphRect.size.width == 0 ||
       [[NSCharacterSet newlineCharacterSet] characterIsMember:[[self string] characterAtIndex:self.selectedRange.location]]){
        glyphRect = rect;
    }
    color = [color colorWithAlphaComponent:0.5];
    [color set];
    NSRectFillUsingOperation(glyphRect, NSCompositeSourceOver);
    // We do not call super calls since the method in the super class uses NSRectFill and calling it results in filling the rect with the color without transparency.
}

</pre>
<p>Note that we are not overriding &#8220;drawIPR&#8221;. This results in drawing a thick and transparent caret whenever NSTextView draws a caret. Unfortunately this leads another problem. The code is only &#8220;drawing&#8221; a caret but not &#8220;clearing&#8221; a caret to blink a caret. So once the caret is drawn the caret stays there and there is no blinking. Furthermore if you move a caret, all the characters the caret passes will be covered by the drawing and stays there.</p>
<p><a href="http://programming.jugglershu.net/wp/wp-content/uploads/2013/08/Screen-Shot-2013-08-17-at-12.34.39-PM.png"><img class="alignnone size-full wp-image-780 aligncenter" alt="Screen Shot 2013-08-17 at 12.34.39 PM" src="http://programming.jugglershu.net/wp/wp-content/uploads/2013/08/Screen-Shot-2013-08-17-at-12.34.39-PM.png" width="495" height="326" /></a></p>
<p style="text-align: center;"><strong>Drawn carets are not cleared</strong></p>
<p>The reason why the caret is not cleared correctly is because NSTextView does not know the rect which should be cleared. NSTextView tries to clear the area NSTextView draws a caret (which is a thin caret).</p>
<h2>Clearing thick caret</h2>
<p>Now the problem is clearing caret. The clearing a caret is done in &#8220;drawIPR&#8221; when the &#8220;turnedOn&#8221; argument is NO. We can tell NSTextView to redraw the view by calling &#8220;setNeedsDisplay&#8221; as we already saw.</p>
<h2>Solution</h2>
<p>The following code works well.</p>
<pre class="brush: cpp; gutter: false">
-(void)_drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color{
    NSRange charRange = NSMakeRange(self.selectedRange.location, 1);
    NSRect glyphRect = [[self layoutManager] boundingRectForGlyphRange:charRange inTextContainer:[self textContainer]];
    if( glyphRect.size.width == 0 ||
       [[NSCharacterSet newlineCharacterSet] characterIsMember:[[self string] characterAtIndex:self.selectedRange.location]] ){
        glyphRect = rect;
    }
    color = [color colorWithAlphaComponent:0.5];
    [color set];
    NSRectFillUsingOperation(glyphRect, NSCompositeSourceOver);
    // We do not call super calls since the method in the super class uses NSRectFill and calling it results in filling the rect with the color without transparency.
}

- (void)drawInsertionPointInRect:(NSRect)rect color:(NSColor *)color turnedOn:(BOOL)flag{
    // Call super class first.
    [super drawInsertionPointInRect:rect color:color turnedOn:flag];
    // Then tell the view to redraw to clear a caret.
    if( !flag ){
        [self setNeedsDisplay:YES];
    }
}
</pre>
<p>The problem I can think of is that &#8220;setNeedsDisplay:YES&#8221; is not really optimised way to redraw the view since we are just interested in clearing the rect of a caret but not entire view. What we have to do for it is  to keep the rect of a caret in a instance variable (by adding to the class) and call &#8220;setNeedsDisplayInRect:&#8221; to redraw that area.</p>
<h2>(Appendix)Further investigation</h2>
<p>I also did some further investigation for NSTextView internal.<br />
The root of the problem of not clearing a caret is that NSTextView keeps the rect of a caret rect in an internal variable and use it to clear the caret. If we can update it by ourselves the problem should be solved. By using some Objective-C runtime functions I reverse engineered NSTextView class and I found the followings.</p>
<ul>
<li>NSTextView has a private variable named &#8220;_ivars&#8221;</li>
<li>&#8220;_ivars&#8221; is a instence of NSTextViewIvars .</li>
<li>NSTextViewIvars has several variables (maybe 12 variables).</li>
<li>One of them is named &#8220;_insertionPointRect&#8221;<br />
(I found &#8220;_insertionPointRectCache&#8221; too but do not know how this is used)</li>
</ul>
<p>And also I found that after calling &#8220;_drawIPR&#8221; of NSTextView the value of the &#8220;_insertionPointRect&#8221; is updated. </p>
<h2>Another solution (not recommended)</h2>
<p>(If you are coming from top to get the code which works, use the code in &#8220;Solution&#8221; section.)<br />
So what we should do is</p>
<ul>
<li>Override &#8220;_drawIPR&#8221;</li>
<li>In &#8220;_drawIPR&#8221;, draw thick/transparent caret.</li>
<li>In &#8220;_drawIPR&#8221;, update &#8220;_insertionPointRect&#8221; to remember the rect of our caret.</li>
</ul>
<p>So the code I got to achieve from this investigation is&#8230; </p>
<pre class="brush: cpp; gutter: false">
-(void)_drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color{
    NSRange charRange = NSMakeRange(self.selectedRange.location, 1);
    NSRect glyphRect = [[self layoutManager] boundingRectForGlyphRange:charRange inTextContainer:[self textContainer]];
    if( glyphRect.size.width == 0 ||
       [[NSCharacterSet newlineCharacterSet] characterIsMember:[[self string] characterAtIndex:self.selectedRange.location]] ){
        glyphRect = rect;
    }
    color = [color colorWithAlphaComponent:0.5];
    [color set];
    NSRectFillUsingOperation(glyphRect, NSCompositeSourceOver);
    // Update internal variables (_insertionPointRect) in NSTextView
    id nsTextViewIvars;
    object_getInstanceVariable(self, &quot;_ivars&quot;, (void**)&amp;nsTextViewIvars);
    [nsTextViewIvars setValue:[NSValue valueWithRect:glyphRect] forKey:@&quot;_insertionPointRect&quot;];
}
// And you do not need to override &quot;drawIPR&quot; since NSTextView automatically clears a caret.
</pre>
<p>Yes. This clears a caret we draw correctly. But I sometimes see a caret draws several times at one time when you move a caret. Not too bad but not as beautiful as the code in &#8220;Solution&#8221; section.</p>
<p>Thank you for reading and I hope this helps you.</p>
]]></content:encoded>
			<wfw:commentRss>http://programming.jugglershu.net/wp/?feed=rss2&#038;p=765</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
