From 9f66442e6018c063d2ba01c59880e308af6dd75f Mon Sep 17 00:00:00 2001 From: groverlynn Date: Mon, 11 Mar 2024 10:00:00 +0100 Subject: [PATCH] fix layout bugs --- .../Contents.json | 15 - ....down.and.line.horizontal.and.arrow.up.svg | 160 -- .../Contents.json | 15 - ....up.and.line.horizontal.and.arrow.down.svg | 160 -- .../Contents.json | 3 - .../Contents.json | 3 - .../chevron.down.symbolset/Contents.json | 12 + .../chevron.down.symbolset/chevron.down.svg | 160 ++ .../Contents.json | 3 - .../Contents.json | 3 - .../Contents.json | 3 - .../Contents.json | 3 - .../Contents.json | 3 - .../chevron.up.circle.symbolset/Contents.json | 3 - .../chevron.up.symbolset/Contents.json | 12 + .../chevron.up.symbolset/chevron.up.svg | 160 ++ .../Contents.json | 3 - .../delete.backward.symbolset/Contents.json | 3 - SquirrelInputController.m | 102 +- SquirrelPanel.h | 15 +- SquirrelPanel.m | 1541 +++++++++-------- 21 files changed, 1245 insertions(+), 1137 deletions(-) delete mode 100644 Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/Contents.json delete mode 100644 Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/arrow.down.and.line.horizontal.and.arrow.up.svg delete mode 100644 Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/Contents.json delete mode 100644 Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/arrow.up.and.line.horizontal.and.arrow.down.svg create mode 100644 Assets.xcassets/Symbols/chevron.down.symbolset/Contents.json create mode 100644 Assets.xcassets/Symbols/chevron.down.symbolset/chevron.down.svg create mode 100644 Assets.xcassets/Symbols/chevron.up.symbolset/Contents.json create mode 100644 Assets.xcassets/Symbols/chevron.up.symbolset/chevron.up.svg diff --git a/Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/Contents.json b/Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/Contents.json deleted file mode 100644 index fa61fb274..000000000 --- a/Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "symbol-rendering-intent" : "template" - }, - "symbols" : [ - { - "filename" : "arrow.down.and.line.horizontal.and.arrow.up.svg", - "idiom" : "universal" - } - ] -} diff --git a/Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/arrow.down.and.line.horizontal.and.arrow.up.svg b/Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/arrow.down.and.line.horizontal.and.arrow.up.svg deleted file mode 100644 index d402b74ba..000000000 --- a/Assets.xcassets/Symbols/arrow.down.and.line.horizontal.and.arrow.up.symbolset/arrow.down.and.line.horizontal.and.arrow.up.svg +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - Weight/Scale Variations - Ultralight - Thin - Light - Regular - Medium - Semibold - Bold - Heavy - Black - - - - - - - - - - - Design Variations - Symbols are supported in up to nine weights and three scales. - For optimal layout with text and other symbols, vertically align - symbols with the adjacent text. - - - - - - Margins - Leading and trailing margins on the left and right side of each symbol - can be adjusted by modifying the x-location of the margin guidelines. - Modifications are automatically applied proportionally to all - scales and weights. - - - - Exporting - Symbols should be outlined when exporting to ensure the - design is preserved when submitting to Xcode. - Template v.2.0 - Requires Xcode 12 or greater - Generated from arrow.down.and.line.horizontal.and.arrow.up - Typeset at 100.0 points - Small - Medium - Large - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/Contents.json b/Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/Contents.json deleted file mode 100644 index d27172bd5..000000000 --- a/Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "symbol-rendering-intent" : "template" - }, - "symbols" : [ - { - "filename" : "arrow.up.and.line.horizontal.and.arrow.down.svg", - "idiom" : "universal" - } - ] -} diff --git a/Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/arrow.up.and.line.horizontal.and.arrow.down.svg b/Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/arrow.up.and.line.horizontal.and.arrow.down.svg deleted file mode 100644 index 1145d99e9..000000000 --- a/Assets.xcassets/Symbols/arrow.up.and.line.horizontal.and.arrow.down.symbolset/arrow.up.and.line.horizontal.and.arrow.down.svg +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - Weight/Scale Variations - Ultralight - Thin - Light - Regular - Medium - Semibold - Bold - Heavy - Black - - - - - - - - - - - Design Variations - Symbols are supported in up to nine weights and three scales. - For optimal layout with text and other symbols, vertically align - symbols with the adjacent text. - - - - - - Margins - Leading and trailing margins on the left and right side of each symbol - can be adjusted by modifying the x-location of the margin guidelines. - Modifications are automatically applied proportionally to all - scales and weights. - - - - Exporting - Symbols should be outlined when exporting to ensure the - design is preserved when submitting to Xcode. - Template v.2.0 - Requires Xcode 12 or greater - Generated from arrow.up.and.line.horizontal.and.arrow.down - Typeset at 100.0 points - Small - Medium - Large - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Assets.xcassets/Symbols/chevron.down.circle.fill.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.down.circle.fill.symbolset/Contents.json index b9f37d2d2..d3c199f16 100644 --- a/Assets.xcassets/Symbols/chevron.down.circle.fill.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.down.circle.fill.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.down.circle.fill.svg", diff --git a/Assets.xcassets/Symbols/chevron.down.circle.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.down.circle.symbolset/Contents.json index 429d077ea..984190b39 100644 --- a/Assets.xcassets/Symbols/chevron.down.circle.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.down.circle.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.down.circle.svg", diff --git a/Assets.xcassets/Symbols/chevron.down.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.down.symbolset/Contents.json new file mode 100644 index 000000000..24d86edb8 --- /dev/null +++ b/Assets.xcassets/Symbols/chevron.down.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "chevron.down.svg", + "idiom" : "universal" + } + ] +} diff --git a/Assets.xcassets/Symbols/chevron.down.symbolset/chevron.down.svg b/Assets.xcassets/Symbols/chevron.down.symbolset/chevron.down.svg new file mode 100644 index 000000000..26086ef64 --- /dev/null +++ b/Assets.xcassets/Symbols/chevron.down.symbolset/chevron.down.svg @@ -0,0 +1,160 @@ + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.2.0 + Requires Xcode 12 or greater + Generated from chevron.down + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets.xcassets/Symbols/chevron.left.circle.fill.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.left.circle.fill.symbolset/Contents.json index af1cde5fa..180793b8e 100644 --- a/Assets.xcassets/Symbols/chevron.left.circle.fill.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.left.circle.fill.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.left.circle.fill.svg", diff --git a/Assets.xcassets/Symbols/chevron.left.circle.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.left.circle.symbolset/Contents.json index 6f8f69fc9..8be0d8280 100644 --- a/Assets.xcassets/Symbols/chevron.left.circle.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.left.circle.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.left.circle.svg", diff --git a/Assets.xcassets/Symbols/chevron.right.circle.fill.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.right.circle.fill.symbolset/Contents.json index 2cebdf058..d26b98e97 100644 --- a/Assets.xcassets/Symbols/chevron.right.circle.fill.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.right.circle.fill.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.right.circle.fill.svg", diff --git a/Assets.xcassets/Symbols/chevron.right.circle.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.right.circle.symbolset/Contents.json index b9fd72302..81ab74c7b 100644 --- a/Assets.xcassets/Symbols/chevron.right.circle.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.right.circle.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.right.circle.svg", diff --git a/Assets.xcassets/Symbols/chevron.up.circle.fill.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.up.circle.fill.symbolset/Contents.json index e811e05f5..07159b8e2 100644 --- a/Assets.xcassets/Symbols/chevron.up.circle.fill.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.up.circle.fill.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.up.circle.fill.svg", diff --git a/Assets.xcassets/Symbols/chevron.up.circle.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.up.circle.symbolset/Contents.json index f7e785481..41d98e84a 100644 --- a/Assets.xcassets/Symbols/chevron.up.circle.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/chevron.up.circle.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "chevron.up.circle.svg", diff --git a/Assets.xcassets/Symbols/chevron.up.symbolset/Contents.json b/Assets.xcassets/Symbols/chevron.up.symbolset/Contents.json new file mode 100644 index 000000000..329b5e370 --- /dev/null +++ b/Assets.xcassets/Symbols/chevron.up.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "chevron.up.svg", + "idiom" : "universal" + } + ] +} diff --git a/Assets.xcassets/Symbols/chevron.up.symbolset/chevron.up.svg b/Assets.xcassets/Symbols/chevron.up.symbolset/chevron.up.svg new file mode 100644 index 000000000..e35a6e2d0 --- /dev/null +++ b/Assets.xcassets/Symbols/chevron.up.symbolset/chevron.up.svg @@ -0,0 +1,160 @@ + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.2.0 + Requires Xcode 12 or greater + Generated from chevron.up + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets.xcassets/Symbols/delete.backward.fill.symbolset/Contents.json b/Assets.xcassets/Symbols/delete.backward.fill.symbolset/Contents.json index 009340fb1..91859e5d6 100644 --- a/Assets.xcassets/Symbols/delete.backward.fill.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/delete.backward.fill.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "delete.backward.fill.svg", diff --git a/Assets.xcassets/Symbols/delete.backward.symbolset/Contents.json b/Assets.xcassets/Symbols/delete.backward.symbolset/Contents.json index b7fe9eb3b..0a84febe1 100644 --- a/Assets.xcassets/Symbols/delete.backward.symbolset/Contents.json +++ b/Assets.xcassets/Symbols/delete.backward.symbolset/Contents.json @@ -3,9 +3,6 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { "filename" : "delete.backward.svg", diff --git a/SquirrelInputController.m b/SquirrelInputController.m index ef142b696..2effec155 100644 --- a/SquirrelInputController.m +++ b/SquirrelInputController.m @@ -246,14 +246,12 @@ - (BOOL)processKey:(int)rime_keycode modifiers:(int)rime_modifiers { if (!panel.locked && !panel.expanded && rime_keycode == (is_vertical ? XK_Left : XK_Down)) { [panel setExpanded:YES]; - _currentIndex = NSNotFound; } return (BOOL)rime_get_api()->highlight_candidate(_session, newIndex) || panel.visible; - } else if (!panel.locked && panel.expanded && panel.activePage == 0 && + } else if (!panel.locked && panel.expanded && panel.sectionNum == 0 && rime_keycode == (is_vertical ? XK_Right : XK_Up)) { [panel setExpanded:NO]; - _currentIndex = NSNotFound; return panel.visible; } } @@ -380,8 +378,8 @@ - (void)performAction:(SquirrelAction)action onIndex:(SquirrelIndex)index { handled = rime_get_api()->select_candidate(_session, index); break; case kHIGHLIGHT: - rime_get_api()->highlight_candidate(_session, index); - _currentIndex = index; + handled = rime_get_api()->highlight_candidate(_session, index); + _currentIndex = NSNotFound; break; case kDELETE: handled = rime_get_api()->delete_candidate(_session, index); @@ -618,7 +616,7 @@ - (id)composedString:(id)sender { } - (NSArray*)candidates:(id)sender { - return _candidates; + return NSApp.squirrelAppDelegate.panel.candidates; } - (void)hidePalettes { @@ -756,13 +754,12 @@ - (CGRect)getIbeamRect { - (void)showPanelWithPreedit:(NSString*)preedit selRange:(NSRange)selRange caretPos:(NSUInteger)caretPos - candidates:(NSArray*)candidates - comments:(NSArray*)comments + candidateIndices:(NSRange)indexRange highlightedIndex:(NSUInteger)highlightedIndex pageNum:(NSUInteger)pageNum - finalPage:(BOOL)finalPage { + finalPage:(BOOL)finalPage + didCompose:(BOOL)didCompose { // NSLog(@"showPanelWithPreedit:...:"); - _candidates = candidates; SquirrelPanel* panel = NSApp.squirrelAppDelegate.panel; panel.inputController = self; panel.IbeamRect = [self getIbeamRect]; @@ -772,11 +769,11 @@ - (void)showPanelWithPreedit:(NSString*)preedit [panel showPreedit:preedit selRange:selRange caretPos:caretPos - candidates:candidates - comments:comments + candidateIndices:indexRange highlightedIndex:highlightedIndex pageNum:pageNum - finalPage:finalPage]; + finalPage:finalPage + didCompose:didCompose]; } } @@ -919,36 +916,42 @@ - (void)rimeUpdate { : (NSUInteger)ctx.menu.highlighted_candidate_index; BOOL finalPage = (BOOL)ctx.menu.is_last_page; - // update expander and active page status in tabular layout; + // update expander and section status in tabular layout; // already processed the action if _currentIndex == NSNotFound if (panel.tabular && !showingStatus) { if (numCandidates == 0 || didCompose) { - panel.activePage = 0; + panel.sectionNum = 0; } else if (_currentIndex != NSNotFound) { NSUInteger currentPageNum = _currentIndex / pageSize; - if (!panel.locked && panel.expanded && panel.topRow && pageNum == 0 && - highlightedIndex == 0) { + if (!panel.locked && panel.expanded && panel.firstLine && + pageNum == 0 && highlightedIndex == 0 && _currentIndex == 0) { panel.expanded = NO; } else if (!panel.locked && !panel.expanded && pageNum > currentPageNum) { panel.expanded = YES; } if (panel.expanded && pageNum > currentPageNum && - panel.activePage < 4) { - panel.activePage = MIN(panel.activePage + pageNum - currentPageNum, - finalPage ? 4UL : 3UL); + panel.sectionNum < (panel.vertical ? 2 : 4)) { + panel.sectionNum = + MIN(panel.sectionNum + pageNum - currentPageNum, + (finalPage ? 4UL : 3UL) - (panel.vertical ? 2UL : 0UL)); } else if (panel.expanded && pageNum < currentPageNum && - panel.activePage > 0) { - panel.activePage = MAX(panel.activePage + pageNum - currentPageNum, + panel.sectionNum > 0) { + panel.sectionNum = MAX(panel.sectionNum + pageNum - currentPageNum, pageNum == 0 ? 0UL : 1UL); } } - highlightedIndex += pageSize * panel.activePage; + highlightedIndex += pageSize * panel.sectionNum; } - _currentIndex = - numCandidates == 0 - ? NSNotFound - : highlightedIndex + pageSize * (pageNum - panel.activePage); + NSUInteger extraCandidates = + panel.expanded + ? (finalPage ? panel.sectionNum : (panel.vertical ? 2 : 4)) * + pageSize + : 0; + NSRange candidateIndices = + NSMakeRange((pageNum - panel.sectionNum) * pageSize, + numCandidates + extraCandidates); + _currentIndex = highlightedIndex + candidateIndices.location; if (showingStatus) { [self clearBuffer]; @@ -1000,41 +1003,48 @@ - (void)rimeUpdate { caretPos:0]; } } + if (didCompose || numCandidates == 0) { + [panel.candidates removeAllObjects]; + [panel.comments removeAllObjects]; + } // update candidates - NSMutableArray* candidates = - numCandidates ? [[NSMutableArray alloc] init] : nil; - NSMutableArray* comments = - numCandidates ? [[NSMutableArray alloc] init] : nil; - if (numCandidates > 0 && panel.expanded && panel.activePage > 0) { - NSUInteger index = pageSize * (pageNum - panel.activePage); + if (panel.candidates.count < pageSize * pageNum) { + NSUInteger index = panel.candidates.count; RimeCandidateListIterator iterator; if (rime_get_api()->candidate_list_from_index(_session, &iterator, (int)index)) { NSUInteger endIndex = pageSize * pageNum; while (index++ < endIndex && rime_get_api()->candidate_list_next(&iterator)) { - [candidates addObject:@(iterator.candidate.text)]; - [comments addObject:@(iterator.candidate.comment ?: "")]; + [panel.candidates addObject:@(iterator.candidate.text)]; + [panel.comments addObject:@(iterator.candidate.comment ?: "")]; } rime_get_api()->candidate_list_end(&iterator); } } - for (NSUInteger i = 0; i < numCandidates; ++i) { - [candidates addObject:@(ctx.menu.candidates[i].text)]; - [comments addObject:@(ctx.menu.candidates[i].comment ?: "")]; + if (panel.candidates.count < pageSize * (pageNum + 1)) { + for (NSUInteger i = 0; i < numCandidates; ++i) { + panel.candidates[pageSize * pageNum + i] = + @(ctx.menu.candidates[i].text); + panel.comments[pageSize * pageNum + i] = + @(ctx.menu.candidates[i].comment ?: ""); + } } - if (numCandidates > 0 && panel.expanded && panel.activePage < 5) { - NSUInteger index = pageSize * (pageNum + 1); + if (panel.candidates.count < NSMaxRange(candidateIndices)) { + NSUInteger index = panel.candidates.count; RimeCandidateListIterator iterator; if (rime_get_api()->candidate_list_from_index(_session, &iterator, (int)index)) { - NSUInteger endIndex = pageSize * (pageNum + 5 - panel.activePage); + NSUInteger endIndex = + pageSize * (pageNum + (panel.vertical ? 3 : 5) - panel.sectionNum); while (index++ < endIndex && rime_get_api()->candidate_list_next(&iterator)) { - [candidates addObject:@(iterator.candidate.text)]; - [comments addObject:@(iterator.candidate.comment ?: "")]; + [panel.candidates addObject:@(iterator.candidate.text)]; + [panel.comments addObject:@(iterator.candidate.comment ?: "")]; } rime_get_api()->candidate_list_end(&iterator); + candidateIndices.length = + panel.candidates.count - candidateIndices.location; } } [self showPanelWithPreedit:_inlinePreedit && !_showingSwitcherMenu @@ -1042,11 +1052,11 @@ - (void)rimeUpdate { : preeditText selRange:NSMakeRange(start, end - start) caretPos:_showingSwitcherMenu ? NSNotFound : caretPos - candidates:candidates - comments:comments + candidateIndices:candidateIndices highlightedIndex:highlightedIndex pageNum:pageNum - finalPage:finalPage]; + finalPage:finalPage + didCompose:didCompose]; rime_get_api()->free_context(&ctx); } else { [self hidePalettes]; diff --git a/SquirrelPanel.h b/SquirrelPanel.h index a3c8270eb..23045c8ea 100644 --- a/SquirrelPanel.h +++ b/SquirrelPanel.h @@ -14,12 +14,12 @@ typedef NS_ENUM(NSUInteger, SquirrelAppear) { // Linear candidate list layout, as opposed to stacked candidate list layout. @property(nonatomic, readonly) BOOL linear; // Tabular candidate list layout, initializes as tab-aligned linear layout, -// expandable to stack more candidates +// expandable to stack 5 (3 for vertical) pages/sections of candidates @property(nonatomic, readonly) BOOL tabular; @property(nonatomic, readonly) BOOL locked; -@property(nonatomic, readonly) BOOL topRow; -@property(nonatomic) NSUInteger activePage; +@property(nonatomic, readonly) BOOL firstLine; @property(nonatomic) BOOL expanded; +@property(nonatomic) NSUInteger sectionNum; // Vertical text orientation, as opposed to horizontal text orientation. @property(nonatomic, readonly) BOOL vertical; // Show preedit text inline. @@ -31,6 +31,9 @@ typedef NS_ENUM(NSUInteger, SquirrelAppear) { // Status message before pop-up is displayed; nil before normal panel is // displayed @property(nonatomic, strong, readonly, nullable) NSString* statusMessage; +// Store candidates and comments queried from rime +@property(nonatomic, strong, nullable) NSMutableArray* candidates; +@property(nonatomic, strong, nullable) NSMutableArray* comments; // position of the text input I-beam cursor on screen. @property(nonatomic) NSRect IbeamRect; @@ -41,11 +44,11 @@ typedef NS_ENUM(NSUInteger, SquirrelAppear) { - (void)showPreedit:(NSString* _Nullable)preedit selRange:(NSRange)selRange caretPos:(NSUInteger)caretPos - candidates:(NSArray* _Nullable)candidates - comments:(NSArray* _Nullable)comments + candidateIndices:(NSRange)indexRange highlightedIndex:(NSUInteger)highlightedIndex pageNum:(NSUInteger)pageNum - finalPage:(BOOL)finalPage; + finalPage:(BOOL)finalPage + didCompose:(BOOL)didCompose; - (void)hide; diff --git a/SquirrelPanel.m b/SquirrelPanel.m index 7ca28eae7..7b24cf764 100644 --- a/SquirrelPanel.m +++ b/SquirrelPanel.m @@ -78,7 +78,7 @@ - (void)superscriptRange:(NSRange)range { [self addAttributes:@{ NSFontAttributeName : font, (id)kCTBaselineClassAttributeName : - (id)kCTBaselineClassIdeographicHigh, + (id)kCTBaselineClassIdeographicCentered, NSSuperscriptAttributeName : @(1) } range:subRange]; @@ -99,7 +99,7 @@ - (void)subscriptRange:(NSRange)range { [self addAttributes:@{ NSFontAttributeName : font, (id)kCTBaselineClassAttributeName : - (id)kCTBaselineClassIdeographicLow, + (id)kCTBaselineClassIdeographicCentered, NSSuperscriptAttributeName : @(-1) } range:subRange]; @@ -113,14 +113,14 @@ - (void)formatMarkDown { error:nil]; NSInteger __block offset = 0; [regex - enumerateMatchesInString:self.string + enumerateMatchesInString:self.mutableString options:0 range:NSMakeRange(0, self.length) usingBlock:^(NSTextCheckingResult* _Nullable result, NSMatchingFlags flags, BOOL* _Nonnull stop) { result = [result resultByAdjustingRangesWithOffset:offset]; - NSString* tag = [self.string + NSString* tag = [self.mutableString substringWithRange:[result rangeAtIndex:1]]; if ([tag isEqualToString:@"**"] || [tag isEqualToString:@"__"] || @@ -211,7 +211,8 @@ - (CGFloat)annotateRubyInRange:(NSRange)range effectiveRange:NULL]; baseFont = CFBridgingRelease(CTFontCreateForStringWithLanguage( - (CTFontRef)baseFont, (CFStringRef)self.string, + (CTFontRef)baseFont, + (CFStringRef)self.mutableString, CFRangeMake((CFIndex)baseRange.location, (CFIndex)baseRange.length), CFSTR("zh"))); @@ -221,57 +222,30 @@ - (CGFloat)annotateRubyInRange:(NSRange)range CGFloat rubyScale = 0.5; CFStringRef rubyString = - (__bridge CFStringRef)[self.string + (__bridge CFStringRef)[self.mutableString substringWithRange:[result rangeAtIndex:4]]; - NSFont* rubyFont = - [self attribute:NSFontAttributeName - atIndex:[result rangeAtIndex:4].location - effectiveRange:NULL]; - rubyFont = [NSFont - fontWithDescriptor:rubyFont.fontDescriptor - size:rubyFont.pointSize * rubyScale]; - rubyFont = - CFBridgingRelease(CTFontCreateForStringWithLanguage( - (CTFontRef)rubyFont, rubyString, - CFRangeMake(0, CFStringGetLength(rubyString)), - CFSTR("zh"))); - rubyFont = - isVertical ? rubyFont.verticalFont : rubyFont; + CGFloat height = + isVertical + ? (baseFont.verticalFont.ascender - + baseFont.verticalFont.descender) + : (baseFont.ascender - baseFont.descender); rubyLineHeight = - MAX(rubyLineHeight, - rubyFont.ascender - rubyFont.descender); - CGColorRef rubyColor = - [[self attribute:NSForegroundColorAttributeName - atIndex:[result rangeAtIndex:4].location - effectiveRange:NULL] CGColor]; - CFTypeRef keys[] = { - kCTFontAttributeName, - kCTForegroundColorAttributeName, - kCTBaselineClassAttributeName, - kCTRubyAnnotationSizeFactorAttributeName, - kCTRubyAnnotationScaleToFitAttributeName}; - CFTypeRef values[] = { - (__bridge CTFontRef)rubyFont, rubyColor, - kCTBaselineClassIdeographicHigh, - CFNumberCreate(NULL, kCFNumberDoubleType, - &rubyScale), - kCFBooleanFalse}; - CFDictionaryRef rubyAttrs = CFDictionaryCreate( - NULL, keys, values, 5, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); + MAX(rubyLineHeight, ceil(height * 0.5)); + CFStringRef rubyText[kCTRubyPositionCount]; + rubyText[kCTRubyPositionBefore] = rubyString; + rubyText[kCTRubyPositionAfter] = NULL; + rubyText[kCTRubyPositionInterCharacter] = NULL; + rubyText[kCTRubyPositionInline] = NULL; CTRubyAnnotationRef rubyAnnotation = - CTRubyAnnotationCreateWithAttributes( + CTRubyAnnotationCreate( kCTRubyAlignmentDistributeSpace, - kCTRubyOverhangNone, kCTRubyPositionBefore, - rubyString, rubyAttrs); + kCTRubyOverhangNone, rubyScale, rubyText); [self deleteCharactersInRange:[result rangeAtIndex:3]]; if (@available(macOS 12.0, *)) { [self addAttributes:@{ (id)kCTRubyAnnotationAttributeName : - CFBridgingRelease(rubyAnnotation), - NSVerticalGlyphFormAttributeName : @(isVertical) + CFBridgingRelease(rubyAnnotation) } range:baseRange]; [self @@ -437,6 +411,7 @@ typedef NS_ENUM(NSUInteger, SquirrelStatusMessageType) { @property(nonatomic, readonly) CGFloat alpha; @property(nonatomic, readonly) CGFloat translucency; @property(nonatomic, readonly) CGFloat lineLength; +@property(nonatomic, readonly) CGFloat expanderWidth; @property(nonatomic, readonly) NSSize borderInset; @property(nonatomic, readonly) BOOL showPaging; @property(nonatomic, readonly) BOOL rememberSize; @@ -471,6 +446,10 @@ typedef NS_ENUM(NSUInteger, SquirrelStatusMessageType) { NSParagraphStyle* statusParagraphStyle; @property(nonatomic, strong, readonly, nonnull) NSAttributedString* separator; +@property(nonatomic, strong, readonly, nonnull) + NSAttributedString* symbolDeleteFill; +@property(nonatomic, strong, readonly, nonnull) + NSAttributedString* symbolDeleteStroke; @property(nonatomic, strong, readonly, nullable) NSAttributedString* symbolBackFill; @property(nonatomic, strong, readonly, nullable) @@ -479,16 +458,11 @@ typedef NS_ENUM(NSUInteger, SquirrelStatusMessageType) { NSAttributedString* symbolForwardFill; @property(nonatomic, strong, readonly, nullable) NSAttributedString* symbolForwardStroke; -@property(nonatomic, strong, readonly, nullable) - NSAttributedString* symbolDeleteFill; -@property(nonatomic, strong, readonly, nullable) - NSAttributedString* symbolDeleteStroke; @property(nonatomic, strong, readonly, nullable) NSAttributedString* symbolCompress; @property(nonatomic, strong, readonly, nullable) NSAttributedString* symbolExpand; @property(nonatomic, strong, readonly, nullable) NSAttributedString* symbolLock; -@property(nonatomic, readonly) CGFloat expanderWidth; @property(nonatomic, strong, readonly, nonnull) NSString* selectKeys; @property(nonatomic, strong, readonly, nonnull) NSString* candidateFormat; @@ -715,7 +689,6 @@ - (instancetype)init { _pagingAttrs = pagingAttrs; _pagingHighlightedAttrs = pagingHighlightedAttrs; _statusAttrs = statusAttrs; - [self updateSeperatorAndSymbolAttrs]; _paragraphStyle = paragraphStyle; _preeditParagraphStyle = preeditParagraphStyle; @@ -727,6 +700,7 @@ - (instancetype)init { _pageSize = 5; _candidateFormat = kDefaultCandidateFormat; [self updateCandidateFormats]; + [self updateSeperatorAndSymbolAttrs]; } return self; } @@ -801,7 +775,6 @@ - (void)setAttrs:(NSDictionary*)attrs _pagingAttrs = pagingAttrs; _pagingHighlightedAttrs = pagingHighlightedAttrs; _statusAttrs = statusAttrs; - [self updateSeperatorAndSymbolAttrs]; } - (void)updateSeperatorAndSymbolAttrs { @@ -839,16 +812,14 @@ - (void)updateSeperatorAndSymbolAttrs { attributes:attrsDeleteStroke]; if (_tabular) { NSTextAttachment* attmCompress = [[NSTextAttachment alloc] init]; - attmCompress.image = [NSImage - imageNamed:@"Symbols/arrow.down.and.line.horizontal.and.arrow.up"]; + attmCompress.image = [NSImage imageNamed:@"Symbols/chevron.up"]; NSMutableDictionary* attrsCompress = _pagingAttrs.mutableCopy; attrsCompress[NSAttachmentAttributeName] = attmCompress; _symbolCompress = [[NSAttributedString alloc] initWithString:attmCharacter attributes:attrsCompress]; NSTextAttachment* attmExpand = [[NSTextAttachment alloc] init]; - attmExpand.image = [NSImage - imageNamed:@"Symbols/arrow.up.and.line.horizontal.and.arrow.down"]; + attmExpand.image = [NSImage imageNamed:@"Symbols/chevron.down"]; NSMutableDictionary* attrsExpand = _pagingAttrs.mutableCopy; attrsExpand[NSAttachmentAttributeName] = attmExpand; _symbolExpand = [[NSAttributedString alloc] initWithString:attmCharacter @@ -861,9 +832,12 @@ - (void)updateSeperatorAndSymbolAttrs { _symbolLock = [[NSAttributedString alloc] initWithString:attmCharacter attributes:attrsLock]; - _expanderWidth = - MAX(MAX(_symbolCompress.size.width, _symbolExpand.size.width), - _symbolLock.size.width); + _expanderWidth = MAX( + MAX(ceil(_symbolCompress.size.width), ceil(_symbolExpand.size.width)), + ceil(_symbolLock.size.width)); + NSMutableParagraphStyle* paragraphStyle = _paragraphStyle.mutableCopy; + paragraphStyle.tailIndent = -_expanderWidth; + _paragraphStyle = paragraphStyle; } else if (_showPaging) { NSTextAttachment* attmBackFill = [[NSTextAttachment alloc] init]; attmBackFill.image = [NSImage @@ -930,6 +904,7 @@ - (void)setSelectKeys:(NSString*)selectKeys - (void)setCandidateFormat:(NSString*)candidateFormat { _candidateFormat = candidateFormat; [self updateCandidateFormats]; + [self updateSeperatorAndSymbolAttrs]; } - (void)updateCandidateFormats { @@ -1044,21 +1019,24 @@ - (void)updateCandidateFormats { NSFont* substituteFont = CFBridgingRelease( CTFontCreateForString((CTFontRef)labelFont, (CFStringRef)labelString, CFRangeMake(0, (CFIndex)labelString.length))); - NSMutableDictionary* labelAttrs = _labelAttrs.mutableCopy; - NSMutableDictionary* labelHighlightedAttrs = - _labelHighlightedAttrs.mutableCopy; - if (![substituteFont isEqualTo:labelFont]) { + if ([substituteFont isNotEqualTo:labelFont]) { + NSMutableDictionary* labelAttrs = _labelAttrs.mutableCopy; + NSMutableDictionary* labelHighlightedAttrs = + _labelHighlightedAttrs.mutableCopy; labelAttrs[NSFontAttributeName] = substituteFont; labelHighlightedAttrs[NSFontAttributeName] = substituteFont; + _labelAttrs = labelAttrs; + _labelHighlightedAttrs = labelHighlightedAttrs; + if (_linear) { + NSMutableDictionary* pagingAttrs = _pagingAttrs.mutableCopy; + NSMutableDictionary* pagingHighlightAttrs = + _pagingHighlightedAttrs.mutableCopy; + pagingAttrs[NSFontAttributeName] = substituteFont; + pagingHighlightAttrs[NSFontAttributeName] = substituteFont; + _pagingAttrs = pagingAttrs; + _pagingHighlightedAttrs = pagingHighlightAttrs; + } } - labelAttrs[(id)kCTBaselineInfoAttributeName] = @{ - (id)kCTBaselineClassIdeographicCentered : @(substituteFont.capHeight * 0.5) - }; - labelHighlightedAttrs[(id)kCTBaselineInfoAttributeName] = @{ - (id)kCTBaselineClassIdeographicCentered : @(substituteFont.capHeight * 0.5) - }; - _labelAttrs = labelAttrs.copy; - _labelHighlightedAttrs = labelHighlightedAttrs.copy; candidateRange = [candidateFormat rangeOfString:@"%@"]; labelRange = NSMakeRange(0, candidateRange.location); @@ -1081,7 +1059,7 @@ - (void)updateCandidateFormats { [format formatMarkDown]; [highlightedFormat formatMarkDown]; // add placeholder for comment '%s' - candidateRange = [format.string rangeOfString:@"%@"]; + candidateRange = [format.mutableString rangeOfString:@"%@"]; commentRange = NSMakeRange(NSMaxRange(candidateRange), format.length - NSMaxRange(candidateRange)); if (commentRange.length > 0) { @@ -1089,13 +1067,13 @@ - (void)updateCandidateFormats { replaceCharactersInRange:commentRange withString:[kTipSpecifier stringByAppendingString: - [format.string + [format.mutableString substringWithRange:commentRange]]]; [highlightedFormat replaceCharactersInRange:commentRange withString:[kTipSpecifier stringByAppendingString: - [highlightedFormat.string + [highlightedFormat.mutableString substringWithRange:commentRange]]]; } else { [format appendAttributedString:[[NSAttributedString alloc] @@ -1111,7 +1089,7 @@ - (void)updateCandidateFormats { [[NSMutableArray alloc] initWithCapacity:labels.count]; NSMutableArray* candidateHighlightedFormats = [[NSMutableArray alloc] initWithCapacity:labels.count]; - enumRange = [format.string rangeOfString:@"%c"]; + enumRange = [format.mutableString rangeOfString:@"%c"]; for (NSString* label in labels) { NSMutableAttributedString* newFormat = format.mutableCopy; NSMutableAttributedString* newHighlightedFormat = @@ -1249,9 +1227,8 @@ - (void)drawGlyphsForGlyphRange:(NSRange)glyphRange atPoint:(NSPoint)origin { offset.y += (runFont.capHeight - runFont.pointSize) * (superscript == 0 - ? 0.5 - : (superscript == 1 ? 1.0 / 0.55 - 0.55 - : 0.0)); + ? 0.25 + : (superscript == 1 ? 0.5 / 0.55 : 0.0)); } NSPoint glyphOrigin = [textContainer.textView convertPointToBacking:NSMakePoint( @@ -1310,14 +1287,15 @@ - (BOOL)layoutManager:(NSLayoutManager*)layoutManager - (BOOL)layoutManager:(NSLayoutManager*)layoutManager shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex { - return charIndex <= 1 || [layoutManager.textStorage.string + return charIndex <= 1 || [layoutManager.textStorage.mutableString characterAtIndex:charIndex - 1] != '\t'; } - (NSControlCharacterAction)layoutManager:(NSLayoutManager*)layoutManager shouldUseAction:(NSControlCharacterAction)action forControlCharacterAtIndex:(NSUInteger)charIndex { - if ([layoutManager.textStorage.string characterAtIndex:charIndex] == 0x8B && + if ([layoutManager.textStorage.mutableString characterAtIndex:charIndex] == + 0x8B && [layoutManager.textStorage attribute:(id)kCTRubyAnnotationAttributeName atIndex:charIndex effectiveRange:NULL]) { @@ -1334,7 +1312,8 @@ - (NSRect)layoutManager:(NSLayoutManager*)layoutManager glyphPosition:(NSPoint)glyphPosition characterIndex:(NSUInteger)charIndex { CGFloat width = 0.0; - if ([layoutManager.textStorage.string characterAtIndex:charIndex] == 0x8B) { + if ([layoutManager.textStorage.mutableString characterAtIndex:charIndex] == + 0x8B) { NSRange rubyRange; id rubyAnnotation = [layoutManager.textStorage attribute:(id)kCTRubyAnnotationAttributeName @@ -1411,7 +1390,7 @@ - (BOOL)textLayoutManager:(NSTextLayoutManager*)textLayoutManager [contentStorage offsetFromLocation:contentStorage.documentRange.location toLocation:location]; return charIndex <= 1 || - [contentStorage.textStorage.string + [contentStorage.textStorage.mutableString characterAtIndex:(NSUInteger)charIndex - 1] != '\t'; } @@ -1434,34 +1413,35 @@ @interface SquirrelView : NSView typedef struct { NSUInteger index; - NSUInteger row; - NSUInteger tabColumn; -} SquirrelTabularPosition; - -@property(nonatomic, strong, readonly, nonnull) NSTextView* textView; -@property(nonatomic, strong, readonly, nonnull) NSTextStorage* textStorage; -@property(nonatomic, strong, readonly, nonnull) SquirrelTheme* currentTheme; -@property(nonatomic, strong, readonly, nonnull) CAShapeLayer* shape; -@property(nonatomic, strong, readonly, nullable) - NSMutableArray* candidatePaths; -@property(nonatomic, strong, readonly, nullable) - NSMutableArray* pagingPaths; -@property(nonatomic, strong, readonly, nullable) NSBezierPath* expanderPath; -@property(nonatomic, strong, readonly, nullable) NSBezierPath* deleteBackPath; -@property(nonatomic, readonly) NSUInteger numCandidates; -@property(nonatomic, readonly) NSRange preeditRange; -@property(nonatomic, readonly) NSRange highlightedPreeditRange; -@property(nonatomic, readonly) NSRange pagingRange; -@property(nonatomic, readonly) NSUInteger highlightedIndex; -@property(nonatomic, readonly) SquirrelIndex functionButton; + NSUInteger lineNum; + NSUInteger tabNum; +} SquirrelTabularIndex; + +@property(nonatomic, readonly, strong, nonnull) NSTextView* textView; +@property(nonatomic, readonly, strong, nonnull) NSTextStorage* textStorage; +@property(nonatomic, readonly, strong, nonnull) SquirrelTheme* currentTheme; +@property(nonatomic, readonly, strong, nonnull) CAShapeLayer* shape; +@property(nonatomic, readonly, nullable) SquirrelTabularIndex* tabularIndices; +@property(nonatomic, readonly, nullable) NSRectArray candidateRects; +@property(nonatomic, readonly, nullable) NSRectArray sectionRects; @property(nonatomic, readonly) NSRect contentRect; @property(nonatomic, readonly) NSRect preeditBlock; @property(nonatomic, readonly) NSRect candidateBlock; @property(nonatomic, readonly) NSRect pagingBlock; -@property(nonatomic, readonly) NSEdgeInsets alignmentRectInsets; +@property(nonatomic, readonly) NSRect deleteBackRect; +@property(nonatomic, readonly) NSRect expanderRect; +@property(nonatomic, readonly) NSRect pageUpRect; +@property(nonatomic, readonly) NSRect pageDownRect; @property(nonatomic, readonly) SquirrelAppear appear; -@property(nonatomic, readonly) SquirrelTabularPosition* tabularPositions; -@property(nonatomic) NSRange* candidateRanges; +@property(nonatomic, readonly) SquirrelIndex functionButton; +@property(nonatomic, readonly) NSEdgeInsets alignmentRectInsets; +@property(nonatomic, readonly) NSUInteger numCandidates; +@property(nonatomic, readonly) NSUInteger highlightedIndex; +@property(nonatomic, readonly) NSRange preeditRange; +@property(nonatomic, readonly) NSRange highlightedPreeditRange; +@property(nonatomic, readonly) NSRange pagingRange; +@property(nonatomic, nullable) NSRange* candidateRanges; +@property(nonatomic, nullable) BOOL* truncated; @property(nonatomic) BOOL expanded; - (NSTextRange* _Nullable)getTextRangeFromCharRange:(NSRange)charRange @@ -1473,9 +1453,9 @@ - (NSRange)getCharRangeFromTextRange:(NSTextRange* _Nullable)textRange - (NSRect)blockRectForRange:(NSRange)range; - (void)multilineRectForRange:(NSRange)charRange - leadingRect:(NSRectPointer)leadingRect - bodyRect:(NSRectPointer)bodyRect - trailingRect:(NSRectPointer)trailingRect; + leadingRect:(NSRectPointer _Nonnull)leadingRect + bodyRect:(NSRectPointer _Nonnull)bodyRect + trailingRect:(NSRectPointer _Nonnull)trailingRect; - (void)drawViewWithInsets:(NSEdgeInsets)alignmentRectInsets numCandidates:(NSUInteger)numCandidates @@ -1484,6 +1464,11 @@ - (void)drawViewWithInsets:(NSEdgeInsets)alignmentRectInsets highlightedPreeditRange:(NSRange)highlightedPreeditRange pagingRange:(NSRange)pagingRange; +- (void)setPreeditRange:(NSRange)preeditRange + highlightedPreeditRange:(NSRange)highlightedPreeditRange; + +- (void)highlightCandidate:(NSUInteger)highlightedIndex; + - (void)highlightFunctionButton:(SquirrelIndex)functionButton; - (SquirrelIndex)getIndexFromMouseSpot:(NSPoint)spot; @@ -1491,9 +1476,6 @@ - (SquirrelIndex)getIndexFromMouseSpot:(NSPoint)spot; @end @implementation SquirrelView -SquirrelTheme* _defaultTheme; -SquirrelTheme* _darkTheme; - // Need flipped coordinate system, as required by textStorage - (BOOL)isFlipped { return YES; @@ -1522,7 +1504,13 @@ - (SquirrelAppear)appear { } - (SquirrelTheme*)selectTheme:(SquirrelAppear)appear { - return appear == darkAppear ? _darkTheme : _defaultTheme; + static SquirrelTheme* defaultTheme = [[SquirrelTheme alloc] init]; + if (@available(macOS 10.14, *)) { + static SquirrelTheme* darkTheme = [[SquirrelTheme alloc] init]; + return appear == darkAppear ? darkTheme : defaultTheme; + } else { + return defaultTheme; + } } - (SquirrelTheme*)currentTheme { @@ -1573,10 +1561,6 @@ - (instancetype)initWithFrame:(NSRect)frameRect { _textView.wantsLayer = NO; _shape = [[CAShapeLayer alloc] init]; - _defaultTheme = [[SquirrelTheme alloc] init]; - if (@available(macOS 10.14, *)) { - _darkTheme = [[SquirrelTheme alloc] init]; - } } return self; } @@ -1636,20 +1620,16 @@ - (NSRect)blockRectForRange:(NSRange)range { NSRect __block blockRect = NSZeroRect; [_textView.textLayoutManager enumerateTextSegmentsInRange:textRange - type:NSTextLayoutManagerSegmentTypeHighlight + type:NSTextLayoutManagerSegmentTypeStandard options: NSTextLayoutManagerSegmentOptionsRangeNotRequired - usingBlock:^( + usingBlock:^BOOL( NSTextRange* _Nullable segRange, CGRect segFrame, CGFloat baseline, NSTextContainer* _Nonnull textContainer) { blockRect = NSUnionRect(blockRect, segFrame); return YES; }]; - CGFloat lineSpacing = [[_textStorage attribute:NSParagraphStyleAttributeName - atIndex:NSMaxRange(range) - 1 - effectiveRange:NULL] lineSpacing]; - blockRect.size.height += lineSpacing; return blockRect; } else { NSTextContainer* textContainer = _textView.textContainer; @@ -1689,44 +1669,42 @@ - (void)multilineRectForRange:(NSRange)charRange trailingRect:(NSRectPointer)trailingRect { if (@available(macOS 12.0, *)) { NSTextRange* textRange = [self getTextRangeFromCharRange:charRange]; - NSMutableArray* lineRects = [[NSMutableArray alloc] init]; - NSMutableArray* lineRanges = [[NSMutableArray alloc] init]; + NSRect __block leadingLineRect = NSZeroRect; + NSRect __block trailingLineRect = NSZeroRect; + NSTextRange __block* leadingLineRange; + NSTextRange __block* trailingLineRange; [_textView.textLayoutManager enumerateTextSegmentsInRange:textRange - type:NSTextLayoutManagerSegmentTypeHighlight - options:NSTextLayoutManagerSegmentOptionsNone - usingBlock:^( + type:NSTextLayoutManagerSegmentTypeStandard + options: + NSTextLayoutManagerSegmentOptionsMiddleFragmentsExcluded + usingBlock:^BOOL( NSTextRange* _Nullable segRange, CGRect segFrame, CGFloat baseline, NSTextContainer* _Nonnull textContainer) { if (!NSIsEmptyRect(segFrame)) { - NSRect lastSegFrame = - lineRects.count > 0 - ? [lineRects.lastObject rectValue] - : NSZeroRect; - if (NSMinY(segFrame) < NSMaxY(lastSegFrame)) { - segFrame = NSUnionRect(segFrame, lastSegFrame); - lineRects[lineRects.count - 1] = - [NSValue valueWithRect:segFrame]; - segRange = [segRange + if (NSIsEmptyRect(leadingLineRect) || + NSMinY(segFrame) < NSMaxY(leadingLineRect)) { + leadingLineRect = + NSUnionRect(segFrame, leadingLineRect); + leadingLineRange = [leadingLineRange textRangeByFormingUnionWithTextRange: - lineRanges.lastObject]; - lineRanges[lineRanges.count - 1] = segRange; + segRange]; } else { - [lineRects - addObject:[NSValue valueWithRect:segFrame]]; - [lineRanges addObject:segRange]; + trailingLineRect = + NSUnionRect(segFrame, trailingLineRect); + trailingLineRange = [trailingLineRange + textRangeByFormingUnionWithTextRange: + segRange]; } } return YES; }]; - if (lineRects.count == 1) { - *bodyRect = [lineRects[0] rectValue]; + if (NSIsEmptyRect(trailingLineRect)) { + *bodyRect = leadingLineRect; } else { CGFloat containerWidth = self.contentRect.size.width; - NSRect leadingLineRect = [lineRects.firstObject rectValue]; leadingLineRect.size.width = containerWidth - NSMinX(leadingLineRect); - NSRect trailingLineRect = [lineRects.lastObject rectValue]; if (NSMaxX(trailingLineRect) == NSMaxX(leadingLineRect)) { if (NSMinX(leadingLineRect) == NSMinX(trailingLineRect)) { *bodyRect = NSUnionRect(leadingLineRect, trailingLineRect); @@ -1744,8 +1722,8 @@ - (void)multilineRectForRange:(NSRange)charRange NSMinY(trailingLineRect) - NSMinY(leadingLineRect)); } else { *leadingRect = leadingLineRect; - if (![lineRanges.lastObject - containsLocation:[lineRanges.firstObject endLocation]]) { + if (![trailingLineRange + containsLocation:leadingLineRange.endLocation]) { *bodyRect = NSMakeRect(0.0, NSMaxY(leadingLineRect), containerWidth, NSMinY(trailingLineRect) - NSMaxY(leadingLineRect)); @@ -1828,16 +1806,6 @@ - (void)drawViewWithInsets:(NSEdgeInsets)alignmentRectInsets _preeditRange = preeditRange; _highlightedPreeditRange = highlightedPreeditRange; _pagingRange = pagingRange; - _tabularPositions = - numCandidates > 0 ? new SquirrelTabularPosition[numCandidates] : NULL; - _expanderPath = nil; - _deleteBackPath = nil; - _candidatePaths = numCandidates > 0 ? [[NSMutableArray alloc] - initWithCapacity:numCandidates] - : nil; - _pagingPaths = pagingRange.length > 0 || self.expanded - ? [[NSMutableArray alloc] initWithCapacity:2] - : nil; _functionButton = kVoidSymbol; // invalidate Rect beyond bound of textview to clear any out-of-bound drawing // from last round @@ -1845,22 +1813,68 @@ - (void)drawViewWithInsets:(NSEdgeInsets)alignmentRectInsets _textView.needsDisplayInRect = self.bounds; } -- (void)highlightFunctionButton:(SquirrelIndex)functionButton { - _functionButton = functionButton; - if (_expanderPath) { - self.needsDisplayInRect = _expanderPath.bounds; - _textView.needsDisplayInRect = _expanderPath.bounds; +- (void)setPreeditRange:(NSRange)preeditRange + highlightedPreeditRange:(NSRange)highlightedPreeditRange { + if (_preeditRange.length != preeditRange.length) { + for (NSUInteger i = 0; i < _numCandidates; ++i) { + _candidateRanges[i].location += + preeditRange.length - _preeditRange.length; + } + if (_pagingRange.location != NSNotFound) { + _pagingRange.location += preeditRange.length - _preeditRange.length; + } } - if (_deleteBackPath) { - self.needsDisplayInRect = _deleteBackPath.bounds; - _textView.needsDisplayInRect = _deleteBackPath.bounds; + _preeditRange = preeditRange; + _highlightedPreeditRange = highlightedPreeditRange; + self.needsDisplayInRect = _preeditBlock; + _textView.needsDisplayInRect = _preeditBlock; +} + +- (void)highlightCandidate:(NSUInteger)highlightedIndex { + if (_expanded) { + NSUInteger prevActivePage = _highlightedIndex / self.currentTheme.pageSize; + NSUInteger newActivePage = highlightedIndex / self.currentTheme.pageSize; + if (newActivePage != prevActivePage) { + self.needsDisplayInRect = _sectionRects[prevActivePage]; + _textView.needsDisplayInRect = _sectionRects[prevActivePage]; + } + self.needsDisplayInRect = _sectionRects[newActivePage]; + _textView.needsDisplayInRect = _sectionRects[newActivePage]; + } else { + self.needsDisplayInRect = _candidateBlock; + _textView.needsDisplayInRect = _candidateBlock; } - if (_pagingPaths.count > 0) { - self.needsDisplayInRect = _pagingPaths[0].bounds; - self.needsDisplayInRect = _pagingPaths[1].bounds; - _textView.needsDisplayInRect = _pagingPaths[0].bounds; - _textView.needsDisplayInRect = _pagingPaths[1].bounds; + _highlightedIndex = highlightedIndex; +} + +- (void)highlightFunctionButton:(SquirrelIndex)functionButton { + for (SquirrelIndex index : + (SquirrelIndex[2]){_functionButton, functionButton}) { + switch (index) { + case kPageUpKey: + case kHomeKey: + self.needsDisplayInRect = _pageUpRect; + _textView.needsDisplayInRect = _pageUpRect; + break; + case kPageDownKey: + case kEndKey: + self.needsDisplayInRect = _pageDownRect; + _textView.needsDisplayInRect = _pageDownRect; + break; + case kBackSpaceKey: + case kEscapeKey: + self.needsDisplayInRect = _deleteBackRect; + _textView.needsDisplayInRect = _deleteBackRect; + break; + case kExpandButton: + case kCompressButton: + case kLockButton: + self.needsDisplayInRect = _expanderRect; + _textView.needsDisplayInRect = _expanderRect; + break; + } } + _functionButton = functionButton; } // Bezier cubic curve, which has continuous roundness @@ -2007,7 +2021,7 @@ static void multilineRectVertices(NSRect leadingRect, } } -static inline NSColor* hooverColor(NSColor* color, SquirrelAppear appear) { +static NSColor* hooverColor(NSColor* color, SquirrelAppear appear) { if (color == nil) { return nil; } @@ -2019,7 +2033,7 @@ static void multilineRectVertices(NSRect leadingRect, } } -static inline NSColor* disabledColor(NSColor* color, SquirrelAppear appear) { +static NSColor* disabledColor(NSColor* color, SquirrelAppear appear) { if (color == nil) { return nil; } @@ -2034,56 +2048,61 @@ static void multilineRectVertices(NSRect leadingRect, - (CAShapeLayer*)getFunctionButtonLayer { SquirrelTheme* theme = self.currentTheme; NSColor* buttonColor; - NSBezierPath* buttonPath; + NSRect buttonRect = NSZeroRect; switch (_functionButton) { case kPageUpKey: buttonColor = hooverColor(theme.linear && !theme.tabular ? theme.highlightedCandidateBackColor : theme.highlightedPreeditBackColor, self.appear); - buttonPath = _pagingPaths[0]; + buttonRect = _pageUpRect; break; case kHomeKey: buttonColor = disabledColor(theme.linear && !theme.tabular ? theme.highlightedCandidateBackColor : theme.highlightedPreeditBackColor, self.appear); - buttonPath = _pagingPaths[0]; + buttonRect = _pageUpRect; break; case kPageDownKey: buttonColor = hooverColor(theme.linear && !theme.tabular ? theme.highlightedCandidateBackColor : theme.highlightedPreeditBackColor, self.appear); - buttonPath = _pagingPaths[1]; + buttonRect = _pageDownRect; break; case kEndKey: buttonColor = disabledColor(theme.linear && !theme.tabular ? theme.highlightedCandidateBackColor : theme.highlightedPreeditBackColor, self.appear); - buttonPath = _pagingPaths[1]; + buttonRect = _pageDownRect; break; case kExpandButton: case kCompressButton: case kLockButton: buttonColor = hooverColor(theme.highlightedPreeditBackColor, self.appear); - buttonPath = _expanderPath; + buttonRect = _expanderRect; break; case kBackSpaceKey: buttonColor = hooverColor(theme.highlightedPreeditBackColor, self.appear); - buttonPath = _deleteBackPath; + buttonRect = _deleteBackRect; break; case kEscapeKey: buttonColor = disabledColor(theme.highlightedPreeditBackColor, self.appear); - buttonPath = _deleteBackPath; + buttonRect = _deleteBackRect; break; default: return nil; break; } - if (buttonPath && buttonColor) { + if (!NSIsEmptyRect(buttonRect) && buttonColor) { + CGFloat cornerRadius = + MIN(theme.highlightedCornerRadius, NSHeight(buttonRect) * 0.5); + NSPoint buttonVertices[4]; + rectVertices(buttonRect, buttonVertices); + NSBezierPath* buttonPath = squirclePath(buttonVertices, 4, cornerRadius); CAShapeLayer* functionButtonLayer = [[CAShapeLayer alloc] init]; functionButtonLayer.path = buttonPath.quartzPath; functionButtonLayer.fillColor = buttonColor.CGColor; @@ -2144,6 +2163,7 @@ - (void)updateLayer { // Draw preedit Rect _preeditBlock = NSZeroRect; + _deleteBackRect = NSZeroRect; NSBezierPath* highlightedPreeditPath; if (preeditRange.length > 0) { NSRect innerBox = [self blockRectForRange:preeditRange]; @@ -2225,24 +2245,24 @@ - (void)updateLayer { squirclePath(multilineVertices, numVert, cornerRadius); } } - NSRect deleteBackRect = + _deleteBackRect = [self blockRectForRange:NSMakeRange(NSMaxRange(_preeditRange) - 1, 1)]; - deleteBackRect.size.width += floor(theme.separatorWidth * 0.5); - deleteBackRect.origin.x = NSMaxX(backgroundRect) - NSWidth(deleteBackRect); - deleteBackRect.origin.y += _alignmentRectInsets.top; - deleteBackRect = [self - backingAlignedRect:NSIntersectionRect(deleteBackRect, _preeditBlock) + _deleteBackRect.size.width += floor(theme.separatorWidth * 0.5); + _deleteBackRect.origin.x = + NSMaxX(backgroundRect) - NSWidth(_deleteBackRect); + _deleteBackRect.origin.y += _alignmentRectInsets.top; + _deleteBackRect = [self + backingAlignedRect:NSIntersectionRect(_deleteBackRect, _preeditBlock) options:NSAlignAllEdgesNearest]; - NSPoint deleteBackVertices[4]; - rectVertices(deleteBackRect, deleteBackVertices); - _deleteBackPath = squirclePath(deleteBackVertices, 4, cornerRadius); } // Draw candidate Rect _candidateBlock = NSZeroRect; - NSBezierPath* candidateBlockPath; - NSBezierPath* gridPath; - NSBezierPath* activePagePath; + _candidateRects = NULL; + _sectionRects = NULL; + _tabularIndices = NULL; + NSBezierPath *candidateBlockPath, *highlightedCandidatePath; + NSBezierPath *gridPath, *activePagePath; if (candidateBlockRange.length > 0) { _candidateBlock = [self blockRectForRange:candidateBlockRange]; _candidateBlock.size.width = backgroundRect.size.width; @@ -2271,19 +2291,24 @@ - (void)updateLayer { CGFloat cornerRadius = MIN(theme.highlightedCornerRadius, theme.paragraphStyle.minimumLineHeight * 0.5); if (theme.linear) { + _candidateRects = new NSRect[_numCandidates * 3]; CGFloat gridOriginY; CGFloat tabInterval; - NSUInteger rowNum = 0; - NSRect activePageBlock = NSZeroRect; + NSUInteger lineNum = 0; + NSRect sectionRect = _candidateBlock; if (theme.tabular) { + _tabularIndices = new SquirrelTabularIndex[_numCandidates]; + _sectionRects = new NSRect[_numCandidates / theme.pageSize]; gridPath = [NSBezierPath bezierPath]; gridOriginY = NSMinY(_candidateBlock); tabInterval = theme.separatorWidth * 2; + sectionRect.size.height = 0; } for (NSUInteger i = 0; i < _numCandidates; ++i) { NSRange candidateRange = NSIntersectionRange(_candidateRanges[i], visibleRange); if (candidateRange.length == 0) { + _numCandidates = i; break; } NSRect leadingRect = NSZeroRect; @@ -2293,7 +2318,6 @@ - (void)updateLayer { leadingRect:&leadingRect bodyRect:&bodyRect trailingRect:&trailingRect]; - NSInteger numVert = 0; if (NSIsEmptyRect(leadingRect)) { bodyRect.origin.y -= ceil(theme.linespace * 0.5); bodyRect.size.height += ceil(theme.linespace * 0.5); @@ -2307,59 +2331,61 @@ - (void)updateLayer { [self backingAlignedRect:NSIntersectionRect(leadingRect, _candidateBlock) options:NSAlignAllEdgesNearest]; - numVert += 4; } if (NSIsEmptyRect(trailingRect)) { bodyRect.size.height += floor(theme.linespace * 0.5); } else { trailingRect.origin.x += theme.borderInset.width; - trailingRect.size.width += theme.separatorWidth; + trailingRect.size.width += theme.tabular ? 0.0 : theme.separatorWidth; trailingRect.origin.y += _alignmentRectInsets.top; trailingRect.size.height += floor(theme.linespace * 0.5); trailingRect = [self backingAlignedRect:NSIntersectionRect(trailingRect, _candidateBlock) options:NSAlignAllEdgesNearest]; - numVert += 4; } if (!NSIsEmptyRect(bodyRect)) { bodyRect.origin.x += theme.borderInset.width; - bodyRect.size.width += theme.separatorWidth; + if (_truncated[i]) { + bodyRect.size.width = NSMaxX(_candidateBlock) - NSMinX(bodyRect); + } else { + bodyRect.size.width += theme.tabular && NSIsEmptyRect(trailingRect) + ? 0.0 + : theme.separatorWidth; + } bodyRect.origin.y += _alignmentRectInsets.top; bodyRect = [self backingAlignedRect:NSIntersectionRect(bodyRect, _candidateBlock) options:NSAlignAllEdgesNearest]; - numVert += 2; } if (theme.tabular) { - if (self.expanded && - i / theme.pageSize == _highlightedIndex / theme.pageSize) { + if (self.expanded) { if (i % theme.pageSize == 0) { - activePageBlock.origin = NSIsEmptyRect(leadingRect) - ? bodyRect.origin - : leadingRect.origin; + sectionRect.origin.y += NSHeight(sectionRect); } else if (i % theme.pageSize == theme.pageSize - 1) { - activePageBlock.size.height = + sectionRect.size.height = NSMaxY(NSIsEmptyRect(trailingRect) ? bodyRect : trailingRect) - - activePageBlock.origin.y; - activePageBlock.size.width = NSWidth(_candidateBlock); - NSPoint activePageVertices[4]; - rectVertices(activePageBlock, activePageVertices); - activePagePath = - squirclePath(activePageVertices, 4, - MIN(theme.highlightedCornerRadius, - NSHeight(activePageBlock) * 0.5)); + NSMinY(sectionRect); + NSUInteger sec = i / theme.pageSize; + _sectionRects[sec] = sectionRect; + if (sec == _highlightedIndex / theme.pageSize) { + NSPoint activePageVertices[4]; + rectVertices(sectionRect, activePageVertices); + activePagePath = squirclePath(activePageVertices, 4, + MIN(theme.highlightedCornerRadius, + NSHeight(sectionRect) * 0.5)); + } } } CGFloat bottomEdge = NSMaxY(NSIsEmptyRect(trailingRect) ? bodyRect : trailingRect); if (ABS(bottomEdge - gridOriginY) > 2) { if (i > 0) { - ++rowNum; + ++lineNum; } if (ABS(bottomEdge - NSMaxY(_candidateBlock)) > - 2) { // horizontal border except for the last row + 2) { // horizontal border except for the last line [gridPath moveToPoint:NSMakePoint(NSMinX(_candidateBlock) + ceil(theme.separatorWidth * 0.5), @@ -2386,54 +2412,45 @@ - (void)updateLayer { : leadingRect) - cornerRadius * 0.8)]; } - CGFloat tailEdge = - NSMaxX(NSIsEmptyRect(trailingRect) ? bodyRect : trailingRect); - CGFloat tailTabPosition = - ceil((tailEdge - theme.borderInset.width) / tabInterval) * - tabInterval + - theme.borderInset.width; - if (!NSIsEmptyRect(trailingRect)) { - trailingRect.size.width += tailTabPosition - tailEdge; - trailingRect = - [self backingAlignedRect:NSIntersectionRect(trailingRect, - _candidateBlock) - options:NSAlignAllEdgesNearest]; - } else if (NSIsEmptyRect(leadingRect)) { - bodyRect.size.width += tailTabPosition - tailEdge; - bodyRect = [self - backingAlignedRect:NSIntersectionRect(bodyRect, _candidateBlock) - options:NSAlignAllEdgesNearest]; - } - _tabularPositions[i] = - (SquirrelTabularPosition){i, rowNum, headTabColumn}; - } - - NSBezierPath* candidatePath; - // Handles the special case where containing boxes are separated - if (NSIsEmptyRect(bodyRect) && !NSIsEmptyRect(leadingRect) && - !NSIsEmptyRect(trailingRect) && - NSMaxX(trailingRect) < NSMinX(leadingRect)) { - NSPoint leadingVertices[4], trailingVertices[4]; - rectVertices(leadingRect, leadingVertices); - rectVertices(trailingRect, trailingVertices); - candidatePath = squirclePath(leadingVertices, 4, cornerRadius); - [candidatePath - appendBezierPath:squirclePath(trailingVertices, 4, cornerRadius)]; - } else { - numVert = MIN(8, MAX(4, numVert)); - NSPoint multilineVertices[numVert]; - multilineRectVertices(leadingRect, bodyRect, trailingRect, - multilineVertices); - candidatePath = - squirclePath(multilineVertices, numVert, cornerRadius); + _tabularIndices[i] = + (SquirrelTabularIndex){i, lineNum, headTabColumn}; } - _candidatePaths[i] = candidatePath; + _candidateRects[i * 3] = leadingRect; + _candidateRects[i * 3 + 1] = bodyRect; + _candidateRects[i * 3 + 2] = trailingRect; + } + NSInteger numVert = + (NSIsEmptyRect(_candidateRects[_highlightedIndex * 3]) ? 0 : 4) + + (NSIsEmptyRect(_candidateRects[_highlightedIndex * 3 + 1]) ? 0 : 2) + + (NSIsEmptyRect(_candidateRects[_highlightedIndex * 3 + 2]) ? 0 : 4); + // Handles the special case where containing boxes are separated + if (numVert == 8 && NSMaxX(_candidateRects[_highlightedIndex * 3 + 2]) < + NSMinX(_candidateRects[_highlightedIndex * 3])) { + NSPoint leadingVertices[4], trailingVertices[4]; + rectVertices(_candidateRects[_highlightedIndex * 3], leadingVertices); + rectVertices(_candidateRects[_highlightedIndex * 3 + 2], + trailingVertices); + highlightedCandidatePath = + squirclePath(leadingVertices, 4, cornerRadius); + [highlightedCandidatePath + appendBezierPath:squirclePath(trailingVertices, 4, cornerRadius)]; + } else { + numVert = MIN(8, MAX(4, numVert)); + NSPoint multilineVertices[numVert]; + multilineRectVertices(_candidateRects[_highlightedIndex * 3], + _candidateRects[_highlightedIndex * 3 + 1], + _candidateRects[_highlightedIndex * 3 + 2], + multilineVertices); + highlightedCandidatePath = + squirclePath(multilineVertices, numVert, cornerRadius); } } else { // stacked layout + _candidateRects = new NSRect[_numCandidates]; for (NSUInteger i = 0; i < _numCandidates; ++i) { NSRange candidateRange = NSIntersectionRange(_candidateRanges[i], visibleRange); if (candidateRange.length == 0) { + _numCandidates = i; break; } NSRect candidateRect = [self blockRectForRange:candidateRange]; @@ -2446,112 +2463,99 @@ - (void)updateLayer { [self backingAlignedRect:NSIntersectionRect(candidateRect, _candidateBlock) options:NSAlignAllEdgesNearest]; - NSPoint candidateVertices[4]; - rectVertices(candidateRect, candidateVertices); - _candidatePaths[i] = squirclePath(candidateVertices, 4, cornerRadius); + _candidateRects[i] = candidateRect; } + NSPoint candidateVertices[4]; + rectVertices(_candidateRects[_highlightedIndex], candidateVertices); + highlightedCandidatePath = + squirclePath(candidateVertices, 4, cornerRadius); } } // Draw paging Rect _pagingBlock = NSZeroRect; - NSBezierPath* scrollerPath; + _pageUpRect = NSZeroRect; + _pageDownRect = NSZeroRect; + _expanderRect = NSZeroRect; + NSBezierPath *pageUpPath, *pageDownPath; if (theme.tabular && candidateBlockRange.length > 0) { - NSRect expanderRect = + _expanderRect = [self blockRectForRange:NSMakeRange(_textStorage.length - 1, 1)]; - expanderRect.size.width += theme.separatorWidth; - expanderRect.origin.x = NSMaxX(backgroundRect) - NSWidth(expanderRect); - expanderRect.size.height += theme.linespace; - expanderRect.origin.y += + _expanderRect.origin.x += theme.borderInset.width; + _expanderRect.size.width = NSMaxX(backgroundRect) - NSMinX(_expanderRect); + _expanderRect.size.height += theme.linespace; + _expanderRect.origin.y += _alignmentRectInsets.top - ceil(theme.linespace * 0.5); - expanderRect = [self - backingAlignedRect:NSIntersectionRect(expanderRect, backgroundRect) + _expanderRect = [self + backingAlignedRect:NSIntersectionRect(_expanderRect, backgroundRect) options:NSAlignAllEdgesNearest]; - NSPoint expanderVertices[4]; - rectVertices(expanderRect, expanderVertices); - _expanderPath = - squirclePath(expanderVertices, 4, - MIN(theme.highlightedCornerRadius, - theme.paragraphStyle.minimumLineHeight * 0.5)); if (theme.showPaging && self.expanded && - _tabularPositions[_numCandidates - 1].row > 0) { + _tabularIndices[_numCandidates - 1].lineNum > 0) { _pagingBlock = NSMakeRect(NSMaxX(_candidateBlock), NSMinY(_candidateBlock), NSMaxX(backgroundRect) - NSMaxX(_candidateBlock), - NSMinY(expanderRect) - NSMinY(_candidateBlock)); + NSMinY(_expanderRect) - NSMinY(_candidateBlock)); CGFloat width = MIN(theme.paragraphStyle.minimumLineHeight, NSWidth(_pagingBlock)); - CGFloat height = MIN(width, NSHeight(_pagingBlock) * 0.5); - NSRect pageUpRect = NSMakeRect( - NSMinX(_pagingBlock), NSMidY(_pagingBlock) - height, width, height); - NSRect pageDownRect = - NSMakeRect(NSMinX(_pagingBlock), NSMidY(_pagingBlock), width, height); - CGFloat cornerRadius = MIN(theme.highlightedCornerRadius, height * 0.5); - NSPoint pageUpVertices[4], pageDownVertices[4]; - rectVertices(pageUpRect, pageUpVertices); - rectVertices(pageDownRect, pageDownVertices); - _pagingPaths[0] = squirclePath(pageUpVertices, 4, cornerRadius); - _pagingPaths[1] = squirclePath(pageDownVertices, 4, cornerRadius); - - scrollerPath = NSBezierPath.bezierPath; - [scrollerPath - moveToPoint:NSMakePoint(NSMinX(pageUpRect) + ceil(width * 0.2), - NSMaxY(pageUpRect) - ceil(width * 0.2))]; - [scrollerPath - lineToPoint:NSMakePoint(NSMidX(pageUpRect), - NSMaxY(pageUpRect) - ceil(width * 0.4))]; - [scrollerPath - lineToPoint:NSMakePoint(NSMaxX(pageUpRect) - ceil(width * 0.2), - NSMaxY(pageUpRect) - ceil(width * 0.2))]; - [scrollerPath - moveToPoint:NSMakePoint(NSMinX(pageDownRect) + ceil(width * 0.2), - NSMinY(pageDownRect) + ceil(width * 0.2))]; - [scrollerPath - lineToPoint:NSMakePoint(NSMidX(pageDownRect), - NSMinY(pageDownRect) + ceil(width * 0.4))]; - [scrollerPath - lineToPoint:NSMakePoint(NSMaxX(pageDownRect) - ceil(width * 0.2), - NSMinY(pageDownRect) + ceil(width * 0.2))]; + _pageUpRect = NSMakeRect(NSMidX(_pagingBlock) - width * 0.5, + NSMidY(_pagingBlock) - width, width, width); + _pageDownRect = NSMakeRect(NSMidX(_pagingBlock) - width * 0.5, + NSMidY(_pagingBlock), width, width); + pageUpPath = [NSBezierPath + bezierPathWithOvalInRect:NSInsetRect(_pageUpRect, width * 0.2, + width * 0.2)]; + [pageUpPath + moveToPoint:NSMakePoint(NSMinX(_pageUpRect) + ceil(width * 0.325), + NSMaxY(_pageUpRect) - ceil(width * 0.4))]; + [pageUpPath + lineToPoint:NSMakePoint(NSMidX(_pageUpRect), + NSMinY(_pageUpRect) + ceil(width * 0.4))]; + [pageUpPath + lineToPoint:NSMakePoint(NSMaxX(_pageUpRect) - ceil(width * 0.325), + NSMaxY(_pageUpRect) - ceil(width * 0.4))]; + pageDownPath = [NSBezierPath + bezierPathWithOvalInRect:NSInsetRect(_pageDownRect, width * 0.2, + width * 0.2)]; + [pageDownPath + moveToPoint:NSMakePoint(NSMinX(_pageDownRect) + ceil(width * 0.325), + NSMinY(_pageDownRect) + ceil(width * 0.4))]; + [pageDownPath + lineToPoint:NSMakePoint(NSMidX(_pageDownRect), + NSMaxY(_pageDownRect) - ceil(width * 0.4))]; + [pageDownPath + lineToPoint:NSMakePoint(NSMaxX(_pageDownRect) - ceil(width * 0.325), + NSMinY(_pageDownRect) + ceil(width * 0.4))]; } } else if (pagingRange.length > 0) { - NSRect pageUpRect = - [self blockRectForRange:NSMakeRange(pagingRange.location, 1)]; - NSRect pageDownRect = + _pageUpRect = [self blockRectForRange:NSMakeRange(pagingRange.location, 1)]; + _pageDownRect = [self blockRectForRange:NSMakeRange(NSMaxRange(pagingRange) - 1, 1)]; - pageDownRect.origin.x += _alignmentRectInsets.left; - pageDownRect.size.width += ceil(theme.separatorWidth * 0.5); - pageDownRect.origin.y += _alignmentRectInsets.top; - pageUpRect.origin.x += theme.borderInset.width; + _pageDownRect.origin.x += _alignmentRectInsets.left; + _pageDownRect.size.width += ceil(theme.separatorWidth * 0.5); + _pageDownRect.origin.y += _alignmentRectInsets.top; + _pageUpRect.origin.x += theme.borderInset.width; // bypass the bug of getting wrong glyph position when tab is presented - pageUpRect.size.width = NSWidth(pageDownRect); - pageUpRect.origin.y += _alignmentRectInsets.top; + _pageUpRect.size.width = NSWidth(_pageDownRect); + _pageUpRect.origin.y += _alignmentRectInsets.top; if (theme.linear) { - pageUpRect.origin.y -= ceil(theme.linespace * 0.5); - pageUpRect.size.height += theme.linespace; - pageDownRect.origin.y -= ceil(theme.linespace * 0.5); - pageDownRect.size.height += theme.linespace; - pageUpRect = NSIntersectionRect(pageUpRect, _candidateBlock); - pageDownRect = NSIntersectionRect(pageDownRect, _candidateBlock); + _pageUpRect.origin.y -= ceil(theme.linespace * 0.5); + _pageUpRect.size.height += theme.linespace; + _pageDownRect.origin.y -= ceil(theme.linespace * 0.5); + _pageDownRect.size.height += theme.linespace; + _pageUpRect = NSIntersectionRect(_pageUpRect, _candidateBlock); + _pageDownRect = NSIntersectionRect(_pageDownRect, _candidateBlock); } else { _pagingBlock = NSMakeRect(NSMinX(backgroundRect), NSMaxY(_candidateBlock), NSWidth(backgroundRect), NSMaxY(backgroundRect) - NSMaxY(_candidateBlock)); - pageUpRect = NSIntersectionRect(pageUpRect, _pagingBlock); - pageDownRect = NSIntersectionRect(pageDownRect, _pagingBlock); + _pageUpRect = NSIntersectionRect(_pageUpRect, _pagingBlock); + _pageDownRect = NSIntersectionRect(_pageDownRect, _pagingBlock); } - pageUpRect = [self backingAlignedRect:pageUpRect - options:NSAlignAllEdgesNearest]; - pageDownRect = [self backingAlignedRect:pageDownRect - options:NSAlignAllEdgesNearest]; - CGFloat cornerRadius = - MIN(theme.highlightedCornerRadius, - MIN(NSWidth(pageDownRect), NSHeight(pageDownRect)) * 0.5); - NSPoint pageUpVertices[4], pageDownVertices[4]; - rectVertices(pageUpRect, pageUpVertices); - rectVertices(pageDownRect, pageDownVertices); - _pagingPaths[0] = squirclePath(pageUpVertices, 4, cornerRadius); - _pagingPaths[1] = squirclePath(pageDownVertices, 4, cornerRadius); + _pageUpRect = [self backingAlignedRect:_pageUpRect + options:NSAlignAllEdgesNearest]; + _pageDownRect = [self backingAlignedRect:_pageDownRect + options:NSAlignAllEdgesNearest]; } // Set layers @@ -2588,10 +2592,10 @@ - (void)updateLayer { // background color layer CAShapeLayer* backColorLayer = [[CAShapeLayer alloc] init]; if ((!NSIsEmptyRect(_preeditBlock) || !NSIsEmptyRect(_pagingBlock) || - _expanderPath) && + !NSIsEmptyRect(_expanderRect)) && theme.preeditBackColor) { if (candidateBlockPath) { - NSBezierPath* nonCandidatePath = [backgroundPath copy]; + NSBezierPath* nonCandidatePath = backgroundPath.copy; [nonCandidatePath appendBezierPath:candidateBlockPath]; backColorLayer.path = nonCandidatePath.quartzPath; backColorLayer.fillRule = kCAFillRuleEvenOdd; @@ -2640,8 +2644,7 @@ - (void)updateLayer { [ForeLayers addSublayer:highlightedPreeditLayer]; } // highlighted candidate layer - if (_highlightedIndex < _candidatePaths.count && - theme.highlightedCandidateBackColor) { + if (highlightedCandidatePath && theme.highlightedCandidateBackColor) { if (activePagePath) { CAShapeLayer* activePageLayer = [[CAShapeLayer alloc] init]; activePageLayer.path = activePagePath.quartzPath; @@ -2655,8 +2658,7 @@ - (void)updateLayer { [BackLayers addSublayer:activePageLayer]; } CAShapeLayer* highlightedCandidateLayer = [[CAShapeLayer alloc] init]; - highlightedCandidateLayer.path = - _candidatePaths[_highlightedIndex].quartzPath; + highlightedCandidateLayer.path = highlightedCandidatePath.quartzPath; highlightedCandidateLayer.fillColor = theme.highlightedCandidateBackColor.CGColor; [ForeLayers addSublayer:highlightedCandidateLayer]; @@ -2674,21 +2676,37 @@ - (void)updateLayer { gridLayer.path = gridPath.quartzPath; gridLayer.lineWidth = 1.0; gridLayer.strokeColor = [theme.commentAttrs[NSForegroundColorAttributeName] - blendedColorWithFraction:0.5 + blendedColorWithFraction:0.8 ofColor:theme.backColor] .CGColor; [ForeLayers addSublayer:gridLayer]; } - // paging scroller in expanded tabular - if (scrollerPath) { - CAShapeLayer* scrollerLayer = [[CAShapeLayer alloc] init]; - scrollerLayer.path = scrollerPath.quartzPath; - scrollerLayer.fillColor = NSColor.clearColor.CGColor; - scrollerLayer.lineWidth = + // paging buttons in expanded tabular layout + if (pageUpPath && pageDownPath) { + CAShapeLayer* pageUpLayer = [[CAShapeLayer alloc] init]; + pageUpLayer.path = pageUpPath.quartzPath; + pageUpLayer.fillColor = NSColor.clearColor.CGColor; + pageUpLayer.lineWidth = + ceil([theme.pagingAttrs[NSFontAttributeName] pointSize] * 0.05); + NSDictionary* pageUpAttrs = + _functionButton == kPageUpKey || _functionButton == kHomeKey + ? theme.preeditHighlightedAttrs + : theme.preeditAttrs; + pageUpLayer.strokeColor = + [pageUpAttrs[NSForegroundColorAttributeName] CGColor]; + [ForeLayers addSublayer:pageUpLayer]; + CAShapeLayer* pageDownLayer = [[CAShapeLayer alloc] init]; + pageDownLayer.path = pageDownPath.quartzPath; + pageDownLayer.fillColor = NSColor.clearColor.CGColor; + pageDownLayer.lineWidth = ceil([theme.pagingAttrs[NSFontAttributeName] pointSize] * 0.05); - scrollerLayer.strokeColor = - [theme.pagingAttrs[NSForegroundColorAttributeName] CGColor]; - [ForeLayers addSublayer:scrollerLayer]; + NSDictionary* pageDownAttrs = + _functionButton == kPageDownKey || _functionButton == kEndKey + ? theme.preeditHighlightedAttrs + : theme.preeditAttrs; + pageDownLayer.strokeColor = + [pageDownAttrs[NSForegroundColorAttributeName] CGColor]; + [ForeLayers addSublayer:pageDownLayer]; } // logo at the beginning for status message if (NSIsEmptyRect(_preeditBlock) && NSIsEmptyRect(_candidateBlock)) { @@ -2715,25 +2733,26 @@ - (void)updateLayer { - (SquirrelIndex)getIndexFromMouseSpot:(NSPoint)spot { NSPoint point = [self convertPoint:spot fromView:nil]; - if (NSPointInRect(point, self.bounds)) { - NSBezierPath.defaultFlatness = self.currentTheme.highlightedCornerRadius; - if (NSPointInRect(point, _preeditBlock)) { - return [_deleteBackPath.bezierPathByFlatteningPath containsPoint:point] - ? kBackSpaceKey - : kCodeInputArea; + if (NSMouseInRect(point, self.bounds, YES)) { + if (NSMouseInRect(point, _preeditBlock, YES)) { + return NSMouseInRect(point, _deleteBackRect, YES) ? kBackSpaceKey + : kCodeInputArea; } - if ([_expanderPath.bezierPathByFlatteningPath containsPoint:point]) { + if (NSMouseInRect(point, _expanderRect, YES)) { return kExpandButton; - } else if (_pagingPaths.count > 0) { - if ([_pagingPaths[0].bezierPathByFlatteningPath containsPoint:point]) { - return kPageUpKey; - } - if ([_pagingPaths[1].bezierPathByFlatteningPath containsPoint:point]) { - return kPageDownKey; - } } - for (NSUInteger i = 0; i < _candidatePaths.count; ++i) { - if ([_candidatePaths[i].bezierPathByFlatteningPath containsPoint:point]) { + if (NSMouseInRect(point, _pageUpRect, YES)) { + return kPageUpKey; + } + if (NSMouseInRect(point, _pageDownRect, YES)) { + return kPageDownKey; + } + for (NSUInteger i = 0; i < _numCandidates; ++i) { + if (self.currentTheme.linear + ? (NSMouseInRect(point, _candidateRects[i * 3], YES) || + NSMouseInRect(point, _candidateRects[i * 3 + 1], YES) || + NSMouseInRect(point, _candidateRects[i * 3 + 2], YES)) + : NSMouseInRect(point, _candidateRects[i], YES)) { return i; } } @@ -2880,15 +2899,14 @@ @implementation SquirrelPanel { BOOL _initPosition; NSTimer* _statusTimer; - NSString* _preedit; - NSRange _selRange; NSUInteger _caretPos; - NSArray* _candidates; - NSArray* _comments; + BOOL _caretAtHome; + NSRange _indexRange; NSUInteger _highlightedIndex; NSUInteger _pageNum; BOOL _finalPage; NSUInteger _functionButton; + SquirrelIndex _cursorIndex; } - (BOOL)linear { @@ -2911,9 +2929,9 @@ - (BOOL)inlineCandidate { return _view.currentTheme.inlineCandidate; } -- (BOOL)topRow { - return _view.tabularPositions - ? _view.tabularPositions[_highlightedIndex].row == 0 +- (BOOL)firstLine { + return _view.tabularIndices + ? _view.tabularIndices[_highlightedIndex].lineNum == 0 : YES; } @@ -2924,14 +2942,15 @@ - (BOOL)expanded { - (void)setExpanded:(BOOL)expanded { if (_view.currentTheme.tabular && !_locked && _view.expanded != expanded) { _view.expanded = expanded; - _activePage = 0; + _sectionNum = 0; } } -- (void)setActivePage:(NSUInteger)activePage { +- (void)setSectionNum:(NSUInteger)sectionNum { if (_view.currentTheme.tabular && _view.expanded && - _activePage != activePage) { - _activePage = MAX(MIN(activePage, 4UL), 0UL); + _sectionNum != sectionNum) { + _sectionNum = + MAX(MIN(sectionNum, _view.currentTheme.vertical ? 2UL : 4UL), 0UL); } } @@ -2961,7 +2980,7 @@ - (void)getLock { } } [userConfig close]; - _activePage = 0; + _sectionNum = 0; } } @@ -2997,6 +3016,8 @@ - (instancetype)init { self.contentView = contentView; [self updateDisplayParameters]; + _candidates = [[NSMutableArray alloc] init]; + _comments = [[NSMutableArray alloc] init]; _toolTip = [[SquirrelToolTip alloc] init]; } return self; @@ -3009,42 +3030,45 @@ - (void)windowDidChangeBackingProperties:(NSNotification*)notification { } - (NSUInteger)candidateIndexOnDirection:(SquirrelIndex)arrowKey { - if (!self.tabular || _candidates.count == 0 || + if (!self.tabular || _indexRange.length == 0 || _highlightedIndex == NSNotFound) { return NSNotFound; } NSUInteger pageSize = _view.currentTheme.pageSize; - NSUInteger currentTabColumn = - _view.tabularPositions[_highlightedIndex].tabColumn; - NSUInteger currentRow = _view.tabularPositions[_highlightedIndex].row; + NSUInteger currentTab = _view.tabularIndices[_highlightedIndex].tabNum; + NSUInteger currentLine = _view.tabularIndices[_highlightedIndex].lineNum; + NSUInteger finalLine = _view.tabularIndices[_indexRange.length - 1].lineNum; if (arrowKey == (self.vertical ? kLeftKey : kDownKey)) { - if (_highlightedIndex == _candidates.count - 1 && _finalPage) { + if (_highlightedIndex == _indexRange.length - 1 && _finalPage) { return NSNotFound; } + if (currentLine == finalLine && !_finalPage) { + return _highlightedIndex + pageSize + _indexRange.location; + } NSUInteger newIndex = _highlightedIndex + 1; - while (newIndex < _candidates.count && - (_view.tabularPositions[newIndex].row == currentRow || - (_view.tabularPositions[newIndex].row == currentRow + 1 && - _view.tabularPositions[newIndex].tabColumn <= currentTabColumn))) { + while (newIndex < _indexRange.length && + (_view.tabularIndices[newIndex].lineNum == currentLine || + (_view.tabularIndices[newIndex].lineNum == currentLine + 1 && + _view.tabularIndices[newIndex].tabNum <= currentTab))) { ++newIndex; } - if (newIndex != _candidates.count || _finalPage) { + if (newIndex != _indexRange.length || _finalPage) { --newIndex; } - return newIndex + pageSize * (_pageNum - _activePage); + return newIndex + _indexRange.location; } else if (arrowKey == (self.vertical ? kRightKey : kUpKey)) { - if (currentRow == 0) { + if (currentLine == 0) { return _pageNum == 0 ? NSNotFound - : pageSize * (_pageNum - _activePage) - 1; + : pageSize * (_pageNum - _sectionNum) - 1; } NSInteger newIndex = (NSInteger)_highlightedIndex - 1; while (newIndex > 0 && - (_view.tabularPositions[newIndex].row == currentRow || - (_view.tabularPositions[newIndex].row == currentRow - 1 && - _view.tabularPositions[newIndex].tabColumn > currentTabColumn))) { + (_view.tabularIndices[newIndex].lineNum == currentLine || + (_view.tabularIndices[newIndex].lineNum == currentLine - 1 && + _view.tabularIndices[newIndex].tabNum > currentTab))) { --newIndex; } - return (NSUInteger)newIndex + pageSize * (_pageNum - _activePage); + return (NSUInteger)newIndex + _indexRange.location; } return NSNotFound; } @@ -3052,10 +3076,9 @@ - (NSUInteger)candidateIndexOnDirection:(SquirrelIndex)arrowKey { // handle mouse interaction events - (void)sendEvent:(NSEvent*)event { SquirrelTheme* theme = _view.currentTheme; - NSUInteger cursorIndex; switch (event.type) { case NSEventTypeLeftMouseDown: - if (event.clickCount == 1 && _functionButton == kCodeInputArea) { + if (event.clickCount == 1 && _cursorIndex == kCodeInputArea) { NSPoint spot = [_view.textView convertPoint:self.mouseLocationOutsideOfEventStream fromView:nil]; @@ -3079,34 +3102,38 @@ - (void)sendEvent:(NSEvent*)event { } break; case NSEventTypeLeftMouseUp: - cursorIndex = - [_view getIndexFromMouseSpot:self.mouseLocationOutsideOfEventStream]; - if (event.clickCount == 1 && cursorIndex != NSNotFound) { - if (cursorIndex == _highlightedIndex) { - cursorIndex += (_pageNum - _activePage) * theme.pageSize; - [self.inputController performAction:kSELECT onIndex:cursorIndex]; - } else if (cursorIndex == _functionButton) { - if (cursorIndex == kExpandButton) { + if (event.clickCount == 1 && _cursorIndex != NSNotFound) { + if (_cursorIndex == _highlightedIndex) { + [self.inputController + performAction:kSELECT + onIndex:_cursorIndex + _indexRange.location]; + } else if (_cursorIndex == _functionButton) { + if (_cursorIndex == kExpandButton) { if (_locked) { [self setLock:NO]; + [_view.textStorage + replaceCharactersInRange:NSMakeRange( + _view.textStorage.length - 1, 1) + withAttributedString:_view.expanded ? theme.symbolCompress + : theme.symbolExpand]; + _view.textView.needsDisplayInRect = _view.expanderRect; } else { self.expanded = !_view.expanded; - self.activePage = 0; + self.sectionNum = 0; } } - [self.inputController performAction:kPROCESS onIndex:cursorIndex]; + [self.inputController performAction:kPROCESS onIndex:_cursorIndex]; } } break; case NSEventTypeRightMouseUp: - cursorIndex = - [_view getIndexFromMouseSpot:self.mouseLocationOutsideOfEventStream]; - if (event.clickCount == 1 && cursorIndex != NSNotFound) { - if (cursorIndex == _highlightedIndex) { - cursorIndex += (_pageNum - _activePage) * theme.pageSize; - [self.inputController performAction:kDELETE onIndex:cursorIndex]; - } else if (cursorIndex == _functionButton) { - switch (cursorIndex) { + if (event.clickCount == 1 && _cursorIndex != NSNotFound) { + if (_cursorIndex == _highlightedIndex) { + [self.inputController + performAction:kDELETE + onIndex:_cursorIndex + _indexRange.location]; + } else if (_cursorIndex == _functionButton) { + switch (_functionButton) { case kPageUpKey: [self.inputController performAction:kPROCESS onIndex:kHomeKey]; break; @@ -3115,6 +3142,19 @@ - (void)sendEvent:(NSEvent*)event { break; case kExpandButton: [self setLock:!_locked]; + [_view.textStorage + replaceCharactersInRange:NSMakeRange( + _view.textStorage.length - 1, 1) + withAttributedString:_locked ? theme.symbolLock + : _view.expanded + ? theme.symbolCompress + : theme.symbolExpand]; + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.preeditHighlightedAttrs + [NSForegroundColorAttributeName] + range:NSMakeRange(_view.textStorage.length - 1, 1)]; + _view.textView.needsDisplayInRect = _view.expanderRect; [self.inputController performAction:kPROCESS onIndex:kLockButton]; break; case kBackSpaceKey: @@ -3133,135 +3173,30 @@ - (void)sendEvent:(NSEvent*)event { BOOL noDelay = (event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagOption; - cursorIndex = + _cursorIndex = [_view getIndexFromMouseSpot:self.mouseLocationOutsideOfEventStream]; - if (cursorIndex != _highlightedIndex && cursorIndex != _functionButton) { + if (_cursorIndex != _highlightedIndex && + _cursorIndex != _functionButton) { [_toolTip hide]; } else if (noDelay) { [_toolTip.displayTimer fire]; } - if (cursorIndex >= 0 && cursorIndex < _candidates.count && - _highlightedIndex != cursorIndex) { - _highlightedIndex = cursorIndex; - cursorIndex += (_pageNum - _activePage) * theme.pageSize; - self.activePage = _highlightedIndex / theme.pageSize; - _pageNum = cursorIndex / theme.pageSize; - [_toolTip showWithToolTip:NSLocalizedString(@"candidate", nil) - withDelay:!noDelay]; - [self.inputController performAction:kHIGHLIGHT onIndex:cursorIndex]; - [self updateContents]; - [self display]; - } else if ((cursorIndex == kPageUpKey || cursorIndex == kPageDownKey || - cursorIndex == kExpandButton || - cursorIndex == kBackSpaceKey) && - _functionButton != cursorIndex) { - _functionButton = cursorIndex; - switch (_functionButton) { - case kPageUpKey: - if (theme.tabular) { - [_view.textStorage - addAttributes:theme.pagingAttrs - range:NSMakeRange(_view.textStorage.length - 1, 1)]; - } else { - [_view.textStorage - addAttributes:theme.pagingHighlightedAttrs - range:NSMakeRange(_view.pagingRange.location, 1)]; - [_view.textStorage - addAttributes:theme.pagingAttrs - range:NSMakeRange(NSMaxRange(_view.pagingRange) - 1, - 1)]; - } - if (_view.preeditRange.length > 0) { - [_view.textStorage - addAttributes:theme.preeditAttrs - range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, - 1)]; - } - cursorIndex = _pageNum == 0 ? kHomeKey : kPageUpKey; - [_toolTip - showWithToolTip:NSLocalizedString( - _pageNum == 0 ? @"home" : @"page_up", nil) - withDelay:!noDelay]; - break; - case kPageDownKey: - if (theme.tabular) { - [_view.textStorage - addAttributes:theme.pagingAttrs - range:NSMakeRange(_view.textStorage.length - 1, 1)]; - } else { - [_view.textStorage - addAttributes:theme.pagingAttrs - range:NSMakeRange(_view.pagingRange.location, 1)]; - [_view.textStorage - addAttributes:theme.pagingHighlightedAttrs - range:NSMakeRange(NSMaxRange(_view.pagingRange) - 1, - 1)]; - } - if (_view.preeditRange.length > 0) { - [_view.textStorage - addAttributes:theme.preeditAttrs - range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, - 1)]; - } - cursorIndex = _finalPage ? kEndKey : kPageDownKey; - [_toolTip - showWithToolTip:NSLocalizedString( - _finalPage ? @"end" : @"page_down", nil) - withDelay:!noDelay]; - break; - case kExpandButton: - [_view.textStorage - addAttributes:theme.pagingHighlightedAttrs - range:NSMakeRange(_view.textStorage.length - 1, 1)]; - if (_view.preeditRange.length > 0) { - [_view.textStorage - addAttributes:theme.preeditAttrs - range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, - 1)]; - } - cursorIndex = _locked ? kLockButton - : _view.expanded ? kCompressButton - : kExpandButton; - [_toolTip - showWithToolTip:NSLocalizedString(_locked ? @"unlock" - : _view.expanded ? @"compress" - : @"expand", - nil) - withDelay:!noDelay]; - break; - case kBackSpaceKey: - [_view.textStorage - addAttributes:theme.preeditHighlightedAttrs - range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, - 1)]; - if (theme.tabular) { - [_view.textStorage - addAttributes:theme.pagingAttrs - range:NSMakeRange(_view.textStorage.length - 1, 1)]; - } else if (_view.pagingRange.length > 0) { - [_view.textStorage - addAttributes:theme.pagingAttrs - range:NSMakeRange(_view.pagingRange.location, 1)]; - [_view.textStorage - addAttributes:theme.pagingAttrs - range:NSMakeRange(NSMaxRange(_view.pagingRange) - 1, - 1)]; - } - BOOL caretAtHome = - _caretPos == NSNotFound || - (_caretPos == _selRange.location && _selRange.location == 1); - cursorIndex = caretAtHome ? kEscapeKey : kBackSpaceKey; - [_toolTip - showWithToolTip:NSLocalizedString( - caretAtHome ? @"escape" : @"delete", nil) - withDelay:!noDelay]; - break; + if (_cursorIndex >= 0 && _cursorIndex < _indexRange.length && + _highlightedIndex != _cursorIndex) { + [self highlightFunctionButton:kVoidSymbol delayToolTip:!noDelay]; + if (noDelay) { + [_toolTip showWithToolTip:NSLocalizedString(@"candidate", nil) + withDelay:!noDelay]; } - [_view highlightFunctionButton:cursorIndex]; - [self display]; - } else if (cursorIndex == kCodeInputArea && - _functionButton != cursorIndex) { - _functionButton = cursorIndex; + self.sectionNum = _cursorIndex / theme.pageSize; + [self.inputController + performAction:kHIGHLIGHT + onIndex:_cursorIndex + _indexRange.location]; + } else if ((_cursorIndex == kPageUpKey || _cursorIndex == kPageDownKey || + _cursorIndex == kExpandButton || + _cursorIndex == kBackSpaceKey) && + _functionButton != _cursorIndex) { + [self highlightFunctionButton:_cursorIndex delayToolTip:!noDelay]; } } break; case NSEventTypeMouseExited: @@ -3320,6 +3255,179 @@ - (void)sendEvent:(NSEvent*)event { } } +- (void)highlightCandidate:(NSUInteger)highlightedIndex { + SquirrelTheme* theme = _view.currentTheme; + NSUInteger prevHighlightedIndex = _highlightedIndex; + NSUInteger prevSectionNum = prevHighlightedIndex / theme.pageSize; + _highlightedIndex = highlightedIndex; + self.sectionNum = highlightedIndex / theme.pageSize; + // apply new foreground colors + for (NSUInteger i = 0; i < theme.pageSize; ++i) { + NSUInteger prevIndex = i + prevSectionNum * theme.pageSize; + if ((_sectionNum != prevSectionNum || prevIndex == prevHighlightedIndex) && + prevIndex < _indexRange.length) { + NSRange prevRange = _view.candidateRanges[prevIndex]; + NSRange prevTextRange = + [[_view.textStorage.mutableString substringWithRange:prevRange] + rangeOfString:_candidates[prevIndex + _indexRange.location]]; + NSColor* labelColor = [theme.labelAttrs[NSForegroundColorAttributeName] + blendedColorWithFraction:prevIndex == prevHighlightedIndex && + _sectionNum == prevSectionNum + ? 0.0 + : 0.5 + ofColor:NSColor.clearColor]; + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:labelColor + range:NSMakeRange(prevRange.location, prevTextRange.location)]; + if (prevIndex == prevHighlightedIndex) { + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.attrs[NSForegroundColorAttributeName] + range:NSMakeRange( + prevRange.location + prevTextRange.location, + prevTextRange.length)]; + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.commentAttrs[NSForegroundColorAttributeName] + range:NSMakeRange( + prevRange.location + NSMaxRange(prevTextRange), + prevRange.length - NSMaxRange(prevTextRange))]; + } + } + NSUInteger newIndex = i + _sectionNum * theme.pageSize; + if ((_sectionNum != prevSectionNum || newIndex == _highlightedIndex) && + newIndex < _indexRange.length) { + NSRange newRange = _view.candidateRanges[newIndex]; + NSRange newTextRange = + [[_view.textStorage.mutableString substringWithRange:newRange] + rangeOfString:_candidates[newIndex + _indexRange.location]]; + NSColor* labelColor = + (newIndex == _highlightedIndex + ? theme.labelHighlightedAttrs + : theme.labelAttrs)[NSForegroundColorAttributeName]; + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:labelColor + range:NSMakeRange(newRange.location, newTextRange.location)]; + NSColor* textColor = (newIndex == _highlightedIndex + ? theme.highlightedAttrs + : theme.attrs)[NSForegroundColorAttributeName]; + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:textColor + range:NSMakeRange(newRange.location + newTextRange.location, + newTextRange.length)]; + NSColor* commentColor = + (newIndex == _highlightedIndex + ? theme.commentHighlightedAttrs + : theme.commentAttrs)[NSForegroundColorAttributeName]; + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:commentColor + range:NSMakeRange(newRange.location + NSMaxRange(newTextRange), + newRange.length - NSMaxRange(newTextRange))]; + } + } + [_view highlightCandidate:_highlightedIndex]; + [self displayIfNeeded]; +} + +- (void)highlightFunctionButton:(SquirrelIndex)functionButton + delayToolTip:(BOOL)delay { + if (_functionButton == functionButton) { + return; + } + SquirrelTheme* theme = _view.currentTheme; + switch (_functionButton) { + case kPageUpKey: + if (!theme.tabular) { + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.pagingAttrs[NSForegroundColorAttributeName] + range:NSMakeRange(_view.pagingRange.location, 1)]; + } + break; + case kPageDownKey: + if (!theme.tabular) { + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.pagingAttrs[NSForegroundColorAttributeName] + range:NSMakeRange(NSMaxRange(_view.pagingRange) - 1, 1)]; + } + break; + case kExpandButton: + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.preeditAttrs[NSForegroundColorAttributeName] + range:NSMakeRange(_view.textStorage.length - 1, 1)]; + break; + case kBackSpaceKey: + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.preeditAttrs[NSForegroundColorAttributeName] + range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, 1)]; + break; + } + _functionButton = functionButton; + switch (_functionButton) { + case kPageUpKey: + if (!theme.tabular) { + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.pagingHighlightedAttrs + [NSForegroundColorAttributeName] + range:NSMakeRange(_view.pagingRange.location, 1)]; + } + functionButton = _pageNum == 0 ? kHomeKey : kPageUpKey; + [_toolTip showWithToolTip:NSLocalizedString( + _pageNum == 0 ? @"home" : @"page_up", nil) + withDelay:delay]; + break; + case kPageDownKey: + if (!theme.tabular) { + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.pagingHighlightedAttrs + [NSForegroundColorAttributeName] + range:NSMakeRange(NSMaxRange(_view.pagingRange) - 1, 1)]; + } + functionButton = _finalPage ? kEndKey : kPageDownKey; + [_toolTip showWithToolTip:NSLocalizedString( + _finalPage ? @"end" : @"page_down", nil) + withDelay:delay]; + break; + case kExpandButton: + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.preeditHighlightedAttrs + [NSForegroundColorAttributeName] + range:NSMakeRange(_view.textStorage.length - 1, 1)]; + functionButton = _locked ? kLockButton + : _view.expanded ? kCompressButton + : kExpandButton; + [_toolTip showWithToolTip:NSLocalizedString(_locked ? @"unlock" + : _view.expanded ? @"compress" + : @"expand", + nil) + withDelay:delay]; + break; + case kBackSpaceKey: + [_view.textStorage + addAttribute:NSForegroundColorAttributeName + value:theme.preeditHighlightedAttrs + [NSForegroundColorAttributeName] + range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, 1)]; + functionButton = _caretAtHome ? kEscapeKey : kBackSpaceKey; + [_toolTip showWithToolTip:NSLocalizedString( + _caretAtHome ? @"escape" : @"delete", nil) + withDelay:delay]; + break; + } + [_view highlightFunctionButton:functionButton]; + [self displayIfNeeded]; +} + - (void)updateScreen { for (NSScreen* screen in NSScreen.screens) { if (NSPointInRect(_IbeamRect.origin, screen.frame)) { @@ -3343,6 +3451,11 @@ - (void)updateDisplayParameters { NSRect screenRect = _screen.visibleFrame; SquirrelTheme* theme = _view.currentTheme; _view.textView.layoutOrientation = (NSTextLayoutOrientation)theme.vertical; + // rotate the view, the core in vertical mode! + self.contentView.boundsRotation = theme.vertical ? -90.0 : 0.0; + _view.textView.boundsRotation = 0.0; + _view.textView.boundsOrigin = NSZeroPoint; + CGFloat textWidthRatio = MIN(0.8, 1.0 / (theme.vertical ? 4 : 3) + [theme.attrs[NSFontAttributeName] pointSize] / 144.0); @@ -3355,10 +3468,8 @@ - (void)updateDisplayParameters { } if (theme.tabular) { CGFloat tabInterval = theme.separatorWidth * 2; - _textWidthLimit = - floor((_textWidthLimit - theme.expanderWidth) / tabInterval) * - tabInterval + - theme.expanderWidth; + _textWidthLimit = floor(_textWidthLimit / tabInterval) * tabInterval + + theme.expanderWidth; } CGFloat textHeightLimit = (theme.vertical ? NSWidth(screenRect) : NSHeight(screenRect)) * 0.8 - @@ -3612,26 +3723,17 @@ - (void)show { options:NSAlignAllEdgesNearest]; [self setFrame:windowRect display:YES]; - // rotate the view, the core in vertical mode! - self.contentView.boundsRotation = theme.vertical ? -90.0 : 0.0; self.contentView.boundsOrigin = theme.vertical ? NSMakePoint(0.0, NSWidth(windowRect)) : NSZeroPoint; - NSRect viewRect = self.contentView.bounds; - _view.boundsOrigin = NSZeroPoint; _view.frame = viewRect; - _view.needsDisplay = YES; - - _view.textView.boundsRotation = 0.0; - _view.textView.boundsOrigin = NSZeroPoint; - _view.textView.frame = - NSOffsetRect(viewRect, insets.left - _view.textView.textContainerOrigin.x, - insets.top - _view.textView.textContainerOrigin.y); - _view.textView.needsDisplay = YES; - + _view.textView.frame = NSMakeRect( + NSMinX(viewRect) + insets.left - _view.textView.textContainerOrigin.x, + NSMinY(viewRect) + insets.bottom - _view.textView.textContainerOrigin.y, + NSWidth(viewRect) - insets.left - insets.right, + NSHeight(viewRect) - insets.top - insets.bottom); if (@available(macOS 10.14, *)) { if (theme.translucency > 0) { - _back.boundsOrigin = NSZeroPoint; _back.frame = viewRect; _back.hidden = NO; } else { @@ -3655,30 +3757,33 @@ - (void)hide { _maxSize = NSZeroSize; _initPosition = YES; self.expanded = NO; - self.activePage = 0; + self.sectionNum = 0; } - (BOOL)shouldBreakLineInsideRange:(NSRange)range { + SquirrelTheme* theme = _view.currentTheme; [_view.textStorage fixFontAttributeInRange:range]; - CGFloat maxTextWidth = _textWidthLimit; - if (self.tabular) { - maxTextWidth -= - _view.currentTheme.expanderWidth + _view.currentTheme.separatorWidth; - } + CGFloat maxTextWidth = + _textWidthLimit - (theme.tabular ? theme.expanderWidth : 0.0); NSUInteger __block lineCount = 0; if (@available(macOS 12.0, *)) { NSTextRange* textRange = [_view getTextRangeFromCharRange:range]; [_view.textView.textLayoutManager enumerateTextSegmentsInRange:textRange - type:NSTextLayoutManagerSegmentTypeHighlight + type:NSTextLayoutManagerSegmentTypeStandard options: NSTextLayoutManagerSegmentOptionsRangeNotRequired usingBlock:^BOOL( NSTextRange* _Nullable segRange, CGRect segFrame, CGFloat baseline, NSTextContainer* _Nonnull textContainer) { - lineCount += - NSMaxX(segFrame) > maxTextWidth ? 2 : 1; + CGFloat endEdge = ceil(NSMaxX(segFrame)); + if (theme.tabular) { + endEdge = ceil((endEdge + theme.separatorWidth) / + (theme.separatorWidth * 2)) * + theme.separatorWidth * 2; + } + lineCount += endEdge > maxTextWidth - 0.1 ? 2 : 1; return lineCount <= 1; }]; } else { @@ -3691,8 +3796,15 @@ - (BOOL)shouldBreakLineInsideRange:(NSRange)range { NSRect rect, NSRect usedRect, NSTextContainer* _Nonnull textContainer, NSRange lineRange, BOOL* _Nonnull stop) { + CGFloat endEdge = ceil(NSMaxX(usedRect)); + if (theme.tabular) { + endEdge = + ceil((endEdge + theme.separatorWidth) / + (theme.separatorWidth * 2)) * + theme.separatorWidth * 2; + } lineCount += - NSMaxX(usedRect) > maxTextWidth ? 2 : 1; + endEdge > maxTextWidth - 0.1 ? 2 : 1; }]; } return lineCount > 1; @@ -3700,8 +3812,9 @@ - (BOOL)shouldBreakLineInsideRange:(NSRange)range { - (BOOL)shouldUseTabInRange:(NSRange)range maxLineLength:(CGFloat*)maxLineLength { + SquirrelTheme* theme = _view.currentTheme; [_view.textStorage fixFontAttributeInRange:range]; - if (_view.currentTheme.lineLength > 0) { + if (theme.lineLength > 0) { *maxLineLength = MAX(_textWidthLimit, _maxSize.width); return YES; } @@ -3710,33 +3823,43 @@ - (BOOL)shouldUseTabInRange:(NSRange)range CGFloat __block rangeEndEdge; [_view.textView.textLayoutManager enumerateTextSegmentsInRange:textRange - type:NSTextLayoutManagerSegmentTypeHighlight + type:NSTextLayoutManagerSegmentTypeStandard options: NSTextLayoutManagerSegmentOptionsRangeNotRequired - usingBlock:^( + usingBlock:^BOOL( NSTextRange* _Nullable segRange, CGRect segFrame, CGFloat baseline, NSTextContainer* _Nonnull textContainer) { - rangeEndEdge = NSMaxX(segFrame); + rangeEndEdge = ceil(NSMaxX(segFrame)); return YES; }]; - [_view.textView.textLayoutManager - ensureLayoutForRange:_view.textView.textContentStorage.documentRange]; NSRect container = _view.textView.textLayoutManager.usageBoundsForTextContainer; - *maxLineLength = - MAX(*maxLineLength, MAX(NSMaxX(container), _maxSize.width)); + CGFloat containerWidth = ceil(NSMaxX(container)); + if (theme.tabular) { + containerWidth = ceil((containerWidth - theme.expanderWidth) / + (theme.separatorWidth * 2)) * + theme.separatorWidth * 2 + + theme.expanderWidth; + } + *maxLineLength = MAX(*maxLineLength, MAX(containerWidth, _maxSize.width)); return *maxLineLength > rangeEndEdge; } else { NSUInteger glyphIndex = [_view.textView.layoutManager glyphIndexForCharacterAtIndex:range.location]; - CGFloat rangeEndEdge = NSMaxX([_view.textView.layoutManager + CGFloat rangeEndEdge = ceil(NSMaxX([_view.textView.layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphIndex - effectiveRange:NULL]); + effectiveRange:NULL])); NSRect container = [_view.textView.layoutManager usedRectForTextContainer:_view.textView.textContainer]; - *maxLineLength = - MAX(*maxLineLength, MAX(NSMaxX(container), _maxSize.width)); + CGFloat containerWidth = ceil(NSMaxX(container)); + if (theme.tabular) { + containerWidth = ceil((containerWidth - theme.expanderWidth) / + (theme.separatorWidth * 2)) * + theme.separatorWidth * 2 + + theme.expanderWidth; + } + *maxLineLength = MAX(*maxLineLength, MAX(containerWidth, _maxSize.width)); return *maxLineLength > rangeEndEdge; } } @@ -3751,22 +3874,15 @@ - (NSMutableAttributedString*)getPageNumString:(NSUInteger)pageNum { NSAttributedString* pageNumString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%lu", pageNum + 1] attributes:theme.pagingAttrs]; - NSMutableDictionary* pageNumAttrs = [theme.pagingAttrs mutableCopy]; - NSFont* font = pageNumAttrs[NSFontAttributeName]; - CGFloat lineHeight = - (theme.linear ? theme.paragraphStyle : theme.pagingParagraphStyle) - .minimumLineHeight; - CGFloat width = MAX(lineHeight, pageNumString.size.width); + NSFont* font = theme.pagingAttrs[NSFontAttributeName]; + CGFloat width = MAX(font.ascender, pageNumString.size.width); NSImage* pageNumImage = [NSImage - imageWithSize:NSMakeSize(lineHeight, width) + imageWithSize:NSMakeSize(font.ascender, width) flipped:YES drawingHandler:^BOOL(NSRect dstRect) { CGContextRef context = NSGraphicsContext.currentContext.CGContext; CGContextSaveGState(context); - CGContextTranslateCTM( - context, - lineHeight * 0.5 + font.ascender * 0.5 + font.descender * 0.5, - width); + CGContextTranslateCTM(context, font.ascender, width); CGContextRotateCTM(context, -M_PI_2); [pageNumString drawAtPoint:NSMakePoint( @@ -3776,16 +3892,14 @@ - (NSMutableAttributedString*)getPageNumString:(NSUInteger)pageNum { return YES; }]; pageNumImage.resizingMode = NSImageResizingModeStretch; - pageNumImage.size = NSMakeSize(lineHeight, lineHeight); + pageNumImage.size = NSMakeSize(font.ascender, font.ascender); NSTextAttachment* pageNumAttm = [[NSTextAttachment alloc] init]; pageNumAttm.image = pageNumImage; - pageNumAttm.bounds = NSMakeRect( - 0, font.ascender * 0.5 + font.descender * 0.5 - lineHeight * 0.5, - lineHeight, lineHeight); + pageNumAttm.bounds = NSMakeRect(0, 0, font.ascender, font.ascender); NSMutableAttributedString* attmString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" %C ", (unichar)NSAttachmentCharacter] - attributes:pageNumAttrs]; + attributes:theme.pagingAttrs]; [attmString addAttribute:NSAttachmentAttributeName value:pageNumAttm range:NSMakeRange(1, 1)]; @@ -3796,25 +3910,23 @@ - (NSMutableAttributedString*)getPageNumString:(NSUInteger)pageNum { - (void)showPreedit:(NSString*)preedit selRange:(NSRange)selRange caretPos:(NSUInteger)caretPos - candidates:(NSArray*)candidates - comments:(NSArray*)comments + candidateIndices:(NSRange)indexRange highlightedIndex:(NSUInteger)highlightedIndex pageNum:(NSUInteger)pageNum - finalPage:(BOOL)finalPage { + finalPage:(BOOL)finalPage + didCompose:(BOOL)didCompose { if (!NSIntersectsRect(_IbeamRect, _screen.frame)) { [self updateScreen]; [self updateDisplayParameters]; } - _preedit = preedit; - _selRange = selRange; + BOOL updateCandidates = didCompose || !NSEqualRanges(_indexRange, indexRange); + _caretAtHome = caretPos == NSNotFound || + (caretPos == selRange.location && selRange.location == 1); _caretPos = caretPos; - _candidates = candidates; - _comments = comments; - _highlightedIndex = highlightedIndex; _pageNum = pageNum; _finalPage = finalPage; _functionButton = kVoidSymbol; - if (candidates.count > 0 || preedit.length > 0) { + if (indexRange.length > 0 || preedit.length > 0) { _statusMessage = nil; if (_statusTimer.valid) { [_statusTimer invalidate]; @@ -3829,24 +3941,25 @@ - (void)showPreedit:(NSString*)preedit } return; } - [self updateContents]; - [self show]; -} -- (void)updateContents { - [_toolTip.displayTimer invalidate]; SquirrelTheme* theme = _view.currentTheme; - if (theme.lineLength > 0) { - _maxSize.width = MIN(theme.lineLength, _textWidthLimit); - } - NSTextStorage* text = _view.textStorage; - text.attributedString = [[NSAttributedString alloc] init]; + if (updateCandidates) { + text.attributedString = [[NSAttributedString alloc] init]; + if (theme.lineLength > 0) { + _maxSize.width = MIN(theme.lineLength, _textWidthLimit); + } + _indexRange = indexRange; + _highlightedIndex = highlightedIndex; + _view.candidateRanges = + indexRange.length > 0 ? new NSRange[indexRange.length] : NULL; + _view.truncated = + indexRange.length > 0 ? new BOOL[indexRange.length] : NULL; + } NSRange preeditRange = NSMakeRange(NSNotFound, 0); NSRange highlightedPreeditRange = NSMakeRange(NSNotFound, 0); NSRange pagingRange = NSMakeRange(NSNotFound, 0); - _view.candidateRanges = - _candidates.count > 0 ? new NSRange[_candidates.count] : NULL; + NSUInteger candidateBlockStart; NSUInteger lineStart; NSMutableParagraphStyle* paragraphStyleCandidate; @@ -3857,90 +3970,106 @@ - (void)updateContents { CGFloat maxLineLength = 0.0; // preedit - if (_preedit) { + if (preedit) { NSMutableAttributedString* preeditLine = [[NSMutableAttributedString alloc] init]; - if (_selRange.location > 0) { + if (selRange.location > 0) { [preeditLine appendAttributedString: [[NSAttributedString alloc] - initWithString:[_preedit substringToIndex:_selRange.location] + initWithString:[preedit substringToIndex:selRange.location] attributes:theme.preeditAttrs]]; } - if (_selRange.length > 0) { + if (selRange.length > 0) { NSUInteger highlightedPreeditStart = preeditLine.length; - [preeditLine - appendAttributedString: - [[NSAttributedString alloc] - initWithString:[_preedit substringWithRange:_selRange] - attributes:theme.preeditHighlightedAttrs]]; + [preeditLine appendAttributedString: + [[NSAttributedString alloc] + initWithString:[preedit substringWithRange:selRange] + attributes:theme.preeditHighlightedAttrs]]; highlightedPreeditRange = NSMakeRange(highlightedPreeditStart, preeditLine.length - highlightedPreeditStart); CGFloat kerning = [theme.preeditAttrs[NSKernAttributeName] doubleValue]; - if (_selRange.location > 0) { + if (selRange.location > 0) { [preeditLine addAttribute:NSKernAttributeName value:@(kerning * 2) range:NSMakeRange(highlightedPreeditStart - 1, 1)]; } - if (NSMaxRange(_selRange) < _preedit.length) { + if (NSMaxRange(selRange) < preedit.length) { [preeditLine addAttribute:NSKernAttributeName value:@(kerning * 2) range:NSMakeRange(preeditLine.length - 1, 1)]; } } - if (NSMaxRange(_selRange) < _preedit.length) { + if (NSMaxRange(selRange) < preedit.length) { [preeditLine appendAttributedString: [[NSAttributedString alloc] - initWithString:[_preedit - substringFromIndex:NSMaxRange(_selRange)] + initWithString:[preedit + substringFromIndex:NSMaxRange(selRange)] attributes:theme.preeditAttrs]]; } [preeditLine appendAttributedString:[[NSAttributedString alloc] - initWithString:kFullWidthSpace + initWithString:updateCandidates + ? kFullWidthSpace + : @"\t" attributes:theme.preeditAttrs]]; - BOOL caretAtHome = - _caretPos == NSNotFound || - (_caretPos == _selRange.location && _selRange.location == 1); - [preeditLine appendAttributedString:caretAtHome ? theme.symbolDeleteStroke - : theme.symbolDeleteFill]; + [preeditLine appendAttributedString:_caretAtHome ? theme.symbolDeleteStroke + : theme.symbolDeleteFill]; // force caret to be rendered sideways, instead of uprights, in vertical // orientation - if (_caretPos != NSNotFound) { + if (caretPos != NSNotFound) { [preeditLine addAttribute:NSVerticalGlyphFormAttributeName value:@(NO) - range:NSMakeRange( - _caretPos - (_caretPos < NSMaxRange(_selRange)), 1)]; + range:NSMakeRange(caretPos - (caretPos < NSMaxRange(selRange)), + 1)]; } preeditRange = NSMakeRange(0, preeditLine.length); - [text appendAttributedString:preeditLine]; - - if (_candidates.count > 0) { - [text appendAttributedString:[[NSAttributedString alloc] - initWithString:@"\n" - attributes:theme.preeditAttrs]]; + if (updateCandidates) { + [text appendAttributedString:preeditLine]; + if (indexRange.length > 0) { + [text appendAttributedString:[[NSAttributedString alloc] + initWithString:@"\n" + attributes:theme.preeditAttrs]]; + } else { + self.sectionNum = 0; + goto alignDelete; + } } else { - self.activePage = 0; - goto alignDelete; + NSParagraphStyle* rulerStyle = + [text attribute:NSParagraphStyleAttributeName + atIndex:0 + effectiveRange:NULL]; + [preeditLine addAttribute:NSParagraphStyleAttributeName + value:rulerStyle + range:NSMakeRange(0, preeditLine.length)]; + [text replaceCharactersInRange:_view.preeditRange + withAttributedString:preeditLine]; + [_view setPreeditRange:preeditRange + highlightedPreeditRange:highlightedPreeditRange]; } } + if (!updateCandidates) { + [self highlightCandidate:highlightedIndex]; + return; + } + // candidate items candidateBlockStart = text.length; lineStart = text.length; if (theme.linear) { paragraphStyleCandidate = theme.paragraphStyle.copy; } - for (NSUInteger idx = 0; idx < _candidates.count; ++idx) { + for (NSUInteger idx = 0; idx < indexRange.length; ++idx) { NSUInteger col = idx % theme.pageSize; // attributed labels are already included in candidateFormats NSMutableAttributedString* item = - idx == _highlightedIndex + idx == highlightedIndex ? theme.candidateHighlightedFormats[col].mutableCopy : theme.candidateFormats[col].mutableCopy; - NSRange candidateField = [item.string rangeOfString:@"%@"]; + NSRange candidateField = [item.mutableString rangeOfString:@"%@"]; // get the label size for indent NSRange labelRange = NSMakeRange(0, candidateField.location); CGFloat labelWidth = @@ -3948,20 +4077,23 @@ - (void)updateContents { ? 0.0 : ceil([item attributedSubstringFromRange:labelRange].size.width); // hide labels in non-highlighted pages (no selection keys) - if (idx / theme.pageSize != _activePage) { + if (idx / theme.pageSize != _sectionNum) { [item addAttribute:NSForegroundColorAttributeName - value:NSColor.clearColor + value:[theme.labelAttrs[NSForegroundColorAttributeName] + blendedColorWithFraction:0.5 + ofColor:NSColor.clearColor] range:labelRange]; } // plug in candidate texts and comments into the template - [item replaceCharactersInRange:candidateField withString:_candidates[idx]]; - - NSRange commentField = [item.string rangeOfString:kTipSpecifier]; - if (_comments[idx].length > 0) { - [item - replaceCharactersInRange:commentField - withString:[@" " - stringByAppendingString:_comments[idx]]]; + [item replaceCharactersInRange:candidateField + withString:_candidates[idx + indexRange.location]]; + + NSRange commentField = [item.mutableString rangeOfString:kTipSpecifier]; + if (_comments[idx + indexRange.location].length > 0) { + [item replaceCharactersInRange:commentField + withString:[@" " stringByAppendingString: + _comments[idx + + indexRange.location]]]; } else { [item deleteCharactersInRange:commentField]; } @@ -3990,7 +4122,8 @@ - (void)updateContents { range:range]; }]; } - if (_comments[idx].length > 0 && [item.string hasSuffix:@" "]) { + if (_comments[idx + indexRange.location].length > 0 && + [item.mutableString hasSuffix:@" "]) { [item deleteCharactersInRange:NSMakeRange(item.length - 1, 1)]; } if (!theme.linear) { @@ -4014,19 +4147,22 @@ - (void)updateContents { [self shouldBreakLineInsideRange:NSMakeRange( lineStart, text.length - lineStart)])) { - NSRange replaceRange = theme.tabular - ? NSMakeRange(separatorStart + 2, 0) - : NSMakeRange(separatorStart, 1); + NSRange replaceRange = + theme.tabular ? NSMakeRange(separatorStart + separator.length, 0) + : NSMakeRange(separatorStart, 1); [text replaceCharactersInRange:replaceRange withString:@"\n"]; lineStart = separatorStart + (theme.tabular ? 3 : 1); } + if (theme.tabular) { + _view.candidateRanges[idx - 1].length += 2; + } } else { // at the start of a new line, no need to determine line break [text appendAttributedString:item]; } // for linear layout, middle-truncate candidates that are longer than one // line if (theme.linear && ceil(item.size.width) > textWidthLimit) { - if (idx < _candidates.count - 1 || theme.showPaging || theme.tabular) { + if (idx < indexRange.length - 1 || (theme.showPaging && !theme.tabular)) { [text appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:theme.commentAttrs]]; @@ -4037,9 +4173,12 @@ - (void)updateContents { [text addAttribute:NSParagraphStyleAttributeName value:paragraphStyleTruncating range:NSMakeRange(lineStart, item.length)]; - _view.candidateRanges[idx] = NSMakeRange(lineStart, item.length); + _view.truncated[idx] = YES; + _view.candidateRanges[idx] = + NSMakeRange(lineStart, text.length - lineStart); lineStart = text.length; } else { + _view.truncated[idx] = NO; _view.candidateRanges[idx] = NSMakeRange(text.length - item.length, item.length); } @@ -4048,30 +4187,36 @@ - (void)updateContents { // paging indication if (theme.tabular) { [text appendAttributedString:theme.separator]; + _view.candidateRanges[indexRange.length - 1].length += 2; NSUInteger pagingStart = text.length; - [self shouldUseTabInRange:NSMakeRange(pagingStart - 2, 2) - maxLineLength:&maxLineLength]; - CGFloat expanderPosition = - ceil((maxLineLength - theme.expanderWidth) / tabInterval) * tabInterval; NSAttributedString* expander = _locked ? theme.symbolLock : _view.expanded ? theme.symbolCompress : theme.symbolExpand; [text appendAttributedString:expander]; paragraphStyleCandidate = theme.paragraphStyle.mutableCopy; - paragraphStyleCandidate.tabStops = @[]; - CGFloat candidateEndPosition = NSMaxX([_view - blockRectForRange:NSMakeRange(lineStart, pagingStart - 1 - lineStart)]); - for (NSUInteger i = 1; i * tabInterval < candidateEndPosition; ++i) { + if ([self shouldUseTabInRange:NSMakeRange(pagingStart - 2, 3) + maxLineLength:&maxLineLength]) { + [text replaceCharactersInRange:NSMakeRange(pagingStart, 0) + withString:@"\t"]; + paragraphStyleCandidate.tabStops = @[]; + CGFloat candidateEndPosition = NSMaxX([_view + blockRectForRange:NSMakeRange(lineStart, pagingStart - lineStart)]); + for (NSUInteger i = 1; i <= ceil(candidateEndPosition / tabInterval); + ++i) { + [paragraphStyleCandidate + addTabStop:[[NSTextTab alloc] + initWithTextAlignment:NSTextAlignmentLeft + location:i * tabInterval + options:@{}]]; + } [paragraphStyleCandidate addTabStop:[[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft - location:i * tabInterval + location:maxLineLength - + theme.expanderWidth options:@{}]]; } - [paragraphStyleCandidate - addTabStop:[[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft - location:expanderPosition - options:@{}]]; + paragraphStyleCandidate.tailIndent = 0.0; [text addAttribute:NSParagraphStyleAttributeName value:paragraphStyleCandidate range:NSMakeRange(lineStart, text.length - lineStart)]; @@ -4136,15 +4281,9 @@ - (void)updateContents { alignDelete: // right-align the backward delete symbol - if (_preedit && + if (preedit && [self shouldUseTabInRange:NSMakeRange(preeditRange.length - 2, 2) maxLineLength:&maxLineLength]) { - if (theme.tabular && _candidates.count == 0) { - maxLineLength = - ceil((maxLineLength - theme.expanderWidth) / tabInterval) * - tabInterval + - theme.expanderWidth; - } [text replaceCharactersInRange:NSMakeRange(preeditRange.length - 2, 1) withString:@"\t"]; NSMutableParagraphStyle* paragraphStylePreedit = @@ -4160,9 +4299,9 @@ - (void)updateContents { // text done! [text ensureAttributesAreFixedInRange:NSMakeRange(0, text.length)]; - CGFloat topMargin = _preedit ? 0.0 : ceil(theme.linespace * 0.5); + CGFloat topMargin = preedit ? 0.0 : ceil(theme.linespace * 0.5); CGFloat bottomMargin = - _candidates.count > 0 && (theme.linear || !theme.showPaging) + indexRange.length > 0 && (theme.linear || !theme.showPaging) ? floor(theme.linespace * 0.5) : 0.0; NSEdgeInsets insets = NSEdgeInsetsMake( @@ -4170,18 +4309,17 @@ - (void)updateContents { theme.borderInset.width + ceil(theme.separatorWidth * 0.5), theme.borderInset.height + bottomMargin, theme.borderInset.width + floor(theme.separatorWidth * 0.5)); - _view.textView.textContainerInset = - NSMakeSize(theme.borderInset.width + ceil(theme.separatorWidth * 0.5), - theme.borderInset.height + topMargin); - self.animationBehavior = _caretPos == NSNotFound + + self.animationBehavior = caretPos == NSNotFound ? NSWindowAnimationBehaviorUtilityWindow : NSWindowAnimationBehaviorDefault; [_view drawViewWithInsets:insets - numCandidates:_candidates.count - highlightedIndex:_highlightedIndex + numCandidates:indexRange.length + highlightedIndex:highlightedIndex preeditRange:preeditRange highlightedPreeditRange:highlightedPreeditRange pagingRange:pagingRange]; + [self show]; } - (void)updateStatusLong:(NSString*)messageLong @@ -4221,9 +4359,6 @@ - (void)showStatus:(NSString*)message { theme.borderInset.width + ceil(theme.separatorWidth * 0.5), theme.borderInset.height, theme.borderInset.width + floor(theme.separatorWidth * 0.5)); - _view.textView.textContainerInset = - NSMakeSize(theme.borderInset.width + ceil(theme.separatorWidth * 0.5), - theme.borderInset.height); // disable remember_size and fixed line_length for status messages _initPosition = YES; @@ -4699,10 +4834,6 @@ + (void)updateTheme:(SquirrelTheme*)theme size:labelFontSize.doubleValue] : [NSFont monospacedDigitSystemFontOfSize:labelFontSize.doubleValue weight:NSFontWeightRegular]; - NSString* labelString = [theme.labels componentsJoinedByString:@""]; - labelFont = CFBridgingRelease(CTFontCreateForStringWithLanguage( - (CTFontRef)labelFont, (CFStringRef)labelString, - CFRangeMake(0, (CFIndex)labelString.length), CFSTR("zh"))); NSFontDescriptor* commentFontDescriptor = getFontDescriptor(commentFontName); NSFont* commentFont =