Home > Net >  NSLayoutManager glyph generation and caret position
NSLayoutManager glyph generation and caret position

Time:10-05

I have a text editor app, which uses Markdown-style formatting. Formatting characters are hidden when caret/selection is not on the corresponding line.

I'm successfully using shouldGenerateGlyphs: method in NSLayoutManagerDelegate to manipulate what is drawn on NSTextView. I'm using pure delegation and haven't subclassed the layout manager itself.

However, I can't understand how I can position the caret correctly after glyph regeneration. After selection has changed, I'm calling a method (here called hideAndShowMarkup) which regenerates glyphs for both the line currently edited and the one that was selected earlier. Because some glyphs are added on screen, caret gets rendered wrong.

-(void)hideAndShowMarkup {
    [self.layoutManager invalidateGlyphsForCharacterRange:currentLine.range changeInLength:0 actualCharacterRange:nil];
    [self.layoutManager invalidateGlyphsForCharacterRange:prevLine.range changeInLength:0 actualCharacterRange:nil];
    
    [self.layoutManager ensureGlyphsForCharacterRange:currentLine.range];
    [self.layoutManager ensureGlyphsForCharacterRange:prevLine.range];

    [self updateInsertionPointStateAndRestartTimer:YES];
}

updateInsertionPoint... doesn't work here, as ensuring glyphs seems to run asynchronously. I've tried calling it in didCompleteLayoutForTextContainer: delegate method too, but to no effect.

Is there a way to detect when the glyphs have actually been drawn, and to ensure insertion point position after that?

CodePudding user response:

NSLayoutManager is surprisingly poorly documented, and understanding its inner workings required looking through implementations in random public repositories.

To change glyphs in a range and have the caret position correctly, you first need to invalidate glyphs for both of the changed ranges, and then invalidate the layout for the range the caret is about to be positioned in.

Then, after making sure that there is a current graphic context, glyphs can be redrawn synchronously. After this, updating insertion point works normally.

In the example below, I have two objects, line and prevLine which contain ranges for both the line on which caret is positioned now, and the one caret moved away from.

-(void)hideAndShowMarkup {
    [self.layoutManager invalidateGlyphsForCharacterRange:line.range changeInLength:0 actualCharacterRange:nil];
    [self.layoutManager invalidateGlyphsForCharacterRange:prevLine.range changeInLength:0 actualCharacterRange:nil];
    [self.layoutManager invalidateLayoutForCharacterRange:line.range actualCharacterRange:nil];
        
    if (NSGraphicsContext.currentContext) {
        [self.layoutManager drawGlyphsForGlyphRange:line.range atPoint:self.frame.origin];
        [self.layoutManager drawGlyphsForGlyphRange:prevLine.range atPoint:self.frame.origin];
    }
    
    [self updateInsertionPointStateAndRestartTimer:NO];
}
  • Related