From 2ef0d007008ff08966493e00319f4b8890699ae6 Mon Sep 17 00:00:00 2001 From: groverlynn Date: Sun, 12 May 2024 17:28:06 +0200 Subject: [PATCH] use separate textviews --- .../delete.backward.fill.svg | 4 +- .../delete.backward.svg | 4 +- Squirrel.xcodeproj/project.pbxproj | 2 + SquirrelApplicationDelegate.hh | 5 +- SquirrelApplicationDelegate.mm | 53 +- SquirrelConfig.hh | 5 +- SquirrelConfig.mm | 113 +- SquirrelInputController.hh | 5 +- SquirrelInputController.mm | 139 +- SquirrelPanel.hh | 3 +- SquirrelPanel.mm | 2999 +++++++++-------- input_source.mm | 80 +- macos_keycode.mm | 8 +- main.mm | 18 +- 14 files changed, 1755 insertions(+), 1683 deletions(-) diff --git a/Assets.xcassets/Symbols/delete.backward.fill.symbolset/delete.backward.fill.svg b/Assets.xcassets/Symbols/delete.backward.fill.symbolset/delete.backward.fill.svg index 3a839c4d9..25fd848bf 100644 --- a/Assets.xcassets/Symbols/delete.backward.fill.symbolset/delete.backward.fill.svg +++ b/Assets.xcassets/Symbols/delete.backward.fill.symbolset/delete.backward.fill.svg @@ -71,8 +71,8 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN" - - + + diff --git a/Assets.xcassets/Symbols/delete.backward.symbolset/delete.backward.svg b/Assets.xcassets/Symbols/delete.backward.symbolset/delete.backward.svg index e4630ab4d..a2eed5af5 100644 --- a/Assets.xcassets/Symbols/delete.backward.symbolset/delete.backward.svg +++ b/Assets.xcassets/Symbols/delete.backward.symbolset/delete.backward.svg @@ -71,8 +71,8 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN" - - + + diff --git a/Squirrel.xcodeproj/project.pbxproj b/Squirrel.xcodeproj/project.pbxproj index 5a3e2958a..dc9da5ce2 100644 --- a/Squirrel.xcodeproj/project.pbxproj +++ b/Squirrel.xcodeproj/project.pbxproj @@ -638,6 +638,7 @@ CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; @@ -701,6 +702,7 @@ CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; diff --git a/SquirrelApplicationDelegate.hh b/SquirrelApplicationDelegate.hh index fc542f422..6f42e7ee1 100644 --- a/SquirrelApplicationDelegate.hh +++ b/SquirrelApplicationDelegate.hh @@ -1,4 +1,5 @@ #import +#import "rime_api.h" @class SquirrelConfig; @class SquirrelPanel; @@ -8,14 +9,12 @@ // outlet of NSApp's instance @interface SquirrelApplicationDelegate : NSObject -typedef NS_ENUM(NSUInteger, SquirrelNotificationPolicy) { +typedef NS_CLOSED_ENUM(NSUInteger, SquirrelNotificationPolicy) { kShowNotificationsNever = 0, kShowNotificationsWhenAppropriate = 1, kShowNotificationsAlways = 2 }; -typedef uintptr_t RimeSessionId; - @property(nonatomic, weak, nullable) IBOutlet NSMenu* menu; @property(nonatomic, weak, nullable) IBOutlet SquirrelPanel* panel; @property(nonatomic, weak, nullable) IBOutlet id updater; diff --git a/SquirrelApplicationDelegate.mm b/SquirrelApplicationDelegate.mm index 2838d1ef0..19d84bf6a 100644 --- a/SquirrelApplicationDelegate.mm +++ b/SquirrelApplicationDelegate.mm @@ -3,7 +3,6 @@ #import "SquirrelConfig.hh" #import "SquirrelPanel.hh" #import "macos_keycode.hh" -#import "rime_api.h" #import static NSString* const kRimeWikiURL = @"https://github.com/rime/home/wiki"; @@ -15,7 +14,7 @@ @implementation SquirrelApplicationDelegate { - (IBAction)showSwitcher:(id)sender { NSLog(@"Show Switcher"); - if (_switcherKeyEquivalent) { + if (_switcherKeyEquivalent != 0) { RimeSessionId session = [sender unsignedLongValue]; rime_get_api()->process_key(session, _switcherKeyEquivalent, _switcherKeyModifierMask); @@ -46,13 +45,20 @@ - (IBAction)openWiki:(id)sender { } - (IBAction)openLogFolder:(id)sender { - NSURL* logFile = [NSFileManager.defaultManager.temporaryDirectory + NSURL* infoLog = [NSFileManager.defaultManager.temporaryDirectory URLByAppendingPathComponent:@"rime.squirrel.INFO" isDirectory:NO]; - [NSWorkspace.sharedWorkspace activateFileViewerSelectingURLs:@[ logFile ]]; + NSURL* warningLog = [NSFileManager.defaultManager.temporaryDirectory + URLByAppendingPathComponent:@"rime.squirrel.WARNING" + isDirectory:NO]; + NSURL* errorLog = [NSFileManager.defaultManager.temporaryDirectory + URLByAppendingPathComponent:@"rime.squirrel.ERROR" + isDirectory:NO]; + [NSWorkspace.sharedWorkspace + activateFileViewerSelectingURLs:@[ infoLog, warningLog, errorLog ]]; } -void show_notification(const char* msg_text) { +extern void show_notification(const char* msg_text) { if (@available(macOS 10.14, *)) { UNUserNotificationCenter* center = UNUserNotificationCenter.currentNotificationCenter; @@ -61,7 +67,7 @@ void show_notification(const char* msg_text) { UNAuthorizationOptionProvisional completionHandler:^(BOOL granted, NSError* _Nullable error) { - if (error) { + if (error != nil) { NSLog(@"User notification authorization error: %@", error.debugDescription); } @@ -84,7 +90,7 @@ void show_notification(const char* msg_text) { content:content trigger:nil] withCompletionHandler:^(NSError* _Nullable error) { - if (error) { + if (error != nil) { NSLog(@"User notification request error: %@", error.debugDescription); } @@ -104,9 +110,9 @@ void show_notification(const char* msg_text) { } static void show_status(const char* msg_text_long, const char* msg_text_short) { - NSString* msgLong = msg_text_long ? @(msg_text_long) : nil; + NSString* msgLong = msg_text_long != NULL ? @(msg_text_long) : nil; NSString* msgShort = - msg_text_short + msg_text_short != NULL ? @(msg_text_short) : [msgLong substringWithRange: [msgLong rangeOfComposedCharacterSequenceAtIndex:0]]; @@ -118,37 +124,34 @@ static void notification_handler(void* context_object, RimeSessionId session_id, const char* message_type, const char* message_value) { - if (!strcmp(message_type, "deploy")) { - if (!strcmp(message_value, "start")) { + if (strcmp(message_type, "deploy") == 0) { + if (strcmp(message_value, "start") == 0) { show_notification("deploy_start"); - } else if (!strcmp(message_value, "success")) { + } else if (strcmp(message_value, "success") == 0) { show_notification("deploy_success"); - } else if (!strcmp(message_value, "failure")) { + } else if (strcmp(message_value, "failure") == 0) { show_notification("deploy_failure"); } return; } SquirrelApplicationDelegate* app_delegate = (__bridge id)context_object; // schema change - if (!strcmp(message_type, "schema") && + if (strcmp(message_type, "schema") == 0 && app_delegate.showNotifications != kShowNotificationsNever) { const char* schema_name = strchr(message_value, '/'); - if (schema_name) { + if (schema_name != NULL) { ++schema_name; show_status(schema_name, schema_name); } return; } // option change - if (!strcmp(message_type, "option") && app_delegate) { + if (strcmp(message_type, "option") == 0 && app_delegate) { Bool state = message_value[0] != '!'; const char* option_name = message_value + !state; + BOOL updateScriptVariant = [app_delegate.panel.optionSwitcher + updateCurrentScriptVariant:@(message_value)]; BOOL updateStyleOptions = NO; - BOOL updateScriptVariant = NO; - if ([app_delegate.panel.optionSwitcher - updateCurrentScriptVariant:@(message_value)]) { - updateScriptVariant = YES; - } if ([app_delegate.panel.optionSwitcher updateGroupState:@(message_value) ofOption:@(option_name)]) { updateStyleOptions = YES; @@ -167,7 +170,7 @@ static void notification_handler(void* context_object, RimeStringSlice state_label_short = rime_get_api()->get_state_label_abbreviated(session_id, option_name, state, True); - if (state_label_long.str || state_label_short.str) { + if (state_label_long.str != NULL || state_label_short.str != NULL) { const char* short_message = state_label_short.length < strlen(state_label_short.str) ? NULL @@ -227,7 +230,7 @@ - (void)loadSettings { if ([defaultConfig openWithConfigId:@"default"]) { NSString* hotkey = [defaultConfig getStringForOption:@"switcher/hotkeys/@0"]; - if (hotkey) { + if (hotkey != nil) { NSArray* keys = [hotkey componentsSeparatedByString:@"+"]; for (NSUInteger i = 0; i < keys.count - 1; ++i) { _switcherKeyModifierMask |= @@ -304,7 +307,7 @@ - (BOOL)problematicLaunchDetected { NSData* archive = [NSData dataWithContentsOfURL:logfile options:NSDataReadingUncached error:nil]; - if (archive) { + if (archive != nil) { NSDate* previousLaunch = [NSKeyedUnarchiver unarchivedObjectOfClass:NSDate.class fromData:archive @@ -348,7 +351,7 @@ - (void)inputSourceChanged:(NSNotification*)aNotification { CFStringRef inputSource = (CFStringRef)TISGetInputSourceProperty( TISCopyCurrentKeyboardInputSource(), kTISPropertyInputSourceID); CFStringRef bundleId = CFBundleGetIdentifier(CFBundleGetMainBundle()); - if (!CFStringHasPrefix(inputSource, bundleId)) { + if (CFStringHasPrefix(inputSource, bundleId) == kCFCompareEqualTo) { _isCurrentInputMethod = NO; } } diff --git a/SquirrelConfig.hh b/SquirrelConfig.hh index 6821301a1..1dcc5ffd8 100644 --- a/SquirrelConfig.hh +++ b/SquirrelConfig.hh @@ -1,6 +1,5 @@ #import - -typedef uintptr_t RimeSessionId; +#import __attribute__((objc_direct_members)) @interface SquirrelOptionSwitcher : NSObject @@ -101,7 +100,7 @@ typedef NSDictionary SquirrelAppOptions; - (NSUInteger)getListSizeForOption:(NSString* _Nonnull)option; - (NSArray* _Nullable)getListForOption:(NSString* _Nonnull)option; -- (SquirrelOptionSwitcher* _Nullable)getOptionSwitcher; +- (SquirrelOptionSwitcher* _Nonnull)getOptionSwitcher; - (SquirrelAppOptions* _Nonnull)getAppOptions:(NSString* _Nonnull)appName; @end // SquirrelConfig diff --git a/SquirrelConfig.mm b/SquirrelConfig.mm index 45673ef75..b194cdf06 100644 --- a/SquirrelConfig.mm +++ b/SquirrelConfig.mm @@ -1,7 +1,5 @@ #import "SquirrelConfig.hh" -#import - static NSArray* const scripts = @[ @"zh-Hans", @"zh-Hant", @"zh-TW", @"zh-HK", @"zh-MO", @"zh-SG", @"zh-CN", @"zh" @@ -17,8 +15,7 @@ @implementation SquirrelOptionSwitcher defaultScriptVariant:(NSString*)defaultScriptVariant scriptVariantOptions: (NSDictionary*)scriptVariantOptions { - self = [super init]; - if (self) { + if (self = [super init]) { _schemaId = schemaId ?: @""; _switcher = switcher ?: NSMutableDictionary.dictionary; _optionGroups = optionGroups ?: NSDictionary.dictionary; @@ -63,7 +60,7 @@ - (BOOL)updateSwitcher:(NSMutableDictionary*)switcher { - (BOOL)updateGroupState:(NSString*)optionState ofOption:(NSString*)optionName { NSOrderedSet* optionGroup = _optionGroups[optionName]; - if (!optionGroup) { + if (optionGroup == nil) { return NO; } if (optionGroup.count == 1) { @@ -87,7 +84,7 @@ - (BOOL)updateCurrentScriptVariant:(NSString*)scriptVariant { return NO; } NSString* scriptVariantCode = _scriptVariantOptions[scriptVariant]; - if (!scriptVariantCode) { + if (scriptVariantCode == nil) { return NO; } _currentScriptVariant = scriptVariantCode; @@ -136,8 +133,7 @@ @implementation SquirrelConfig { } - (instancetype)init { - self = [super init]; - if (self) { + if (self = [super init]) { _cache = NSCache.alloc.init; _colorSpace = NSColorSpace.sRGBColorSpace; _colorSpaceName = @"sRGB"; @@ -284,14 +280,12 @@ - (NSNumber*)getOptionalDoubleForOption:(NSString*)option } - (NSNumber*)getOptionalBoolForOption:(NSString*)option alias:(NSString*)alias { - NSNumber* cachedValue = [self cachedValueOfObjCType:@encode(BOOL) - forKey:option]; - if (cachedValue) { + if (NSNumber* cachedValue = [self cachedValueOfObjCType:@encode(BOOL) + forKey:option]) { return cachedValue; } - Bool value; - if (_isOpen && - rime_get_api()->config_get_bool(&_config, option.UTF8String, &value)) { + if (Bool value; _isOpen && rime_get_api()->config_get_bool( + &_config, option.UTF8String, &value)) { NSNumber* number = [NSNumber numberWithBool:(BOOL)value]; [_cache setObject:number forKey:option]; return number; @@ -299,8 +293,8 @@ - (NSNumber*)getOptionalBoolForOption:(NSString*)option alias:(NSString*)alias { if (alias != nil) { NSString* aliasOption = [[option stringByDeletingLastPathComponent] stringByAppendingPathComponent:alias.lastPathComponent]; - if (_isOpen && rime_get_api()->config_get_bool( - &_config, aliasOption.UTF8String, &value)) { + if (Bool value; _isOpen && rime_get_api()->config_get_bool( + &_config, aliasOption.UTF8String, &value)) { NSNumber* number = [NSNumber numberWithBool:(BOOL)value]; [_cache setObject:number forKey:option]; return number; @@ -310,14 +304,12 @@ - (NSNumber*)getOptionalBoolForOption:(NSString*)option alias:(NSString*)alias { } - (NSNumber*)getOptionalIntForOption:(NSString*)option alias:(NSString*)alias { - NSNumber* cachedValue = [self cachedValueOfObjCType:@encode(int) - forKey:option]; - if (cachedValue) { + if (NSNumber* cachedValue = [self cachedValueOfObjCType:@encode(int) + forKey:option]) { return cachedValue; } - int value; - if (_isOpen && - rime_get_api()->config_get_int(&_config, option.UTF8String, &value)) { + if (int value; _isOpen && rime_get_api()->config_get_int( + &_config, option.UTF8String, &value)) { NSNumber* number = [NSNumber numberWithInt:value]; [_cache setObject:number forKey:option]; return number; @@ -325,8 +317,8 @@ - (NSNumber*)getOptionalIntForOption:(NSString*)option alias:(NSString*)alias { if (alias != nil) { NSString* aliasOption = [[option stringByDeletingLastPathComponent] stringByAppendingPathComponent:alias.lastPathComponent]; - if (_isOpen && rime_get_api()->config_get_int( - &_config, aliasOption.UTF8String, &value)) { + if (int value; _isOpen && rime_get_api()->config_get_int( + &_config, aliasOption.UTF8String, &value)) { NSNumber* number = [NSNumber numberWithInt:value]; [_cache setObject:number forKey:option]; return number; @@ -337,14 +329,12 @@ - (NSNumber*)getOptionalIntForOption:(NSString*)option alias:(NSString*)alias { - (NSNumber*)getOptionalDoubleForOption:(NSString*)option alias:(NSString*)alias { - NSNumber* cachedValue = [self cachedValueOfObjCType:@encode(double) - forKey:option]; - if (cachedValue) { + if (NSNumber* cachedValue = [self cachedValueOfObjCType:@encode(double) + forKey:option]) { return cachedValue; } - double value; - if (_isOpen && - rime_get_api()->config_get_double(&_config, option.UTF8String, &value)) { + if (double value; _isOpen && rime_get_api()->config_get_double( + &_config, option.UTF8String, &value)) { NSNumber* number = [NSNumber numberWithDouble:value]; [_cache setObject:number forKey:option]; return number; @@ -352,7 +342,8 @@ - (NSNumber*)getOptionalDoubleForOption:(NSString*)option if (alias != nil) { NSString* aliasOption = [[option stringByDeletingLastPathComponent] stringByAppendingPathComponent:alias.lastPathComponent]; - if (_isOpen && rime_get_api()->config_get_double( + if (double value; + _isOpen && rime_get_api()->config_get_double( &_config, aliasOption.UTF8String, &value)) { NSNumber* number = [NSNumber numberWithDouble:value]; [_cache setObject:number forKey:option]; @@ -382,15 +373,14 @@ - (NSImage*)getImageForOption:(NSString*)option { } - (NSString*)getStringForOption:(NSString*)option alias:(NSString*)alias { - NSString* cachedValue = - [self cachedValueOfClass:NSString.class forKey:option]; - if (cachedValue) { + if (NSString* cachedValue = + [self cachedValueOfClass:NSString.class forKey:option]) { return cachedValue; } const char* value = _isOpen ? rime_get_api()->config_get_cstring(&_config, option.UTF8String) : NULL; - if (value) { + if (value != NULL) { NSString* string = [@(value) stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]; [_cache setObject:string forKey:option]; @@ -402,7 +392,7 @@ - (NSString*)getStringForOption:(NSString*)option alias:(NSString*)alias { value = _isOpen ? rime_get_api()->config_get_cstring(&_config, aliasOption.UTF8String) : NULL; - if (value) { + if (value != NULL) { NSString* string = [@(value) stringByTrimmingCharactersInSet:NSCharacterSet .whitespaceCharacterSet]; @@ -414,20 +404,20 @@ - (NSString*)getStringForOption:(NSString*)option alias:(NSString*)alias { } - (NSColor*)getColorForOption:(NSString*)option alias:(NSString*)alias { - NSColor* cachedValue = [self cachedValueOfClass:NSColor.class forKey:option]; - if (cachedValue) { + if (NSColor* cachedValue = + [self cachedValueOfClass:NSColor.class forKey:option]) { return cachedValue; } - NSColor* color = [self colorFromString:[self getStringForOption:option]]; - if (color) { + if (NSColor* color = + [self colorFromString:[self getStringForOption:option]]) { [_cache setObject:color forKey:option]; return color; } if (alias != nil) { NSString* aliasOption = [option.stringByDeletingLastPathComponent stringByAppendingPathComponent:alias.lastPathComponent]; - color = [self colorFromString:[self getStringForOption:aliasOption]]; - if (color) { + if (NSColor* color = + [self colorFromString:[self getStringForOption:aliasOption]]) { [_cache setObject:color forKey:option]; return color; } @@ -436,20 +426,19 @@ - (NSColor*)getColorForOption:(NSString*)option alias:(NSString*)alias { } - (NSImage*)getImageForOption:(NSString*)option alias:(NSString*)alias { - NSImage* cachedValue = [self cachedValueOfClass:NSImage.class forKey:option]; - if (cachedValue) { + if (NSImage* cachedValue = + [self cachedValueOfClass:NSImage.class forKey:option]) { return cachedValue; } - NSImage* image = [self imageFromFile:[self getStringForOption:option]]; - if (image) { + if (NSImage* image = [self imageFromFile:[self getStringForOption:option]]) { [_cache setObject:image forKey:option]; return image; } if (alias != nil) { NSString* aliasOption = [option.stringByDeletingLastPathComponent stringByAppendingPathComponent:alias.lastPathComponent]; - image = [self imageFromFile:[self getStringForOption:aliasOption]]; - if (image) { + if (NSImage* image = + [self imageFromFile:[self getStringForOption:aliasOption]]) { [_cache setObject:image forKey:option]; return image; } @@ -515,7 +504,7 @@ - (NSUInteger)getListSizeForOption:(NSString*)option { - (SquirrelOptionSwitcher*)getOptionSwitcher { RimeConfigIterator switchIter; if (!rime_get_api()->config_begin_list(&switchIter, &_config, "switches")) { - return nil; + return [SquirrelOptionSwitcher.alloc initWithSchemaId:_schemaId]; } NSMutableDictionary* switcher = NSMutableDictionary.alloc.init; @@ -527,10 +516,9 @@ - (SquirrelOptionSwitcher*)getOptionSwitcher { while (rime_get_api()->config_next(&switchIter)) { int reset = [self getIntForOption:[@(switchIter.path) stringByAppendingString:@"/reset"]]; - NSString* name = - [self getStringForOption:[@(switchIter.path) - stringByAppendingString:@"/name"]]; - if (name) { + if (NSString* name = + [self getStringForOption:[@(switchIter.path) + stringByAppendingString:@"/name"]]) { if ([self hasSection:[@"style/!" stringByAppendingString:name]] || [self hasSection:[@"style/" stringByAppendingString:name]]) { switcher[name] = reset ? name : [@"!" stringByAppendingString:name]; @@ -605,10 +593,9 @@ - (SquirrelAppOptions*)getAppOptions:(NSString*)appName { while (rime_get_api()->config_next(&iterator)) { // NSLog(@"DEBUG option[%d]: %s (%s)", iterator.index, iterator.key, // iterator.path); - NSNumber *value = [self getOptionalBoolForOption:@(iterator.path)] ? : - [self getOptionalIntForOption:@(iterator.path)] ? : - [self getOptionalDoubleForOption:@(iterator.path)]; - if (value) { + if (NSNumber *value = [self getOptionalBoolForOption:@(iterator.path)] ? : + [self getOptionalIntForOption:@(iterator.path)] ? : + [self getOptionalDoubleForOption:@(iterator.path)]) { appOptions[@(iterator.key)] = value; } } @@ -619,17 +606,16 @@ - (SquirrelAppOptions*)getAppOptions:(NSString*)appName { #pragma mark - Private methods - (id)cachedValueOfClass:(Class)aClass forKey:(NSString*)key { - id value = [_cache objectForKey:key]; - if ([value isMemberOfClass:aClass]) { + if (id value = [_cache objectForKey:key]; [value isMemberOfClass:aClass]) { return value; } return nil; } - (NSNumber*)cachedValueOfObjCType:(const char*)type forKey:(NSString*)key { - id value = [_cache objectForKey:key]; - if ([value isMemberOfClass:NSNumber.class] && - !strcmp([value objCType], type)) { + if (id value = [_cache objectForKey:key]; + [value isMemberOfClass:NSNumber.class] && + strcmp([value objCType], type) == 0) { return value; } return nil; @@ -641,8 +627,7 @@ - (NSColor*)colorFromString:(NSString*)string { return nil; } NSScanner* hexScanner = [NSScanner scannerWithString:string]; - UInt hex = 0x0; - if ([hexScanner scanHexInt:&hex] && hexScanner.atEnd) { + if (UInt hex = 0x0; [hexScanner scanHexInt:&hex] && hexScanner.atEnd) { UInt r = hex % 0x100; UInt g = hex / 0x100 % 0x100; UInt b = hex / 0x10000 % 0x100; diff --git a/SquirrelInputController.hh b/SquirrelInputController.hh index 7ba40c994..d9749e75f 100644 --- a/SquirrelInputController.hh +++ b/SquirrelInputController.hh @@ -1,4 +1,3 @@ -#import #import @interface SquirrelInputController : IMKInputController @@ -43,9 +42,7 @@ typedef NS_ENUM(NSUInteger, SquirrelIndex) { NSMutableArray* candidateComments; - (void)moveCursor:(NSUInteger)cursorPosition - toPosition:(NSUInteger)targetPosition - inlinePreedit:(BOOL)inlinePreedit - inlineCandidate:(BOOL)inlineCandidate __attribute__((objc_direct)); + toPosition:(NSUInteger)targetPosition __attribute__((objc_direct)); - (void)performAction:(SquirrelAction)action onIndex:(SquirrelIndex)index __attribute__((objc_direct)); diff --git a/SquirrelInputController.mm b/SquirrelInputController.mm index 8afa82397..89ba03f9c 100644 --- a/SquirrelInputController.mm +++ b/SquirrelInputController.mm @@ -6,7 +6,6 @@ #import "macos_keycode.hh" #import #import -#import #import #import @@ -30,7 +29,8 @@ @implementation SquirrelInputController { BOOL _inlineCandidate; BOOL _goodOldCapsLock; BOOL _showingSwitcherMenu; - // app-specific bug fix + // app-specific options and bug fix + SquirrelAppOptions* _appOptions; BOOL _inlinePlaceholder; BOOL _panellessCommitFix; int _inlineOffset; @@ -77,9 +77,9 @@ - (BOOL)handleEvent:(NSEvent*)event client:(id)sender { BOOL handled = NO; @autoreleasepool { - if (!_session || !rime_get_api()->find_session(_session)) { + if (_session == 0 || !rime_get_api()->find_session(_session)) { [self createSession]; - if (!_session) { + if (_session == 0) { return NO; } } @@ -194,7 +194,7 @@ - (BOOL)handleEvent:(NSEvent*)event client:(id)sender { // sender, modifiers, keyCode, keyChars); // translate mac keydown events to rime keyevents int rime_keycode = rime_keycode_from_mac_keycode(keyCode); - if (!rime_keycode) { + if (rime_keycode == 0) { NSString* keyChars = ((modifiers & NSEventModifierFlagShift) && !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption))) @@ -211,7 +211,7 @@ - (BOOL)handleEvent:(NSEvent*)event client:(id)sender { // Navigations, F1..F19) rime_modifiers ^= kHyperMask; } - if (rime_keycode) { + if (rime_keycode != 0) { if ((handled = [self processKey:rime_keycode modifiers:rime_modifiers])) { [self rimeUpdate]; @@ -265,10 +265,7 @@ - (BOOL)mouseDownOnCharacterIndex:(NSUInteger)index } else if (point.x < head.x || index <= 0) { [self performAction:kPROCESS onIndex:kHomeKey]; } else { - [self moveCursor:_inlineCaretPos - toPosition:index - inlinePreedit:_inlinePreedit - inlineCandidate:_inlineCandidate]; + [self moveCursor:_inlineCaretPos toPosition:index]; } return YES; } @@ -314,13 +311,13 @@ - (BOOL)processKey:(int)rime_keycode if (newIndex != NSNotFound) { if (!panel.locked && !panel.expanded && rime_keycode == (is_vertical ? XK_Left : XK_Down)) { - [panel setExpanded:YES]; + panel.expanded = YES; } rime_get_api()->highlight_candidate(_session, newIndex); return YES; } else if (!panel.locked && panel.expanded && panel.sectionNum == 0 && rime_keycode == (is_vertical ? XK_Right : XK_Up)) { - [panel setExpanded:NO]; + panel.expanded = NO; return YES; } } @@ -368,12 +365,10 @@ - (BOOL)processKey:(int)rime_keycode } - (void)moveCursor:(NSUInteger)cursorPosition - toPosition:(NSUInteger)targetPosition - inlinePreedit:(BOOL)inlinePreedit - inlineCandidate:(BOOL)inlineCandidate __attribute__((objc_direct)) { + toPosition:(NSUInteger)targetPosition __attribute__((objc_direct)) { BOOL vertical = NSApp.squirrelAppDelegate.panel.vertical; @autoreleasepool { - NSString* composition = !inlinePreedit && !inlineCandidate + NSString* composition = !_inlinePreedit && !_inlineCandidate ? _composedString : _preeditString.string; RIME_STRUCT(RimeContext, ctx); @@ -388,14 +383,14 @@ - (void)moveCursor:(NSUInteger)cursorPosition rime_get_api()->process_key(_session, vertical ? XK_Up : XK_Left, kControlMask); rime_get_api()->get_context(_session, &ctx); - if (inlineCandidate) { + if (_inlineCandidate) { size_t length = ctx.composition.cursor_pos < ctx.composition.sel_end ? (size_t)ctx.composition.cursor_pos : strlen(ctx.commit_text_preview) - - (inlinePreedit ? 0 - : (size_t)(ctx.composition.cursor_pos - - ctx.composition.sel_end)); + (_inlinePreedit ? 0 + : (size_t)(ctx.composition.cursor_pos - + ctx.composition.sel_end)); prefix = [[NSString.alloc initWithBytes:ctx.commit_text_preview length:(NSUInteger)length encoding:NSUTF8StringEncoding] @@ -423,7 +418,7 @@ - (void)moveCursor:(NSUInteger)cursorPosition kControlMask); rime_get_api()->get_context(_session, &ctx); suffix = [@(ctx.composition.preedit + ctx.composition.cursor_pos + - (!inlinePreedit && !inlineCandidate ? 3 : 0)) + (!_inlinePreedit && !_inlineCandidate ? 3 : 0)) stringByReplacingOccurrencesOfString:@" " withString:@""]; rime_get_api()->free_context(&ctx); @@ -465,7 +460,7 @@ - (void)performAction:(SquirrelAction)action - (void)onChordTimer:(NSTimer*)timer { // chord release triggered by timer int processed_keys = 0; - if (_chordKeyCount && _session) { + if (_chordKeyCount > 0 && _session != 0) { // simulate key-ups for (int i = 0; i < _chordKeyCount; ++i) { if (rime_get_api()->process_key(_session, _chordKeyCodes[i], @@ -531,7 +526,8 @@ - (NSUInteger)recognizedEvents:(id)sender { Bool state) { RimeStringSlice short_label = rime_get_api()->get_state_label_abbreviated(session, option, state, True); - if (short_label.str && short_label.length >= strlen(short_label.str)) { + if (short_label.str != NULL && + short_label.length >= strlen(short_label.str)) { return @(short_label.str); } else { RimeStringSlice long_label = rime_get_api()->get_state_label_abbreviated( @@ -544,25 +540,22 @@ - (NSUInteger)recognizedEvents:(id)sender { - (void)showInitialStatus __attribute__((objc_direct)) { RIME_STRUCT(RimeStatus, status); - if (_session && rime_get_api()->get_status(_session, &status)) { + if (_session != 0 && rime_get_api()->get_status(_session, &status)) { _schemaId = @(status.schema_id); NSString* schemaName = status.schema_name ? @(status.schema_name) : @(status.schema_id); NSMutableArray* options = [NSMutableArray.alloc initWithCapacity:3]; - NSString* asciiMode = - getOptionLabel(_session, "ascii_mode", status.is_ascii_mode); - if (asciiMode) { + if (NSString* asciiMode = + getOptionLabel(_session, "ascii_mode", status.is_ascii_mode)) { [options addObject:asciiMode]; } - NSString* fullShape = - getOptionLabel(_session, "full_shape", status.is_full_shape); - if (fullShape) { + if (NSString* fullShape = + getOptionLabel(_session, "full_shape", status.is_full_shape)) { [options addObject:fullShape]; } - NSString* asciiPunct = - getOptionLabel(_session, "ascii_punct", status.is_ascii_punct); - if (asciiPunct) { + if (NSString* asciiPunct = + getOptionLabel(_session, "ascii_punct", status.is_ascii_punct)) { [options addObject:asciiPunct]; } rime_get_api()->free_status(&status); @@ -602,7 +595,7 @@ - (void)activateServer:(id)sender { keyboardLayout = [@"com.apple.keylayout." stringByAppendingString:keyboardLayout]; } - if (keyboardLayout) { + if (keyboardLayout != nil) { [sender overrideKeyboardWithKeyboardNamed:keyboardLayout]; } @@ -631,10 +624,10 @@ - (instancetype)initWithServer:(IMKServer*)server delegate:(id)delegate client:(id)inputClient { // NSLog(@"initWithServer:delegate:client:"); - self = [super initWithServer:server delegate:delegate client:inputClient]; - if (self) { + if (self = [super initWithServer:server + delegate:delegate + client:inputClient]) { [self createSession]; - self.delegate = self; _candidateTexts = NSMutableArray.alloc.init; _candidateComments = NSMutableArray.alloc.init; } @@ -664,7 +657,7 @@ - (void)deactivateServer:(id)sender { - (void)commitComposition:(id)sender { // NSLog(@"commitComposition:"); [self commitString:[self composedString:sender]]; - if (_session) { + if (_session != 0) { rime_get_api()->clear_composition(_session); } [self hidePalettes]; @@ -750,7 +743,7 @@ - (NSRange)replacementRange { - (void)commitString:(id)string { // NSLog(@"commitString:"); - if (string) { + if (string != nil) { [self.client insertText:string replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; } @@ -760,7 +753,7 @@ - (void)commitString:(id)string { - (void)cancelComposition { [self commitString:[self originalString:self.client]]; [self hidePalettes]; - if (_session) { + if (_session != 0) { rime_get_api()->clear_composition(_session); } } @@ -786,8 +779,8 @@ - (void)showPreeditString:(NSString*)preedit selRange:(NSRange)range caretPos:(NSUInteger)pos __attribute__((objc_direct)) { // NSLog(@"showPreeditString: '%@'", preedit); - if ([preedit isEqualToString:_preeditString.string] && - NSEqualRanges(range, _inlineSelRange) && pos == _inlineCaretPos) { + if (pos == _inlineCaretPos && NSEqualRanges(range, _inlineSelRange) && + [preedit isEqualToString:_preeditString.string]) { return; } _inlineSelRange = range; @@ -909,20 +902,11 @@ - (void)createSession __attribute__((objc_direct)) { // NSLog(@"createSession: %@", app); _session = rime_get_api()->create_session(); _schemaId = nil; - if (_session) { - SquirrelAppOptions* appOptions = - [NSApp.squirrelAppDelegate.config getAppOptions:app]; - for (NSString* key in appOptions) { - NSNumber* number = appOptions[key]; - if (strcmp(number.objCType, @encode(BOOL)) == 0) { - Bool value = number.boolValue; - // NSLog(@"set app option: %@ = %d", key, value); - rime_get_api()->set_option(_session, key.UTF8String, value); - } - } - _panellessCommitFix = appOptions[@"panelless_commit_fix"].boolValue; - _inlinePlaceholder = appOptions[@"inline_placeholder"].boolValue; - _inlineOffset = appOptions[@"inline_offset"].intValue; + if (_session != 0) { + _appOptions = [NSApp.squirrelAppDelegate.config getAppOptions:app]; + _panellessCommitFix = _appOptions[@"panelless_commit_fix"].boolValue; + _inlinePlaceholder = _appOptions[@"inline_placeholder"].boolValue; + _inlineOffset = _appOptions[@"inline_offset"].intValue; if ([app isEqualToString:_currentApp] && _asciiMode >= 0) { rime_get_api()->set_option(_session, "ascii_mode", _asciiMode); } @@ -934,7 +918,7 @@ - (void)createSession __attribute__((objc_direct)) { - (void)destroySession __attribute__((objc_direct)) { // NSLog(@"destroySession:"); - if (_session) { + if (_session != 0) { rime_get_api()->destroy_session(_session); _session = 0; } @@ -967,12 +951,12 @@ static inline NSUInteger UTF8LengthToUTF16Length(const char* string, .length; } -static inline NSUInteger fmin(NSUInteger int_a, NSUInteger int_b) { - return int_a < int_b ? int_a : int_b; +static inline NSUInteger fmin(NSUInteger x, NSUInteger y) { + return x < y ? x : y; } -static inline NSUInteger fmax(NSUInteger int_a, NSUInteger int_b) { - return int_a < int_b ? int_b : int_a; +static inline NSUInteger fmax(NSUInteger x, NSUInteger y) { + return x < y ? y : x; } - (void)rimeUpdate __attribute__((objc_direct)) { @@ -984,7 +968,8 @@ - (void)rimeUpdate __attribute__((objc_direct)) { RIME_STRUCT(RimeStatus, status); if (rime_get_api()->get_status(_session, &status)) { // enable schema specific ui style - if (!_schemaId || strcmp(_schemaId.UTF8String, status.schema_id)) { + if (_schemaId == nil || + strcmp(_schemaId.UTF8String, status.schema_id) != 0) { _schemaId = @(status.schema_id); _showingSwitcherMenu = (BOOL)rime_get_api()->get_option(_session, "dumb"); if (!_showingSwitcherMenu) { @@ -992,11 +977,9 @@ - (void)rimeUpdate __attribute__((objc_direct)) { [NSApp.squirrelAppDelegate loadSchemaSpecificSettings:_schemaId withRimeSession:_session]; // inline preedit - _inlinePreedit = (panel.inlinePreedit && - !rime_get_api()->get_option(_session, "no_inline")) || - rime_get_api()->get_option(_session, "inline"); - _inlineCandidate = panel.inlineCandidate && - !rime_get_api()->get_option(_session, "no_inline"); + _inlinePreedit = (panel.inlinePreedit && !_appOptions[@"no_inline"]) || + _appOptions[@"inline"]; + _inlineCandidate = panel.inlineCandidate && !_appOptions[@"no_inline"]; // if not inline, embed soft cursor in preedit string rime_get_api()->set_option(_session, "soft_cursor", !_inlinePreedit); } else { @@ -1021,15 +1004,13 @@ - (void)rimeUpdate __attribute__((objc_direct)) { _originalString = originalString; // update composed string - if (!preedit || _showingSwitcherMenu) { + if (preedit == NULL || _showingSwitcherMenu) { _composedString = @""; } else if (!_inlinePreedit) { // remove soft cursor - size_t cursorPos = - (size_t)ctx.composition.cursor_pos - - (ctx.composition.cursor_pos < ctx.composition.sel_end ? 3 : 0); char composed[strlen(preedit) - 2]; - strlcpy(composed, preedit, cursorPos + 1); - strlcat(composed, preedit + cursorPos + 3, strlen(preedit) - 2); + strlcpy(composed, preedit, (size_t)ctx.composition.cursor_pos + 1); + strlcat(composed, preedit + ctx.composition.cursor_pos + 3, + strlen(preedit) - 2); _composedString = @(composed); } else { _composedString = @(preedit); @@ -1051,7 +1032,8 @@ - (void)rimeUpdate __attribute__((objc_direct)) { BOOL finalPage = (BOOL)ctx.menu.is_last_page; NSRange selRange = NSMakeRange(start, end - start); - didCompose |= !NSEqualRanges(_selRange, selRange) && pageNum == 0; + didCompose |= !NSEqualRanges(_selRange, selRange) && + highlightedIndex == 0 && pageNum == 0; _selRange = selRange; // update expander and section status in tabular layout; // already processed the action if _currentIndex == NSNotFound @@ -1143,9 +1125,9 @@ - (void)rimeUpdate __attribute__((objc_direct)) { caretPos:caretPos]; } } else { - if (_inlinePlaceholder && preedit) { + if (_inlinePlaceholder && preedit != NULL) { [self showPlaceholder:kFullWidthSpace]; - } else if (!didCommit || preedit) { + } else if (!didCommit || preedit != NULL) { [self showPreeditString:@"" selRange:NSMakeRange(0, 0) caretPos:0]; } } @@ -1220,11 +1202,12 @@ - (void)updateCandidate:(RimeCandidate*)candidate return; } if (index == _candidateTexts.count || - strcmp(candidate->text, _candidateTexts[index].UTF8String)) { + strcmp(candidate->text, _candidateTexts[index].UTF8String) != 0) { _candidateTexts[index] = @(candidate->text); } if (index == _candidateComments.count || - strcmp(candidate->comment ?: "", _candidateComments[index].UTF8String)) { + strcmp(candidate->comment ?: "", _candidateComments[index].UTF8String) != + 0) { _candidateComments[index] = @(candidate->comment ?: ""); } } diff --git a/SquirrelPanel.hh b/SquirrelPanel.hh index abb9e062a..78514198e 100644 --- a/SquirrelPanel.hh +++ b/SquirrelPanel.hh @@ -1,4 +1,3 @@ -#import #import "SquirrelInputController.hh" @class SquirrelConfig; @@ -40,7 +39,7 @@ statusShort:(NSString* _Nullable)messageShort __attribute__((objc_direct)); // display -- (void)showPreedit:(NSString* _Nullable)preeditString +- (void)showPreedit:(NSString* _Nullable)preedit selRange:(NSRange)selRange caretPos:(NSUInteger)caretPos candidateIndices:(NSRange)indexRange diff --git a/SquirrelPanel.mm b/SquirrelPanel.mm index 833f0828c..203ebfa9c 100644 --- a/SquirrelPanel.mm +++ b/SquirrelPanel.mm @@ -12,9 +12,16 @@ static const CGFloat kDefaultFontSize = 24; static const CGFloat kOffsetGap = 5; +template +static inline T clamp(T x, T min, T max) { + const auto y = x < min ? min : x; + return y > max ? max : y; +} + +__attribute__((objc_direct_members)) @interface NSBezierPath (BezierPathQuartzUtilities) -@property(nonatomic, readonly) CGPathRef quartzPath; +@property(nonatomic, readonly, nullable) CGPathRef quartzPath; @end @@ -27,8 +34,7 @@ - (CGPathRef)quartzPath { // Need to begin a path here. CGPathRef immutablePath = NULL; // Then draw the path elements. - NSInteger numElements = self.elementCount; - if (numElements > 0) { + if (NSInteger numElements = self.elementCount; numElements > 0) { CGMutablePathRef path = CGPathCreateMutable(); NSPoint points[3]; for (NSInteger i = 0; i < numElements; i++) { @@ -80,7 +86,7 @@ - (void)superscriptionRange:(NSRange)range { NSFontAttributeName : font, (id)kCTBaselineClassAttributeName : (id)kCTBaselineClassIdeographicCentered, - NSSuperscriptAttributeName : @(1) + NSSuperscriptAttributeName : @1 } range:subRange]; }]; @@ -101,7 +107,7 @@ - (void)subscriptionRange:(NSRange)range { NSFontAttributeName : font, (id)kCTBaselineClassAttributeName : (id)kCTBaselineClassIdeographicCentered, - NSSuperscriptAttributeName : @(-1) + NSSuperscriptAttributeName : @-1 } range:subRange]; }]; @@ -208,9 +214,9 @@ - (CGFloat)annotateRubyInRange:(NSRange)range .location, 1)]; } else { - // base string must use only one font so that all fall - // within one glyph run and the ruby annotation is - // aligned with no duplicates + /* base string must use only one font so that all fall + within one glyph run and the ruby annotation is + aligned with no duplicates */ NSFont* baseFont = [self attribute:NSFontAttributeName atIndex:baseRange.location effectiveRange:NULL]; @@ -287,16 +293,20 @@ - (NSAttributedString*)attributedStringHorizontalInVerticalForms { imageWithSize:NSMakeSize(height, width) flipped:YES drawingHandler:^BOOL(NSRect dstRect) { - CGContextRef context = NSGraphicsContext.currentContext.CGContext; - CGContextSaveGState(context); - CGContextTranslateCTM(context, NSWidth(dstRect) * 0.5, - NSHeight(dstRect) * 0.5); - CGContextRotateCTM(context, -M_PI_2); + [NSGraphicsContext saveGraphicsState]; + NSGraphicsContext.currentContext.shouldAntialias = YES; + NSGraphicsContext.currentContext.imageInterpolation = + NSImageInterpolationHigh; + NSAffineTransform* transform = NSAffineTransform.transform; + [transform translateXBy:NSWidth(dstRect) * 0.5 + yBy:NSHeight(dstRect) * 0.5]; + [transform rotateByDegrees:-90.0]; + [transform concat]; CGPoint origin = CGPointMake(-self.size.width / width * NSHeight(dstRect) * 0.5, -NSWidth(dstRect) * 0.5); [self drawAtPoint:origin]; - CGContextRestoreGState(context); + [NSGraphicsContext restoreGraphicsState]; return YES; }]; image.resizingMode = NSImageResizingModeStretch; @@ -336,8 +346,17 @@ + (NSColorSpace*)labColorSpace { @end // NSColorSpace (labColorSpace) __attribute__((objc_direct_members)) -@implementation -NSColor(semanticColors) +@interface NSColor (semanticColors) + +@property(nonatomic, strong, readonly, nonnull, class) + NSColor* secondaryTextColor; +@property(nonatomic, strong, readonly, nonnull, class) NSColor* accentColor; +@property(nonatomic, strong, readonly, nonnull) NSColor* hooverColor; +@property(nonatomic, strong, readonly, nonnull) NSColor* disabledColor; + +@end + +@implementation NSColor (semanticColors) + (NSColor*)secondaryTextColor { if (@available(macOS 10.10, *)) { @@ -384,97 +403,90 @@ - (NSColor*)disabledColor { __attribute__((objc_direct_members)) @interface NSColor (NSColorWithLabColorSpace) -@property(nonatomic, readonly) CGFloat luminanceComponent; -@property(nonatomic, readonly) CGFloat aGnRdComponent; -@property(nonatomic, readonly) CGFloat bBuYlComponent; +typedef NS_CLOSED_ENUM(NSInteger, ColorInversionExtent) { + kStandardColorInversion = 0, + kAugmentedColorInversion = 1, + kModerateColorInversion = -1 +}; + +@property(nonatomic, readonly) CGFloat lStarComponent; // Luminance +@property(nonatomic, readonly) CGFloat aStarComponent; // Green-Red +@property(nonatomic, readonly) CGFloat bStarComponent; // Blue-Yellow @end @implementation NSColor (NSColorWithLabColorSpace) -typedef NS_ENUM(NSInteger, ColorInversionExtent) { - kDefaultColorInversion = 0, - kAugmentedColorInversion = 1, - kModerateColorInversion = -1 -}; - -+ (NSColor*)colorWithLabLuminance:(CGFloat)luminance - aGnRd:(CGFloat)aGnRd - bBuYl:(CGFloat)bBuYl - alpha:(CGFloat)alpha { ++ (NSColor*)colorWithLabLStar:(CGFloat)lStar + aStar:(CGFloat)aStar + bStar:(CGFloat)bStar + alpha:(CGFloat)alpha { CGFloat components[4]; - components[0] = fmax(fmin(luminance, 100.0), 0.0); - components[1] = fmax(fmin(aGnRd, 127.0), -127.0); - components[2] = fmax(fmin(bBuYl, 127.0), -127.0); - components[3] = fmax(fmin(alpha, 1.0), 0.0); + components[0] = clamp(lStar, 0.0, 100.0); + components[1] = clamp(aStar, -127.0, 127.0); + components[2] = clamp(bStar, -127.0, 127.0); + components[3] = clamp(alpha, 0.0, 1.0); return [NSColor colorWithColorSpace:NSColorSpace.labColorSpace components:components count:4]; } -- (void)getLuminance:(CGFloat*)luminance - aGnRd:(CGFloat*)aGnRd - bBuYl:(CGFloat*)bBuYl - alpha:(CGFloat*)alpha { - static CGFloat luminanceComponent, aGnRdComponent, bBuYlComponent, - alphaComponent; +- (void)getLStar:(CGFloat*)lStar + aStar:(CGFloat*)aStar + bStar:(CGFloat*)bStar + alpha:(CGFloat*)alpha { + static CGFloat components[4] = {0.0, 0.0, 0.0, 1.0}; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - CGFloat components[4] = {0.0, 0.0, 0.0, 1.0}; [([self.colorSpace isEqualTo:NSColorSpace.labColorSpace] ? self : [self colorUsingColorSpace:NSColorSpace.labColorSpace]) getComponents:components]; - luminanceComponent = components[0] / 100.0; - aGnRdComponent = components[1] / 127.0; - bBuYlComponent = components[2] / 127.0; - alphaComponent = components[3]; + components[0] /= 100.0; + components[1] /= 127.0; + components[2] /= 127.0; }); - if (luminance != NULL) - *luminance = luminanceComponent; - if (aGnRd != NULL) - *aGnRd = aGnRdComponent; - if (bBuYl != NULL) - *bBuYl = bBuYlComponent; + if (lStar != NULL) + *lStar = components[0]; + if (aStar != NULL) + *aStar = components[1]; + if (bStar != NULL) + *bStar = components[2]; if (alpha != NULL) - *alpha = alphaComponent; + *alpha = components[3]; } -- (CGFloat)luminanceComponent { - CGFloat luminance; - [self getLuminance:&luminance aGnRd:NULL bBuYl:NULL alpha:NULL]; - return luminance; +- (CGFloat)lStarComponent { + CGFloat lStarComponent; + [self getLStar:&lStarComponent aStar:NULL bStar:NULL alpha:NULL]; + return lStarComponent; } -- (CGFloat)aGnRdComponent { - CGFloat aGnRdComponent; - [self getLuminance:NULL aGnRd:&aGnRdComponent bBuYl:NULL alpha:NULL]; - return aGnRdComponent; +- (CGFloat)aStarComponent { + CGFloat aStarComponent; + [self getLStar:NULL aStar:&aStarComponent bStar:NULL alpha:NULL]; + return aStarComponent; } -- (CGFloat)bBuYlComponent { - CGFloat bBuYlComponent; - [self getLuminance:NULL aGnRd:NULL bBuYl:&bBuYlComponent alpha:NULL]; - return bBuYlComponent; +- (CGFloat)bStarComponent { + CGFloat bStarComponent; + [self getLStar:NULL aStar:NULL bStar:&bStarComponent alpha:NULL]; + return bStarComponent; } - (NSColor*)colorByInvertingLuminanceToExtent:(ColorInversionExtent)extent { NSColor* labColor = [self colorUsingColorSpace:NSColorSpace.labColorSpace]; CGFloat components[4] = {0.0, 0.0, 0.0, 1.0}; [labColor getComponents:components]; - BOOL isDark = components[0] < 60; switch (extent) { case kAugmentedColorInversion: - components[0] = isDark ? 100.0 - components[0] * 2.0 / 3.0 - : 150.0 - components[0] * 1.5; + components[0] = 100.0 - components[0]; break; case kModerateColorInversion: - components[0] = - isDark ? 80.0 - components[0] / 3.0 : 135.0 - components[0] * 1.25; + components[0] = fma(components[0], -0.6, 80.0); break; - case kDefaultColorInversion: - components[0] = - isDark ? 90.0 - components[0] / 2.0 : 120.0 - components[0]; + case kStandardColorInversion: + components[0] = fma(components[0], -0.8, 90.0); break; } NSColor* invertedColor = @@ -491,13 +503,13 @@ - (NSColor*)colorByInvertingLuminanceToExtent:(ColorInversionExtent)extent { __attribute__((objc_direct_members)) @interface SquirrelTheme : NSObject -typedef NS_ENUM(NSUInteger, SquirrelAppear) { - defaultAppear = 0, - lightAppear = 0, - darkAppear = 1 +typedef NS_CLOSED_ENUM(BOOL, SquirrelAppearance) { + kDefaultAppearance = NO, + kLightAppearance = NO, + kDarkAppearance = YES }; -typedef NS_ENUM(NSUInteger, SquirrelStatusMessageType) { +typedef NS_CLOSED_ENUM(NSUInteger, SquirrelStatusMessageType) { kStatusMessageTypeMixed = 0, kStatusMessageTypeShort = 1, kStatusMessageTypeLong = 2 @@ -523,15 +535,15 @@ typedef NS_ENUM(NSUInteger, SquirrelStatusMessageType) { @property(nonatomic, strong, readonly, nullable) NSColor* borderColor; @property(nonatomic, strong, readonly, nullable) NSImage* backImage; +@property(nonatomic, readonly) NSSize borderInsets; @property(nonatomic, readonly) CGFloat cornerRadius; @property(nonatomic, readonly) CGFloat hilitedCornerRadius; @property(nonatomic, readonly) CGFloat fullWidth; @property(nonatomic, readonly) CGFloat linespace; @property(nonatomic, readonly) CGFloat preeditLinespace; @property(nonatomic, readonly) CGFloat opacity; -@property(nonatomic, readonly) CGFloat translucency; @property(nonatomic, readonly) CGFloat lineLength; -@property(nonatomic, readonly) NSSize borderInsets; +@property(nonatomic, readonly) float translucency; @property(nonatomic, readonly) BOOL showPaging; @property(nonatomic, readonly) BOOL rememberSize; @property(nonatomic, readonly) BOOL tabular; @@ -564,8 +576,6 @@ typedef NS_ENUM(NSUInteger, SquirrelStatusMessageType) { NSParagraphStyle* truncatedParagraphStyle; @property(nonatomic, strong, readonly, nonnull) NSAttributedString* separator; -@property(nonatomic, strong, readonly, nonnull) - NSAttributedString* fullWidthPlaceholder; @property(nonatomic, strong, readonly, nonnull) NSAttributedString* symbolDeleteFill; @property(nonatomic, strong, readonly, nonnull) @@ -599,22 +609,16 @@ typedef NS_ENUM(NSUInteger, SquirrelStatusMessageType) { - (void)updateLabelsWithConfig:(SquirrelConfig* _Nonnull)config directUpdate:(BOOL)update; - - (void)setSelectKeys:(NSString* _Nonnull)selectKeys labels:(NSArray* _Nonnull)labels directUpdate:(BOOL)update; - - (void)setCandidateFormat:(NSString* _Nonnull)candidateFormat; - - (void)setStatusMessageType:(NSString* _Nullable)type; - - (void)updateWithConfig:(SquirrelConfig* _Nonnull)config styleOptions:(NSSet* _Nonnull)styleOptions scriptVariant:(NSString* _Nonnull)scriptVariant - forAppearance:(SquirrelAppear)appear; - + forAppearance:(SquirrelAppearance)appearance; - (void)setAnnotationHeight:(CGFloat)height; - - (void)setScriptVariant:(NSString* _Nonnull)scriptVariant; @end @@ -637,15 +641,14 @@ @implementation SquirrelTheme NSMutableArray* validFontDescriptors = [NSMutableArray.alloc initWithCapacity:fontNames.count]; for (NSString* fontName in fontNames) { - NSFont* font = [NSFont - fontWithName:[fontName - stringByTrimmingCharactersInSet: - NSCharacterSet.whitespaceAndNewlineCharacterSet] - size:0.0]; - if (font != nil) { - // If the font name is not valid, NSFontDescriptor will still create - // something for us. However, when we draw the actual text, Squirrel will - // crash if there is any font descriptor with invalid font name. + if (NSFont* font = [NSFont + fontWithName:[fontName stringByTrimmingCharactersInSet: + NSCharacterSet + .whitespaceAndNewlineCharacterSet] + size:0.0]) { + /* If the font name is not valid, NSFontDescriptor will still create + something for us. However, when we draw the actual text, Squirrel will + crash if there is any font descriptor with invalid font name. */ NSFontDescriptor* fontDescriptor = font.fontDescriptor; NSFontDescriptor* UIFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:NSFontDescriptorTraitUIOptimized]; @@ -689,15 +692,14 @@ static CGFloat getLineHeight(NSFont* font, BOOL vertical) { } - (instancetype)init { - self = [super init]; - if (self) { + if (self = [super init]) { NSMutableParagraphStyle* candidateParagraphStyle = NSMutableParagraphStyle.alloc.init; candidateParagraphStyle.alignment = NSTextAlignmentLeft; candidateParagraphStyle.lineBreakStrategy = NSLineBreakStrategyNone; - // Use left-to-right marks to declare the default writing direction and - // prevent strong right-to-left characters from setting the writing - // direction in case the label are direction-less symbols + /* Use left-to-right marks to declare the default writing direction and + prevent strong right-to-left characters from setting the writing + direction in case the label are direction-less symbols */ candidateParagraphStyle.baseWritingDirection = NSWritingDirectionLeftToRight; NSMutableParagraphStyle* preeditParagraphStyle = @@ -728,7 +730,7 @@ - (instancetype)init { textAttrs[NSFontAttributeName] = userFont; // Use left-to-right embedding to prevent right-to-left text from changing // the layout of the candidate. - textAttrs[NSWritingDirectionAttributeName] = @[ @(0) ]; + textAttrs[NSWritingDirectionAttributeName] = @[ @0 ]; textAttrs[NSParagraphStyleAttributeName] = candidateParagraphStyle; NSMutableDictionary* labelAttrs = @@ -747,13 +749,14 @@ - (instancetype)init { NSMutableDictionary.alloc.init; preeditAttrs[NSForegroundColorAttributeName] = NSColor.textColor; preeditAttrs[NSFontAttributeName] = userFont; - preeditAttrs[NSLigatureAttributeName] = @(0); + preeditAttrs[NSLigatureAttributeName] = @0; preeditAttrs[NSParagraphStyleAttributeName] = preeditParagraphStyle; NSMutableDictionary* pagingAttrs = NSMutableDictionary.alloc.init; pagingAttrs[NSFontAttributeName] = monoDigitFont; pagingAttrs[NSForegroundColorAttributeName] = NSColor.textColor; + pagingAttrs[NSParagraphStyleAttributeName] = pagingParagraphStyle; NSMutableDictionary* statusAttrs = commentAttrs.mutableCopy; @@ -795,14 +798,11 @@ - (instancetype)init { - (void)updateSeperatorAndSymbolAttrs { NSMutableDictionary* sepAttrs = _commentAttrs.mutableCopy; - sepAttrs[NSVerticalGlyphFormAttributeName] = @(NO); + sepAttrs[NSVerticalGlyphFormAttributeName] = @NO; _separator = [NSAttributedString.alloc initWithString:_linear ? (_tabular ? @"\u3000\t\x1D" : @"\u3000\x1D") : @"\n" attributes:sepAttrs]; - _fullWidthPlaceholder = - [NSAttributedString.alloc initWithString:kFullWidthSpace - attributes:_commentAttrs]; // Symbols for function buttons NSString* attmCharacter = [NSString stringWithCharacters:(unichar[1]){NSAttachmentCharacter} @@ -813,7 +813,7 @@ - (void)updateSeperatorAndSymbolAttrs { NSMutableDictionary* attrsDeleteFill = _preeditAttrs.mutableCopy; attrsDeleteFill[NSAttachmentAttributeName] = attmDeleteFill; - attrsDeleteFill[NSVerticalGlyphFormAttributeName] = @(NO); + attrsDeleteFill[NSVerticalGlyphFormAttributeName] = @NO; _symbolDeleteFill = [NSAttributedString.alloc initWithString:attmCharacter attributes:attrsDeleteFill]; @@ -822,7 +822,7 @@ - (void)updateSeperatorAndSymbolAttrs { NSMutableDictionary* attrsDeleteStroke = _preeditAttrs.mutableCopy; attrsDeleteStroke[NSAttachmentAttributeName] = attmDeleteStroke; - attrsDeleteStroke[NSVerticalGlyphFormAttributeName] = @(NO); + attrsDeleteStroke[NSVerticalGlyphFormAttributeName] = @NO; _symbolDeleteStroke = [NSAttributedString.alloc initWithString:attmCharacter attributes:attrsDeleteStroke]; @@ -925,7 +925,7 @@ - (void)updateLabelsWithConfig:(SquirrelConfig*)config addObjectsFromArray:[selectLabels subarrayWithRange:NSMakeRange(0, menuSize)]]; } - if (selectKeys) { + if (selectKeys != nil) { if (selectLabels.count == 0) { NSString* keyCaps = [selectKeys.uppercaseString stringByApplyingTransform:NSStringTransformFullwidthToHalfwidth @@ -969,7 +969,7 @@ - (void)setCandidateFormat:(NSString*)candidateFormat { } - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly { - NSMutableAttributedString* candTemplate; + NSMutableAttributedString* candidateTemplate; if (!attrsOnly) { // validate candidate format: must have enumerator '%c' before candidate // '%@' @@ -997,94 +997,93 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly { isSupersetOfSet:labelCharacters]) { // 01..9 if ((enumRange = [candidateFormat rangeOfString:@"%c\u20E3" options:NSLiteralSearch]) - .length > 0) { // 1︎⃣..9︎⃣0︎⃣ + .length > 0) { // 1︎⃣...9︎⃣0︎⃣ for (NSUInteger i = 0; i < labels.count; ++i) { labels[i] = [NSString - stringWithFormat:@"%S", (const unichar[3]){ - [labels[i] characterAtIndex:0] - - 0xFF10 + 0x0030, - 0xFE0E, 0x20E3}]; + stringWithFormat:@"%C\uFE0E\u20E3", + (unichar)([labels[i] characterAtIndex:0] - + 0xFF10 + 0x0030)]; } } else if ((enumRange = [candidateFormat rangeOfString:@"%c\u20DD" options:NSLiteralSearch]) - .length > 0) { // ①..⑨⓪ + .length > 0) { // ①...⑨⓪ for (NSUInteger i = 0; i < labels.count; ++i) { labels[i] = [NSString - stringWithFormat:@"%S", - (const unichar[1]){ - [labels[i] characterAtIndex:0] == 0xFF10 - ? 0x24EA - : [labels[i] characterAtIndex:0] - - 0xFF11 + 0x2460}]; + stringWithFormat:@"%C", + (unichar)([labels[i] characterAtIndex:0] == + 0xFF10 + ? 0x24EA + : [labels[i] characterAtIndex:0] - + 0xFF11 + 0x2460)]; } } else if ((enumRange = [candidateFormat rangeOfString:@"(%c)" options:NSLiteralSearch]) - .length > 0) { // ⑴..⑼⑽ + .length > 0) { // ⑴...⑼⑽ for (NSUInteger i = 0; i < labels.count; ++i) { labels[i] = [NSString - stringWithFormat:@"%S", - (const unichar[1]){ - [labels[i] characterAtIndex:0] == 0xFF10 - ? 0x247D - : [labels[i] characterAtIndex:0] - - 0xFF11 + 0x2474}]; + stringWithFormat:@"%C", + (unichar)([labels[i] characterAtIndex:0] == + 0xFF10 + ? 0x247D + : [labels[i] characterAtIndex:0] - + 0xFF11 + 0x2474)]; } } else if ((enumRange = [candidateFormat rangeOfString:@"%c." options:NSLiteralSearch]) - .length > 0) { // ⒈..⒐🄀 + .length > 0) { // ⒈...⒐🄀 for (NSUInteger i = 0; i < labels.count; ++i) { - labels[i] = [NSString - stringWithFormat:@"%S", - (const unichar[2]){ - [labels[i] characterAtIndex:0] == 0xFF10 - ? 0xD83C - : [labels[i] characterAtIndex:0] - - 0xFF11 + 0x2488, - [labels[i] characterAtIndex:0] == 0xFF10 - ? 0xDD00 - : 0x0}]; + labels[i] = + [labels[i] characterAtIndex:0] == 0xFF10 + ? @"\U0001F100" + : [NSString + stringWithFormat:@"%C", + (unichar)( + [labels[i] characterAtIndex:0] - + 0xFF11 + 0x2488)]; } } else if ((enumRange = [candidateFormat rangeOfString:@"%c," options:NSLiteralSearch]) - .length > 0) { // 🄂..🄊🄁 + .length > 0) { // 🄂...🄊🄁 for (NSUInteger i = 0; i < labels.count; ++i) { labels[i] = [NSString stringWithFormat:@"%S", (const unichar[2]){ - 0xD83C, [labels[i] characterAtIndex:0] - - 0xFF10 + 0xDD01}]; + 0xD83C, + (unichar)([labels[i] characterAtIndex:0] - + 0xFF10 + 0xDD01)}]; } } } else if ([[NSCharacterSet characterSetWithRange:NSMakeRange(0xFF21, 26)] isSupersetOfSet:labelCharacters]) { // A..Z if ((enumRange = [candidateFormat rangeOfString:@"%c\u20DD" options:NSLiteralSearch]) - .length > 0) { // Ⓐ..Ⓩ + .length > 0) { // Ⓐ...Ⓩ for (NSUInteger i = 0; i < labels.count; ++i) { labels[i] = [NSString - stringWithFormat:@"%S", (const unichar[1]){ - [labels[i] characterAtIndex:0] - - 0xFF21 + 0x24B6}]; + stringWithFormat:@"%C", (unichar)([labels[i] characterAtIndex:0] - + 0xFF21 + 0x24B6)]; } } else if ((enumRange = [candidateFormat rangeOfString:@"(%c)" options:NSLiteralSearch]) - .length > 0) { // 🄐..🄩 + .length > 0) { // 🄐...🄩 for (NSUInteger i = 0; i < labels.count; ++i) { labels[i] = [NSString stringWithFormat:@"%S", (const unichar[2]){ - 0xD83C, [labels[i] characterAtIndex:0] - - 0xFF21 + 0xDD10}]; + 0xD83C, + (unichar)([labels[i] characterAtIndex:0] - + 0xFF21 + 0xDD10)}]; } } else if ((enumRange = [candidateFormat rangeOfString:@"%c\u20DE" options:NSLiteralSearch]) - .length > 0) { // 🄰..🅉 + .length > 0) { // 🄰...🅉 for (NSUInteger i = 0; i < labels.count; ++i) { labels[i] = [NSString stringWithFormat:@"%S", (const unichar[2]){ - 0xD83C, [labels[i] characterAtIndex:0] - - 0xFF21 + 0xDD30}]; + 0xD83C, + (unichar)([labels[i] characterAtIndex:0] - + 0xFF21 + 0xDD30)}]; } } } @@ -1092,10 +1091,10 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly { [candidateFormat replaceCharactersInRange:enumRange withString:@"%c"]; _labels = labels; } - candTemplate = + candidateTemplate = [NSMutableAttributedString.alloc initWithString:candidateFormat]; } else { - candTemplate = _candidateTemplate.mutableCopy; + candidateTemplate = _candidateTemplate.mutableCopy; } // make sure label font can render all label strings NSString* labelString = [_labels componentsJoinedByString:@""]; @@ -1126,40 +1125,45 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly { } NSRange textRange = - [candTemplate.mutableString rangeOfString:@"%@" options:NSLiteralSearch]; + [candidateTemplate.mutableString rangeOfString:@"%@" + options:NSLiteralSearch]; NSRange labelRange = NSMakeRange(0, textRange.location); NSRange commentRange = NSMakeRange( - NSMaxRange(textRange), candTemplate.length - NSMaxRange(textRange)); - [candTemplate setAttributes:_labelAttrs range:labelRange]; - [candTemplate setAttributes:_textAttrs range:textRange]; + NSMaxRange(textRange), candidateTemplate.length - NSMaxRange(textRange)); + [candidateTemplate setAttributes:_labelAttrs range:labelRange]; + [candidateTemplate setAttributes:_textAttrs range:textRange]; if (commentRange.length > 0) { - [candTemplate setAttributes:_commentAttrs range:commentRange]; + [candidateTemplate setAttributes:_commentAttrs range:commentRange]; } // parse markdown formats if (!attrsOnly) { - [candTemplate formatMarkDown]; + [candidateTemplate formatMarkDown]; // add placeholder for comment '%s' - textRange = [candTemplate.mutableString rangeOfString:@"%@" - options:NSLiteralSearch]; + textRange = [candidateTemplate.mutableString rangeOfString:@"%@" + options:NSLiteralSearch]; labelRange = NSMakeRange(0, textRange.location); - commentRange = NSMakeRange(NSMaxRange(textRange), - candTemplate.length - NSMaxRange(textRange)); + commentRange = + NSMakeRange(NSMaxRange(textRange), + candidateTemplate.length - NSMaxRange(textRange)); if (commentRange.length > 0) { - [candTemplate replaceCharactersInRange:commentRange - withString:[kTipSpecifier - stringByAppendingString: - [candTemplate.mutableString - substringWithRange: - commentRange]]]; + [candidateTemplate + replaceCharactersInRange:commentRange + withString: + [kTipSpecifier + stringByAppendingString: + [candidateTemplate.mutableString + substringWithRange:commentRange]]]; } else { - [candTemplate appendAttributedString:[NSAttributedString.alloc - initWithString:kTipSpecifier - attributes:_commentAttrs]]; + [candidateTemplate + appendAttributedString:[NSAttributedString.alloc + initWithString:kTipSpecifier + attributes:_commentAttrs]]; } commentRange.length += kTipSpecifier.length; if (!_linear) { - [candTemplate replaceCharactersInRange:NSMakeRange(textRange.location, 0) - withString:@"\t"]; + [candidateTemplate + replaceCharactersInRange:NSMakeRange(textRange.location, 0) + withString:@"\t"]; labelRange.length += 1; textRange.location += 1; commentRange.location += 1; @@ -1170,7 +1174,7 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly { _candidateParagraphStyle.mutableCopy; if (!_linear) { CGFloat indent = 0.0; - NSAttributedString* labelFormat = [candTemplate + NSAttributedString* labelFormat = [candidateTemplate attributedSubstringFromRange:NSMakeRange(0, labelRange.length - 1)]; for (NSString* label in _labels) { NSMutableAttributedString* enumString = labelFormat.mutableCopy; @@ -1212,27 +1216,29 @@ - (void)updateCandidateFormatForAttributesOnly:(BOOL)attrsOnly { _commentAttrs = commentAttrs; _labelAttrs = labelAttrs; - [candTemplate addAttribute:NSParagraphStyleAttributeName - value:candidateParagraphStyle - range:NSMakeRange(0, candTemplate.length)]; - _candidateTemplate = candTemplate; - NSMutableAttributedString* candHilitedTemplate = candTemplate.mutableCopy; - [candHilitedTemplate addAttribute:NSForegroundColorAttributeName - value:_hilitedLabelForeColor - range:labelRange]; - [candHilitedTemplate addAttribute:NSForegroundColorAttributeName - value:_hilitedTextForeColor - range:textRange]; - [candHilitedTemplate addAttribute:NSForegroundColorAttributeName - value:_hilitedCommentForeColor - range:commentRange]; - _candidateHilitedTemplate = candHilitedTemplate; + [candidateTemplate addAttribute:NSParagraphStyleAttributeName + value:candidateParagraphStyle + range:NSMakeRange(0, candidateTemplate.length)]; + _candidateTemplate = candidateTemplate; + NSMutableAttributedString* candidateHilitedTemplate = + candidateTemplate.mutableCopy; + [candidateHilitedTemplate addAttribute:NSForegroundColorAttributeName + value:_hilitedLabelForeColor + range:labelRange]; + [candidateHilitedTemplate addAttribute:NSForegroundColorAttributeName + value:_hilitedTextForeColor + range:textRange]; + [candidateHilitedTemplate addAttribute:NSForegroundColorAttributeName + value:_hilitedCommentForeColor + range:commentRange]; + _candidateHilitedTemplate = candidateHilitedTemplate; if (_tabular) { - NSMutableAttributedString* candDimmedTemplate = candTemplate.mutableCopy; - [candDimmedTemplate addAttribute:NSForegroundColorAttributeName - value:_dimmedLabelForeColor - range:labelRange]; - _candidateDimmedTemplate = candDimmedTemplate; + NSMutableAttributedString* candidateDimmedTemplate = + candidateTemplate.mutableCopy; + [candidateDimmedTemplate addAttribute:NSForegroundColorAttributeName + value:_dimmedLabelForeColor + range:labelRange]; + _candidateDimmedTemplate = candidateDimmedTemplate; } } @@ -1298,24 +1304,24 @@ static void updateTextOrientation(BOOL* isVertical, } // functions for post-retrieve processing -static double inline positive(double param) { +static inline double positive(double param) { return param > 0.0 ? param : 0.0; } -static double inline pos_round(double param) { +static inline double pos_round(double param) { return param > 0.0 ? round(param) : 0.0; } -static double inline pos_ceil(double param) { +static inline double pos_ceil(double param) { return param > 0.0 ? ceil(param) : 0.0; } -static double inline clamp_uni(double param) { +static inline double clamp_uni(double param) { return param > 0.0 ? (param < 1.0 ? param : 1.0) : 0.0; } - (void)updateWithConfig:(SquirrelConfig*)config styleOptions:(NSSet*)styleOptions scriptVariant:(NSString*)scriptVariant - forAppearance:(SquirrelAppear)appear { - // INTERFACE + forAppearance:(SquirrelAppearance)appearance { + /*** INTERFACE ***/ BOOL linear = NO; BOOL tabular = NO; BOOL vertical = NO; @@ -1332,7 +1338,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config [config getStringForOption:@"style/status_message_type"]; NSString* candidateFormat = [config getStringForOption:@"style/candidate_format"]; - // TYPOGRAPHY + /*** TYPOGRAPHY ***/ NSString* fontName = [config getStringForOption:@"style/font_face"]; NSNumber* fontSize = [config getOptionalDoubleForOption:@"style/font_point" applyConstraint:pos_round]; @@ -1373,7 +1379,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config [config getOptionalDoubleForOption:@"style/base_offset"]; NSNumber* lineLength = [config getOptionalDoubleForOption:@"style/line_length"]; - // CHROMATICS + /*** CHROMATICS ***/ NSColor* backColor; NSColor* borderColor; NSColor* preeditBackColor; @@ -1390,24 +1396,24 @@ - (void)updateWithConfig:(SquirrelConfig*)config NSImage* backImage; NSString* colorScheme; - if (appear == darkAppear) { + if (appearance == kDarkAppearance) { for (NSString* option in styleOptions) { if ((colorScheme = [config getStringForOption: [NSString stringWithFormat:@"style/%@/color_scheme_dark", - option]])) { + option]]) != nil) { break; } } colorScheme = colorScheme ?: [config getStringForOption:@"style/color_scheme_dark"]; } - if (!colorScheme) { + if (colorScheme == nil) { for (NSString* option in styleOptions) { if ((colorScheme = [config getStringForOption:[NSString stringWithFormat:@"style/%@/color_scheme", - option]])) { + option]]) != nil) { break; } } @@ -1427,7 +1433,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config // get color scheme and then check possible overrides from styleSwitcher for (NSString* prefix in configPrefixes) { - // CHROMATICS override + /*** CHROMATICS override ***/ config.colorSpace = [config getStringForOption:[prefix stringByAppendingString:@"/color_space"]] @@ -1492,9 +1498,10 @@ - (void)updateWithConfig:(SquirrelConfig*)config getImageForOption:[prefix stringByAppendingString:@"/back_image"]] ?: backImage; - // the following per-color-scheme configurations, if exist, will - // override configurations with the same name under the global 'style' - // section INTERFACE override + /* the following per-color-scheme configurations, if exist, will + override configurations with the same name under the global 'style' + section */ + /*** INTERFACE override ***/ updateCandidateListLayout(&linear, &tabular, config, prefix); updateTextOrientation(&vertical, config, prefix); inlinePreedit = @@ -1520,7 +1527,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config [config getStringForOption: [prefix stringByAppendingString:@"/candidate_format"]] ?: candidateFormat; - // TYPOGRAPHY override + /*** TYPOGRAPHY override ***/ fontName = [config getStringForOption:[prefix stringByAppendingString:@"/font_face"]] @@ -1596,7 +1603,7 @@ - (void)updateWithConfig:(SquirrelConfig*)config ?: lineLength; } - // TYPOGRAPHY refinement + /*** TYPOGRAPHY refinement ***/ fontSize = fontSize ?: @(kDefaultFontSize); labelFontSize = labelFontSize ?: fontSize; commentFontSize = commentFontSize ?: fontSize; @@ -1647,8 +1654,8 @@ - (void)updateWithConfig:(SquirrelConfig*)config CGFloat fullWidth = ceil( [kFullWidthSpace sizeWithAttributes:@{NSFontAttributeName : commentFont}] .width); - spacing = spacing ?: @(0.0); - lineSpacing = lineSpacing ?: @(0.0); + spacing = spacing ?: @0; + lineSpacing = lineSpacing ?: @0; NSMutableParagraphStyle* preeditParagraphStyle = _preeditParagraphStyle.mutableCopy; @@ -1659,8 +1666,6 @@ - (void)updateWithConfig:(SquirrelConfig*)config NSMutableParagraphStyle* candidateParagraphStyle = _candidateParagraphStyle.mutableCopy; - candidateParagraphStyle.alignment = - linear ? NSTextAlignmentNatural : NSTextAlignmentLeft; candidateParagraphStyle.minimumLineHeight = lineHeight; candidateParagraphStyle.maximumLineHeight = lineHeight; candidateParagraphStyle.paragraphSpacingBefore = @@ -1718,14 +1723,15 @@ - (void)updateWithConfig:(SquirrelConfig*)config NSDictionary* baselineRefInfo = @{ (id)kCTBaselineReferenceFont : vertical ? refFont.verticalFont : refFont, (id)kCTBaselineClassIdeographicCentered : - @(vertical ? 0.0 : refFont.ascender * 0.5 + refFont.descender * 0.5), - (id)kCTBaselineClassRoman : - @(vertical ? -refFont.verticalFont.ascender * 0.5 - - refFont.verticalFont.descender * 0.5 - : 0.0), + @(vertical ? 0.0 : (refFont.ascender + refFont.descender) * 0.5), + (id)kCTBaselineClassRoman : @(vertical ? -(refFont.verticalFont.ascender + + refFont.verticalFont.descender) * + 0.5 + : 0.0), (id)kCTBaselineClassIdeographicLow : - @(vertical ? refFont.verticalFont.descender * 0.5 - - refFont.verticalFont.ascender * 0.5 + @(vertical ? (refFont.verticalFont.descender - + refFont.verticalFont.ascender) * + 0.5 : refFont.descender) }; @@ -1776,28 +1782,28 @@ - (void)updateWithConfig:(SquirrelConfig*)config statusAttrs[NSParagraphStyleAttributeName] = statusParagraphStyle; labelAttrs[NSVerticalGlyphFormAttributeName] = @(vertical); - pagingAttrs[NSVerticalGlyphFormAttributeName] = @(NO); + pagingAttrs[NSVerticalGlyphFormAttributeName] = @NO; - // CHROMATICS refinement - translucency = translucency ?: @(0.0); + /*** CHROMATICS refinement ***/ + translucency = translucency ?: @0; if (@available(macOS 10.14, *)) { - if (translucency.doubleValue > 0.001 && !isNative && backColor != nil && - (appear == darkAppear ? backColor.luminanceComponent > 0.65 - : backColor.luminanceComponent < 0.55)) { + if (translucency.floatValue > 0.001f && !isNative && backColor != nil && + (appearance == kDarkAppearance ? backColor.lStarComponent > 0.6 + : backColor.lStarComponent < 0.4)) { backColor = - [backColor colorByInvertingLuminanceToExtent:kDefaultColorInversion]; + [backColor colorByInvertingLuminanceToExtent:kStandardColorInversion]; borderColor = [borderColor - colorByInvertingLuminanceToExtent:kDefaultColorInversion]; + colorByInvertingLuminanceToExtent:kStandardColorInversion]; preeditBackColor = [preeditBackColor - colorByInvertingLuminanceToExtent:kDefaultColorInversion]; + colorByInvertingLuminanceToExtent:kStandardColorInversion]; preeditForeColor = [preeditForeColor - colorByInvertingLuminanceToExtent:kDefaultColorInversion]; + colorByInvertingLuminanceToExtent:kStandardColorInversion]; textForeColor = [textForeColor - colorByInvertingLuminanceToExtent:kDefaultColorInversion]; + colorByInvertingLuminanceToExtent:kStandardColorInversion]; commentForeColor = [commentForeColor - colorByInvertingLuminanceToExtent:kDefaultColorInversion]; + colorByInvertingLuminanceToExtent:kStandardColorInversion]; labelForeColor = [labelForeColor - colorByInvertingLuminanceToExtent:kDefaultColorInversion]; + colorByInvertingLuminanceToExtent:kStandardColorInversion]; hilitedPreeditBackColor = [hilitedPreeditBackColor colorByInvertingLuminanceToExtent:kModerateColorInversion]; hilitedPreeditForeColor = [hilitedPreeditForeColor @@ -1851,6 +1857,9 @@ - (void)updateWithConfig:(SquirrelConfig*)config pagingAttrs[NSForegroundColorAttributeName] = preeditForeColor; statusAttrs[NSForegroundColorAttributeName] = commentForeColor; + _borderInsets = + vertical ? NSMakeSize(borderHeight.doubleValue, borderWidth.doubleValue) + : NSMakeSize(borderWidth.doubleValue, borderHeight.doubleValue); _cornerRadius = fmin(cornerRadius.doubleValue, lineHeight * 0.5); _hilitedCornerRadius = fmin(hilitedCornerRadius.doubleValue, lineHeight * 0.5); @@ -1858,13 +1867,10 @@ - (void)updateWithConfig:(SquirrelConfig*)config _linespace = lineSpacing.doubleValue; _preeditLinespace = spacing.doubleValue; _opacity = opacity ? opacity.doubleValue : 1.0; - _translucency = translucency.doubleValue; _lineLength = lineLength.doubleValue > 0.1 ? fmax(ceil(lineLength.doubleValue), fullWidth * 5) : 0.0; - _borderInsets = - vertical ? NSMakeSize(borderHeight.doubleValue, borderWidth.doubleValue) - : NSMakeSize(borderWidth.doubleValue, borderHeight.doubleValue); + _translucency = translucency.floatValue; _showPaging = showPaging.boolValue; _rememberSize = rememberSize.boolValue; _tabular = tabular; @@ -1940,26 +1946,27 @@ - (void)setAnnotationHeight:(CGFloat)height { _commentAttrs = commentAttrs; _labelAttrs = labelAttrs; - NSMutableAttributedString* candTemplate = _candidateTemplate.mutableCopy; - [candTemplate addAttribute:NSParagraphStyleAttributeName - value:candidateParagraphStyle - range:NSMakeRange(0, candTemplate.length)]; - _candidateTemplate = candTemplate; - NSMutableAttributedString* candHilitedTemplate = + NSMutableAttributedString* candidateTemplate = + _candidateTemplate.mutableCopy; + [candidateTemplate addAttribute:NSParagraphStyleAttributeName + value:candidateParagraphStyle + range:NSMakeRange(0, candidateTemplate.length)]; + _candidateTemplate = candidateTemplate; + NSMutableAttributedString* candidateHilitedTemplate = _candidateHilitedTemplate.mutableCopy; - [candHilitedTemplate + [candidateHilitedTemplate addAttribute:NSParagraphStyleAttributeName value:candidateParagraphStyle - range:NSMakeRange(0, candHilitedTemplate.length)]; - _candidateHilitedTemplate = candHilitedTemplate; + range:NSMakeRange(0, candidateHilitedTemplate.length)]; + _candidateHilitedTemplate = candidateHilitedTemplate; if (_tabular) { - NSMutableAttributedString* candDimmedTemplate = + NSMutableAttributedString* candidateDimmedTemplate = _candidateDimmedTemplate.mutableCopy; - [candDimmedTemplate + [candidateDimmedTemplate addAttribute:NSParagraphStyleAttributeName value:candidateParagraphStyle - range:NSMakeRange(0, candDimmedTemplate.length)]; - _candidateDimmedTemplate = candDimmedTemplate; + range:NSMakeRange(0, candidateDimmedTemplate.length)]; + _candidateDimmedTemplate = candidateDimmedTemplate; } } } @@ -2028,6 +2035,17 @@ - (void)setScriptVariant:(NSString*)scriptVariant { __attribute__((objc_direct_members)) @interface SquirrelLayoutManager : NSLayoutManager + +typedef NS_CLOSED_ENUM(NSUInteger, SquirrelContentBlock) { + kPreeditBlock, + kLinearCandidatesBlock, + kStackedCandidatesBlock, + kPagingBlock, + kStatusBlock +}; + +@property(nonatomic) SquirrelContentBlock contentBlock; + @end @implementation SquirrelLayoutManager @@ -2039,7 +2057,8 @@ - (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(NSPoint)origin { [self textContainerForGlyphAtIndex:glyphsToShow.location effectiveRange:NULL withoutAdditionalLayout:YES]; - BOOL verticalOrientation = (BOOL)textContainer.layoutOrientation; + BOOL verticalOrientation = + textContainer.layoutOrientation == NSTextLayoutOrientationVertical; CGContextRef context = NSGraphicsContext.currentContext.CGContext; CGContextResetClip(context); [self.textStorage @@ -2057,7 +2076,7 @@ - (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(NSPoint)origin { effectiveRange:NULL withoutAdditionalLayout:YES]; CGContextSaveGState(context); - if (attrs[(id)kCTRubyAnnotationAttributeName]) { + if (attrs[(id)kCTRubyAnnotationAttributeName] != nil) { CGContextScaleCTM(context, 1.0, -1.0); NSUInteger glyphIndex = glyphRange.location; CTLineRef line = CTLineCreateWithAttributedString( @@ -2113,10 +2132,9 @@ - (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(NSPoint)origin { NSFont* refFont = attrs[(id)kCTBaselineReferenceInfoAttributeName] [(id)kCTBaselineReferenceFont]; - offset.y += runFont.ascender * 0.5 + - runFont.descender * 0.5 - - refFont.ascender * 0.5 - - refFont.descender * 0.5; + offset.y += (runFont.ascender + runFont.descender - + refFont.ascender - refFont.descender) * + 0.5; } else if (verticalOrientation && runFont.pointSize < 24 && [runFont.fontName @@ -2159,7 +2177,8 @@ - (BOOL)layoutManager:(NSLayoutManager*)layoutManager inTextContainer:(NSTextContainer*)textContainer forGlyphRange:(NSRange)glyphRange { BOOL didModify = NO; - BOOL verticalOrientation = (BOOL)textContainer.layoutOrientation; + BOOL verticalOrientation = + textContainer.layoutOrientation == NSTextLayoutOrientationVertical; NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; NSParagraphStyle* rulerAttrs = @@ -2174,7 +2193,7 @@ - (BOOL)layoutManager:(NSLayoutManager*)layoutManager attribute:(id)kCTBaselineReferenceInfoAttributeName atIndex:charRange.location effectiveRange:NULL][(id)kCTBaselineReferenceFont]; - baseline += refFont.ascender * 0.5 + refFont.descender * 0.5; + baseline += (refFont.ascender + refFont.descender) * 0.5; } CGFloat lineHeightDelta = lineFragmentUsedRect->size.height - lineHeight - lineSpacing; @@ -2185,10 +2204,6 @@ - (BOOL)layoutManager:(NSLayoutManager*)layoutManager round(lineFragmentRect->size.height - lineHeightDelta); didModify |= YES; } - // move half of the linespacing above the line fragment - if (lineSpacing > 0.1) { - baseline += lineSpacing * 0.5; - } CGFloat newBaselineOffset = floor(lineFragmentUsedRect->origin.y - lineFragmentRect->origin.y + baseline); if (fabs(*baselineOffset - newBaselineOffset) > 0.1) { @@ -2205,15 +2220,8 @@ - (BOOL)layoutManager:(NSLayoutManager*)layoutManager } else { unichar charBeforeIndex = [layoutManager.textStorage.mutableString characterAtIndex:charIndex - 1]; - NSTextAlignment alignment = - [[layoutManager.textStorage attribute:NSParagraphStyleAttributeName - atIndex:charIndex - effectiveRange:NULL] alignment]; - if (alignment == NSTextAlignmentNatural) { // candidates in linear layout - return charBeforeIndex == 0x1D; - } else { - return charBeforeIndex != '\t'; - } + return _contentBlock == kLinearCandidatesBlock ? charBeforeIndex == 0x1D + : charBeforeIndex != '\t'; } } @@ -2246,7 +2254,7 @@ - (NSRect)layoutManager:(NSLayoutManager*)layoutManager [layoutManager.textStorage attribute:(id)kCTRubyAnnotationAttributeName atIndex:charIndex - 1 effectiveRange:&rubyRange]; - if (rubyAnnotation) { + if (rubyAnnotation != nil) { NSAttributedString* rubyString = [layoutManager.textStorage attributedSubstringFromRange:rubyRange]; CTLineRef line = @@ -2256,7 +2264,8 @@ - (NSRect)layoutManager:(NSLayoutManager*)layoutManager width = fdim(rubyRect.size.width, rubyString.size.width); } } - return NSMakeRect(glyphPosition.x, 0.0, width, glyphPosition.y); + return NSMakeRect(glyphPosition.x, glyphPosition.y, width, + NSMaxY(proposedRect) - glyphPosition.y); } @end // SquirrelLayoutManager @@ -2265,15 +2274,10 @@ - (NSRect)layoutManager:(NSLayoutManager*)layoutManager API_AVAILABLE(macos(12.0)) @interface SquirrelTextLayoutFragment : NSTextLayoutFragment - -@property(nonatomic) CGFloat topMargin; - @end @implementation SquirrelTextLayoutFragment -@synthesize topMargin; - - (void)drawAtPoint:(CGPoint)point inContext:(CGContextRef)context { if (@available(macOS 14.0, *)) { } else { // in macOS 12 and 13, textLineFragments.typographicBouonds are in @@ -2282,25 +2286,22 @@ - (void)drawAtPoint:(CGPoint)point inContext:(CGContextRef)context { point.y -= self.layoutFragmentFrame.origin.y; } BOOL verticalOrientation = - (BOOL)self.textLayoutManager.textContainer.layoutOrientation; + self.textLayoutManager.textContainer.layoutOrientation == + NSTextLayoutOrientationVertical; for (NSTextLineFragment* lineFrag in self.textLineFragments) { CGRect lineRect = CGRectOffset(lineFrag.typographicBounds, point.x, point.y); - CGFloat lineSpacing = - [[lineFrag.attributedString attribute:NSParagraphStyleAttributeName - atIndex:lineFrag.characterRange.location - effectiveRange:NULL] lineSpacing]; - CGFloat baseline = CGRectGetMidY(lineRect) - lineSpacing * 0.5; + CGFloat baseline = CGRectGetMidY(lineRect); if (!verticalOrientation) { NSFont* refFont = [lineFrag.attributedString attribute:(id)kCTBaselineReferenceInfoAttributeName atIndex:lineFrag.characterRange.location effectiveRange:NULL][(id)kCTBaselineReferenceFont]; - baseline += refFont.ascender * 0.5 + refFont.descender * 0.5; + baseline += (refFont.ascender + refFont.descender) * 0.5; } CGPoint renderOrigin = CGPointMake(NSMinX(lineRect) + lineFrag.glyphOrigin.x, - ceil(baseline) - lineFrag.glyphOrigin.y); + floor(baseline) - lineFrag.glyphOrigin.y); CGPoint deviceOrigin = CGContextConvertPointToDeviceSpace(context, renderOrigin); renderOrigin = CGContextConvertPointToUserSpace( @@ -2314,6 +2315,9 @@ - (void)drawAtPoint:(CGPoint)point inContext:(CGContextRef)context { __attribute__((objc_direct_members)) API_AVAILABLE(macos(12.0)) @interface SquirrelTextLayoutManager : NSTextLayoutManager + +@property(nonatomic) SquirrelContentBlock contentBlock; + @end @implementation SquirrelTextLayoutManager @@ -2322,7 +2326,7 @@ - (BOOL)textLayoutManager:(NSTextLayoutManager*)textLayoutManager shouldBreakLineBeforeLocation:(id)location hyphenating:(BOOL)hyphenating { NSTextContentStorage* contentStorage = - textLayoutManager.textContainer.textView.textContentStorage; + (NSTextContentStorage*)textLayoutManager.textContentManager; NSUInteger charIndex = (NSUInteger) [contentStorage offsetFromLocation:contentStorage.documentRange.location toLocation:location]; @@ -2331,15 +2335,8 @@ - (BOOL)textLayoutManager:(NSTextLayoutManager*)textLayoutManager } else { unichar charBeforeIndex = [contentStorage.textStorage.mutableString characterAtIndex:charIndex - 1]; - NSTextAlignment alignment = - [[contentStorage.textStorage attribute:NSParagraphStyleAttributeName - atIndex:charIndex - effectiveRange:NULL] alignment]; - if (alignment == NSTextAlignmentNatural) { // candidates in linear layout - return charBeforeIndex == 0x1D; - } else { - return charBeforeIndex != '\t'; - } + return _contentBlock == kLinearCandidatesBlock ? charBeforeIndex == 0x1D + : charBeforeIndex != '\t'; } } @@ -2353,14 +2350,6 @@ - (NSTextLayoutFragment*)textLayoutManager: SquirrelTextLayoutFragment* fragment = [SquirrelTextLayoutFragment.alloc initWithTextElement:textElement range:textRange]; - NSTextStorage* textStorage = - textLayoutManager.textContainer.textView.textContentStorage.textStorage; - if (textStorage.length > 0 && - [location isEqual:self.documentRange.location]) { - fragment.topMargin = [[textStorage attribute:NSParagraphStyleAttributeName - atIndex:0 - effectiveRange:NULL] lineSpacing]; - } return fragment; } @@ -2368,13 +2357,25 @@ - (NSTextLayoutFragment*)textLayoutManager: #pragma mark - View behind text, containing drawings of backgrounds and highlights +__attribute__((objc_direct_members)) +@interface NSFlippedView : NSView +@end + +@implementation NSFlippedView + +- (BOOL)isFlipped { + return YES; +} + +@end + __attribute__((objc_direct_members)) @interface SquirrelView : NSView typedef struct { - NSRect leading; + NSRect head; NSRect body; - NSRect trailing; + NSRect tail; } SquirrelTextPolygon; typedef struct { @@ -2398,7 +2399,15 @@ @interface SquirrelView : NSView API_AVAILABLE(macosx(10.14)) SquirrelTheme* darkTheme; @property(nonatomic, readonly, strong, nonnull) SquirrelTheme* currentTheme; @property(nonatomic, readonly, strong, nonnull) NSTextView* textView; -@property(nonatomic, readonly, strong, nonnull) NSTextStorage* textStorage; +@property(nonatomic, readonly, strong, nonnull) NSTextView* preeditView; +@property(nonatomic, readonly, strong, nonnull) NSTextView* pagingView; +@property(nonatomic, readonly, strong, nonnull) NSTextView* statusView; +@property(nonatomic, readonly, strong, nonnull) NSScrollView* scrollView; +@property(nonatomic, readonly, strong, nonnull) NSFlippedView* documentView; +@property(nonatomic, readonly, strong, nonnull) NSTextStorage* contents; +@property(nonatomic, readonly, strong, nonnull) NSTextStorage* preeditContents; +@property(nonatomic, readonly, strong, nonnull) NSTextStorage* pagingContents; +@property(nonatomic, readonly, strong, nonnull) NSTextStorage* statusContents; @property(nonatomic, readonly, strong, nonnull) CAShapeLayer* shape; @property(nonatomic, readonly, nullable) SquirrelTabularIndex* tabularIndices; @property(nonatomic, readonly, nullable) SquirrelTextPolygon* candidatePolygons; @@ -2406,63 +2415,69 @@ @interface SquirrelView : NSView @property(nonatomic, readonly, nullable) SquirrelCandidateRanges* candidateRanges; @property(nonatomic, readonly, nullable) BOOL* truncated; +@property(nonatomic, readonly) NSRect screen; @property(nonatomic, readonly) NSRect contentRect; -@property(nonatomic, readonly) NSRect preeditBlock; -@property(nonatomic, readonly) NSRect candidateBlock; -@property(nonatomic, readonly) NSRect pagingBlock; +@property(nonatomic, readonly) NSRect documentRect; +@property(nonatomic, readonly) NSRect preeditRect; +@property(nonatomic, readonly) NSRect candidatesRect; +@property(nonatomic, readonly) NSRect pagingRect; @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) CGFloat clippedHeight; +@property(nonatomic, readonly) SquirrelAppearance appear; +@property(nonatomic, readonly) SquirrelIndex cursorIndex; @property(nonatomic, readonly) SquirrelIndex functionButton; -@property(nonatomic, readonly) NSEdgeInsets marginInsets; @property(nonatomic, readonly) NSUInteger candidateCount; @property(nonatomic, readonly) NSUInteger hilitedIndex; -@property(nonatomic, readonly) NSRange preeditRange; @property(nonatomic, readonly) NSRange hilitedPreeditRange; -@property(nonatomic, readonly) NSRange pagingRange; -@property(nonatomic, readonly) CGFloat trailPadding; @property(nonatomic) BOOL expanded; +- (void)estimateBoundsOnScreen:(NSRect)screen + withPreedit:(BOOL)hasPreedit + candidates:(SquirrelCandidateRanges*)candidateRanges + truncation:(BOOL*)truncated + count:(NSUInteger)candidateCount + paging:(BOOL)hasPaging; - (void)layoutContents; - -- (NSRect)blockRectForRange:(NSRange)range; - -- (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange; - -- (void)estimateBoundsForPreedit:(NSRange)preeditRange - candidates:(SquirrelCandidateRanges*)candidateRanges - truncation:(BOOL*)truncated - count:(NSUInteger)candidateCount - paging:(NSRange)pagingRange; - -- (void)drawViewWithInsets:(NSEdgeInsets)marginInsets - hilitedIndex:(NSUInteger)hilitedIndex - hilitedPreeditRange:(NSRange)hilitedPreeditRange; - -- (void)setPreeditRange:(NSRange)preeditRange - hilitedPreeditRange:(NSRange)hilitedPreeditRange; - +- (NSRect)blockRectForRange:(NSRange)charRange inView:(NSTextView*)view; +- (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange + inView:(NSTextView*)view; +- (void)drawViewWithHilitedIndex:(NSUInteger)hilitedIndex + hilitedPreeditRange:(NSRange)hilitedPreeditRange; +- (void)setHilitedPreeditRange:(NSRange)hilitedPreeditRange; - (void)highlightCandidate:(NSUInteger)hilitedIndex; - - (void)highlightFunctionButton:(SquirrelIndex)functionButton; - - (SquirrelIndex)getIndexFromMouseSpot:(NSPoint)spot; @end @implementation SquirrelView +static inline NSUInteger NSMaxRange(SquirrelCandidateRanges ranges) { + return ranges.location + ranges.length; +} +static inline NSRange RangeCandidate(SquirrelCandidateRanges ranges) { + return NSMakeRange(ranges.location, ranges.length); +} +static inline NSRange RangeLabel(SquirrelCandidateRanges ranges) { + return NSMakeRange(ranges.location, ranges.text); +} +static inline NSRange RangeText(SquirrelCandidateRanges ranges) { + return NSMakeRange(ranges.location + ranges.text, + ranges.comment - ranges.text); +} +static inline NSRange RangeComment(SquirrelCandidateRanges ranges) { + return NSMakeRange(ranges.location + ranges.comment, + ranges.length - ranges.comment); +} + static SquirrelTheme* _defaultTheme = SquirrelTheme.alloc.init; static SquirrelTheme* _darkTheme API_AVAILABLE(macos(10.14)) = SquirrelTheme.alloc.init; -NS_INLINE NSUInteger NSMaxRange(SquirrelCandidateRanges ranges) { - return (ranges.location + ranges.length); -} - -// Need flipped coordinate system, as required by textStorage +// Need flipped coordinate system, consistent with textView and textContainer - (BOOL)isFlipped { return YES; } @@ -2471,12 +2486,15 @@ - (BOOL)wantsUpdateLayer { return YES; } -- (void)setAppear:(SquirrelAppear)appear { +- (void)setAppear:(SquirrelAppearance)appear { if (@available(macOS 10.14, *)) { if (_appear != appear) { _appear = appear; - [self setValue:appear == darkAppear ? _darkTheme : _defaultTheme + [self setValue:appear == kDarkAppearance ? _darkTheme : _defaultTheme forKey:@"currentTheme"]; + [self setValue:appear == kDarkAppearance ? @(NSScrollerKnobStyleLight) + : @(NSScrollerKnobStyleDark) + forKeyPath:@"scrollView.scrollerKnobStyle"]; } } } @@ -2489,114 +2507,251 @@ + (SquirrelTheme*)darkTheme API_AVAILABLE(macos(10.14)) { return _darkTheme; } -- (instancetype)initWithFrame:(NSRect)frameRect { - self = [super initWithFrame:frameRect]; - if (self) { +static NSTextView* setupTextViewForContentBlock( + SquirrelContentBlock contentBlock, + NSTextStorage* textStorage) { + NSTextContainer* textContainer = + [NSTextContainer.alloc initWithSize:NSZeroSize]; + textContainer.lineFragmentPadding = 0; + if (@available(macOS 12.0, *)) { + SquirrelTextLayoutManager* textLayoutManager = + SquirrelTextLayoutManager.alloc.init; + textLayoutManager.contentBlock = contentBlock; + textLayoutManager.usesFontLeading = NO; + textLayoutManager.usesHyphenation = NO; + textLayoutManager.delegate = textLayoutManager; + textLayoutManager.textContainer = textContainer; + NSTextContentStorage* contentStorage = NSTextContentStorage.alloc.init; + [contentStorage addTextLayoutManager:textLayoutManager]; + contentStorage.textStorage = textStorage; + } else { + SquirrelLayoutManager* layoutManager = SquirrelLayoutManager.alloc.init; + layoutManager.contentBlock = contentBlock; + layoutManager.backgroundLayoutEnabled = YES; + layoutManager.usesFontLeading = NO; + layoutManager.typesetterBehavior = NSTypesetterLatestBehavior; + layoutManager.delegate = layoutManager; + [layoutManager addTextContainer:textContainer]; + [textStorage addLayoutManager:layoutManager]; + } + NSTextView* textView = [NSTextView.alloc initWithFrame:NSZeroRect + textContainer:textContainer]; + textView.drawsBackground = NO; + textView.selectable = NO; + textView.wantsLayer = YES; + textView.layer.geometryFlipped = YES; + textView.layerContentsRedrawPolicy = + NSViewLayerContentsRedrawOnSetNeedsDisplay; + textView.clipsToBounds = NO; + return textView; +} + +- (instancetype)init { + if (self = [super init]) { self.wantsLayer = YES; self.layer.geometryFlipped = YES; self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay; - if (@available(macOS 12.0, *)) { - SquirrelTextLayoutManager* textLayoutManager = - SquirrelTextLayoutManager.alloc.init; - textLayoutManager.usesFontLeading = NO; - textLayoutManager.usesHyphenation = NO; - textLayoutManager.delegate = textLayoutManager; - NSTextContainer* textContainer = - [NSTextContainer.alloc initWithSize:NSZeroSize]; - textContainer.lineFragmentPadding = 0; - textLayoutManager.textContainer = textContainer; - NSTextContentStorage* contentStorage = NSTextContentStorage.alloc.init; - _textStorage = contentStorage.textStorage; - [contentStorage addTextLayoutManager:textLayoutManager]; - _textView = [NSTextView.alloc initWithFrame:frameRect - textContainer:textContainer]; - } else { - SquirrelLayoutManager* layoutManager = SquirrelLayoutManager.alloc.init; - layoutManager.backgroundLayoutEnabled = YES; - layoutManager.usesFontLeading = NO; - layoutManager.typesetterBehavior = NSTypesetterLatestBehavior; - layoutManager.delegate = layoutManager; - NSTextContainer* textContainer = - [NSTextContainer.alloc initWithContainerSize:NSZeroSize]; - textContainer.lineFragmentPadding = 0; - [layoutManager addTextContainer:textContainer]; - _textStorage = NSTextStorage.alloc.init; - [_textStorage addLayoutManager:layoutManager]; - _textView = [NSTextView.alloc initWithFrame:frameRect - textContainer:textContainer]; - } - _textView.drawsBackground = NO; - _textView.selectable = NO; - _textView.wantsLayer = YES; - - _appear = defaultAppear; + _contents = NSTextStorage.alloc.init; + _textView = + setupTextViewForContentBlock(kStackedCandidatesBlock, _contents); + _preeditContents = NSTextStorage.alloc.init; + _preeditView = + setupTextViewForContentBlock(kPreeditBlock, _preeditContents); + _pagingContents = NSTextStorage.alloc.init; + _pagingView = setupTextViewForContentBlock(kPagingBlock, _pagingContents); + _statusContents = NSTextStorage.alloc.init; + _statusView = setupTextViewForContentBlock(kStatusBlock, _statusContents); + + _documentView = NSFlippedView.alloc.init; + _documentView.wantsLayer = YES; + [_documentView addSubview:_textView]; + _scrollView = NSScrollView.alloc.init; + _scrollView.documentView = _documentView; + _scrollView.drawsBackground = NO; + _scrollView.automaticallyAdjustsContentInsets = NO; + _scrollView.hasVerticalScroller = YES; + _scrollView.scrollerStyle = NSScrollerStyleOverlay; + _scrollView.scrollerKnobStyle = NSScrollerKnobStyleDark; + + _appear = kDefaultAppearance; _currentTheme = _defaultTheme; _shape = CAShapeLayer.alloc.init; } return self; } -- (NSTextRange*)getTextRangeFromCharRange:(NSRange)charRange +- (NSTextRange*)textRangeFromCharRange:(NSRange)charRange + inView:(NSTextView*)view API_AVAILABLE(macos(12.0)) { if (charRange.location == NSNotFound) { return nil; } else { - NSTextContentStorage* contentStorage = _textView.textContentStorage; - id startLocation = [contentStorage - locationFromLocation:contentStorage.documentRange.location - withOffset:(NSInteger)charRange.location]; + NSTextContentStorage* storage = view.textContentStorage; + id startLocation = + [storage locationFromLocation:storage.documentRange.location + withOffset:(NSInteger)charRange.location]; id endLocation = - [contentStorage locationFromLocation:startLocation - withOffset:(NSInteger)charRange.length]; + [storage locationFromLocation:startLocation + withOffset:(NSInteger)charRange.length]; return [NSTextRange.alloc initWithLocation:startLocation endLocation:endLocation]; } } -- (NSRange)getCharRangeFromTextRange:(NSTextRange*)textRange - API_AVAILABLE(macos(12.0)) { +- (NSRange)charRangeFromTextRange:(NSTextRange*)textRange + inView:(NSTextView*)view API_AVAILABLE(macos(12.0)) { if (textRange == nil) { return NSMakeRange(NSNotFound, 0); } else { - NSTextContentStorage* contentStorage = _textView.textContentStorage; + NSTextContentStorage* storage = view.textContentStorage; NSInteger location = - [contentStorage offsetFromLocation:contentStorage.documentRange.location - toLocation:textRange.location]; - NSInteger length = - [contentStorage offsetFromLocation:textRange.location - toLocation:textRange.endLocation]; + [storage offsetFromLocation:storage.documentRange.location + toLocation:textRange.location]; + NSInteger length = [storage offsetFromLocation:textRange.location + toLocation:textRange.endLocation]; return NSMakeRange((NSUInteger)location, (NSUInteger)length); } } -// Get the rectangle containing entire contents -- (void)layoutContents { +static NSRect layoutTextView(NSTextView* view) { if (@available(macOS 12.0, *)) { - [_textView.textLayoutManager - ensureLayoutForRange:_textView.textContentStorage.documentRange]; - _contentRect = _textView.textLayoutManager.usageBoundsForTextContainer; + [view.textLayoutManager + ensureLayoutForRange:view.textContentStorage.documentRange]; + return view.textLayoutManager.usageBoundsForTextContainer; } else { - [_textView.layoutManager - ensureLayoutForTextContainer:_textView.textContainer]; - _contentRect = [_textView.layoutManager - usedRectForTextContainer:_textView.textContainer]; + [view.layoutManager ensureLayoutForTextContainer:view.textContainer]; + return [view.layoutManager usedRectForTextContainer:view.textContainer]; + } +} + +static BOOL any(BOOL* array, NSUInteger count) { + for (NSUInteger i = 0; i < count; ++i) { + if (array[i]) { + return YES; + } } - _contentRect.size = - NSMakeSize(ceil(NSWidth(_contentRect)), ceil(NSHeight(_contentRect))); + return NO; +} + +- (void)estimateBoundsOnScreen:(NSRect)screen + withPreedit:(BOOL)hasPreedit + candidates:(SquirrelCandidateRanges*)candidateRanges + truncation:(BOOL*)truncated + count:(NSUInteger)candidateCount + paging:(BOOL)hasPaging { + _screen = screen; + _candidateRanges = candidateRanges; + _truncated = truncated; + _candidateCount = candidateCount; + _preeditView.hidden = !hasPreedit; + _scrollView.hidden = candidateCount == 0; + _pagingView.hidden = !hasPaging; + _statusView.hidden = hasPreedit || candidateCount > 0; + // layout textviews and get their sizes + _preeditRect = NSZeroRect; + _documentRect = NSZeroRect; // in textView's own coordinates + _candidatesRect = NSZeroRect; + _pagingRect = NSZeroRect; + _clippedHeight = 0.0; + if (!hasPreedit && candidateCount == 0) { // status + _contentRect = layoutTextView(_statusView); + return; + } + if (hasPreedit) { + _preeditRect = layoutTextView(_preeditView); + _contentRect = _preeditRect; + } + if (candidateCount > 0) { + _documentRect = layoutTextView(_textView); + if (@available(macOS 12.0, *)) { + _documentRect.size.height += _currentTheme.linespace; + } else { + _documentRect.size.height += + _currentTheme.linear ? 0.0 : _currentTheme.linespace; + } + if (_currentTheme.linear && !any(truncated, candidateCount)) { + _documentRect.size.width -= _currentTheme.fullWidth; + } + _candidatesRect.size = _documentRect.size; + _documentRect.size.width += _currentTheme.fullWidth; + if (hasPreedit) { + _candidatesRect.origin.y = + NSMaxY(_preeditRect) + _currentTheme.preeditLinespace; + _contentRect = NSUnionRect(_preeditRect, _candidatesRect); + } else { + _contentRect = _candidatesRect; + } + if (hasPaging) { + _pagingRect = layoutTextView(_pagingView); + _pagingRect.origin.y = NSMaxY(_candidatesRect); + _contentRect = NSUnionRect(_contentRect, _pagingRect); + } + } else { + return; + } + // clip candidate block if it has too many lines + CGFloat maxHeight = + (_currentTheme.vertical ? NSWidth(_screen) : NSHeight(_screen)) * 0.5 - + _currentTheme.borderInsets.height * 2; + _clippedHeight = fdim(ceil(NSHeight(_contentRect)), ceil(maxHeight)); + _contentRect.size.height -= _clippedHeight; + _candidatesRect.size.height -= _clippedHeight; + _scrollView.verticalScroller.knobProportion = + NSHeight(_candidatesRect) / NSHeight(_documentRect); +} + +// Get the rectangle containing entire contents +- (void)layoutContents { + NSPoint origin = NSMakePoint(_currentTheme.borderInsets.width, + _currentTheme.borderInsets.height); + if (!_statusView.hidden) { // status + _contentRect.origin = + NSMakePoint(origin.x + ceil(_currentTheme.fullWidth * 0.5), origin.y); + return; + } + if (!_preeditView.hidden) { + _preeditRect = layoutTextView(_preeditView); + _preeditRect.size.width += _currentTheme.fullWidth; + _preeditRect.origin = origin; + _contentRect = _preeditRect; + } + if (!_scrollView.hidden) { + _candidatesRect.size.width = NSWidth(_documentRect); + _candidatesRect.size.height = NSHeight(_documentRect) - _clippedHeight; + if (!_preeditView.hidden) { + _candidatesRect.origin.x = origin.x; + _candidatesRect.origin.y = + NSMaxY(_preeditRect) + _currentTheme.preeditLinespace; + _contentRect = NSUnionRect(_preeditRect, _candidatesRect); + } else { + _candidatesRect.origin = origin; + _contentRect = _candidatesRect; + } + if (!_pagingView.hidden) { + _pagingRect = layoutTextView(_pagingView); + _pagingRect.size.width += _currentTheme.fullWidth; + _pagingRect.origin.x = origin.x; + _pagingRect.origin.y = NSMaxY(_candidatesRect); + _contentRect = NSUnionRect(_contentRect, _pagingRect); + } + } + _contentRect.size.width -= _currentTheme.fullWidth; + _contentRect.origin.x += ceil(_currentTheme.fullWidth * 0.5); } // Get the rectangle containing the range of text, will first convert to glyph // or text range, expensive to calculate -- (NSRect)blockRectForRange:(NSRange)charRange { +- (NSRect)blockRectForRange:(NSRange)charRange inView:(NSTextView*)view { if (charRange.location == NSNotFound) { return NSZeroRect; } if (@available(macOS 12.0, *)) { - NSTextRange* textRange = [self getTextRangeFromCharRange:charRange]; - NSRect __block firstLineRect = CGRectNull; - NSRect __block finalLineRect = CGRectNull; - [_textView.textLayoutManager + NSTextRange* textRange = [self textRangeFromCharRange:charRange + inView:view]; + NSRect __block firstLineRect = NSZeroRect; + NSRect __block finalLineRect = NSZeroRect; + [view.textLayoutManager enumerateTextSegmentsInRange:textRange type:NSTextLayoutManagerSegmentTypeStandard options: @@ -2618,73 +2773,72 @@ - (NSRect)blockRectForRange:(NSRange)charRange { } return YES; }]; - if (_currentTheme.linear && _currentTheme.linespace > 0.1 && - _candidateCount > 0) { - if (charRange.location >= _candidateRanges[0].location && - charRange.location < - NSMaxRange(_candidateRanges[_candidateCount - 1])) { - firstLineRect.size.height += _currentTheme.linespace; - firstLineRect.origin.y -= _currentTheme.linespace; - } - if (!NSIsEmptyRect(finalLineRect) && - NSMaxRange(charRange) > _candidateRanges[0].location && - NSMaxRange(charRange) <= - NSMaxRange(_candidateRanges[_candidateCount - 1])) { - finalLineRect.size.height += _currentTheme.linespace; - finalLineRect.origin.y -= _currentTheme.linespace; + CGFloat lineSpacing = view.hash == _textView.hash && _currentTheme.linear + ? _currentTheme.linespace + : 0.0; + if (lineSpacing > 0.1) { + firstLineRect.size.height += lineSpacing; + if (!NSIsEmptyRect(finalLineRect)) { + finalLineRect.size.height += lineSpacing; } } if (NSIsEmptyRect(finalLineRect)) { return firstLineRect; } else { - return NSMakeRect(0.0, NSMinY(firstLineRect), - NSMaxX(_contentRect) - _trailPadding, + CGFloat containerWidth = + NSWidth([view.textLayoutManager usageBoundsForTextContainer]); + return NSMakeRect(0.0, NSMinY(firstLineRect), containerWidth, NSMaxY(finalLineRect) - NSMinY(firstLineRect)); } } else { - NSLayoutManager* layoutManager = _textView.layoutManager; - NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:charRange - actualCharacterRange:NULL]; + NSRange glyphRange = + [view.layoutManager glyphRangeForCharacterRange:charRange + actualCharacterRange:NULL]; NSRange firstLineRange = NSMakeRange(NSNotFound, 0); - NSRect firstLineRect = - [layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphRange.location - effectiveRange:&firstLineRange]; + NSRect firstLineRect = [view.layoutManager + lineFragmentUsedRectForGlyphAtIndex:glyphRange.location + effectiveRange:&firstLineRange]; if (NSMaxRange(glyphRange) <= NSMaxRange(firstLineRange)) { - CGFloat headX = - [layoutManager locationForGlyphAtIndex:glyphRange.location].x; - CGFloat tailX = + CGFloat leading = + [view.layoutManager locationForGlyphAtIndex:glyphRange.location].x; + CGFloat trailing = NSMaxRange(glyphRange) < NSMaxRange(firstLineRange) - ? [layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x - : NSWidth(firstLineRect); - return NSMakeRect(NSMinX(firstLineRect) + headX, NSMinY(firstLineRect), - tailX - headX, NSHeight(firstLineRect)); + ? [view.layoutManager + locationForGlyphAtIndex:NSMaxRange(glyphRange)] + .x + : NSMaxX(firstLineRect); + return NSMakeRect(NSMinX(firstLineRect) + leading, NSMinY(firstLineRect), + trailing - leading, NSHeight(firstLineRect)); } else { - NSRect finalLineRect = [layoutManager + NSRect finalLineRect = [view.layoutManager lineFragmentUsedRectForGlyphAtIndex:NSMaxRange(glyphRange) - 1 effectiveRange:NULL]; - return NSMakeRect(0.0, NSMinY(firstLineRect), - NSMaxX(_contentRect) - _trailPadding, + CGFloat containerWidth = NSWidth( + [view.layoutManager usedRectForTextContainer:view.textContainer]); + return NSMakeRect(0.0, NSMinY(firstLineRect), containerWidth, NSMaxY(finalLineRect) - NSMinY(firstLineRect)); } } } -// Calculate 3 boxes containing the text in range. leadingRect and trailingRect -// are incomplete line rectangle bodyRect is the complete line fragment in the -// middle if the range spans no less than one full line -- (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange { +/* Calculate 3 rectangles encloding the text in range. TextPolygon.head & .tail + are incomplete line fragments TextPolygon.body is the complete line fragment + in the middle if the range spans no less than one full line */ +- (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange + inView:(NSTextView*)view { SquirrelTextPolygon textPolygon = { - .leading = NSZeroRect, .body = NSZeroRect, .trailing = NSZeroRect}; + .head = NSZeroRect, .body = NSZeroRect, .tail = NSZeroRect}; if (charRange.location == NSNotFound) { return textPolygon; } if (@available(macOS 12.0, *)) { - NSTextRange* textRange = [self getTextRangeFromCharRange:charRange]; - NSRect __block leadingLineRect = CGRectNull; - NSRect __block trailingLineRect = CGRectNull; - NSTextRange __block* leadingLineRange; - NSTextRange __block* trailingLineRange; - [_textView.textLayoutManager + NSTextRange* textRange = [self textRangeFromCharRange:charRange + inView:view]; + NSRect __block headLineRect = NSZeroRect; + NSRect __block tailLineRect = NSZeroRect; + NSTextRange __block* headLineRange; + NSTextRange __block* tailLineRange; + [view.textLayoutManager enumerateTextSegmentsInRange:textRange type:NSTextLayoutManagerSegmentTypeStandard options: @@ -2694,130 +2848,128 @@ - (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange { CGFloat baseline, NSTextContainer* _Nonnull textContainer) { if (!CGRectIsEmpty(segFrame)) { - if (NSIsEmptyRect(leadingLineRect) || + if (NSIsEmptyRect(headLineRect) || CGRectGetMinY(segFrame) < - NSMaxY(leadingLineRect)) { - leadingLineRect = - NSUnionRect(segFrame, leadingLineRect); - leadingLineRange = [leadingLineRange + NSMaxY(headLineRect)) { + headLineRect = + NSUnionRect(segFrame, headLineRect); + headLineRange = [headLineRange textRangeByFormingUnionWithTextRange: segRange]; } else { - trailingLineRect = - NSUnionRect(segFrame, trailingLineRect); - trailingLineRange = [trailingLineRange + tailLineRect = + NSUnionRect(segFrame, tailLineRect); + tailLineRange = [tailLineRange textRangeByFormingUnionWithTextRange: segRange]; } } return YES; }]; - if (_currentTheme.linear && _currentTheme.linespace > 0.1 && - _candidateCount > 0) { - if (charRange.location >= _candidateRanges[0].location && - charRange.location < - NSMaxRange(_candidateRanges[_candidateCount - 1])) { - leadingLineRect.size.height += _currentTheme.linespace; - leadingLineRect.origin.y -= _currentTheme.linespace; + NSTextContentStorage* storage = + (NSTextContentStorage*)view.textLayoutManager.textContentManager; + NSParagraphStyle* rulerAttrs = + [storage.textStorage attribute:NSParagraphStyleAttributeName + atIndex:charRange.location + effectiveRange:NULL]; + if (rulerAttrs.lineSpacing > 0.1) { + headLineRect.size.height += rulerAttrs.lineSpacing; + if (!NSIsEmptyRect(tailLineRect)) { + tailLineRect.size.height += rulerAttrs.lineSpacing; } } - if (NSIsEmptyRect(trailingLineRect)) { - textPolygon.body = leadingLineRect; + if (NSIsEmptyRect(tailLineRect)) { + textPolygon.body = headLineRect; } else { - if (_currentTheme.linear && _currentTheme.linespace > 0.1 && - _candidateCount > 0) { - if (NSMaxRange(charRange) > _candidateRanges[0].location && - NSMaxRange(charRange) <= - NSMaxRange(_candidateRanges[_candidateCount - 1])) { - trailingLineRect.size.height += _currentTheme.linespace; - trailingLineRect.origin.y -= _currentTheme.linespace; - } - } - - CGFloat containerWidth = NSMaxX(_contentRect) - _trailPadding; - leadingLineRect.size.width = containerWidth - NSMinX(leadingLineRect); - if (fabs(NSMaxX(trailingLineRect) - NSMaxX(leadingLineRect)) < 1) { - if (fabs(NSMinX(leadingLineRect) - NSMinX(trailingLineRect)) < 1) { - textPolygon.body = NSUnionRect(leadingLineRect, trailingLineRect); + CGFloat containerWidth = + NSWidth([view.textLayoutManager usageBoundsForTextContainer]); + headLineRect.size.width = containerWidth - NSMinX(headLineRect); + if (fabs(NSMaxX(tailLineRect) - NSMaxX(headLineRect)) < 1) { + if (fabs(NSMinX(headLineRect) - NSMinX(tailLineRect)) < 1) { + textPolygon.body = NSUnionRect(headLineRect, tailLineRect); } else { - textPolygon.leading = leadingLineRect; + textPolygon.head = headLineRect; textPolygon.body = - NSMakeRect(0.0, NSMaxY(leadingLineRect), containerWidth, - NSMaxY(trailingLineRect) - NSMaxY(leadingLineRect)); + NSMakeRect(0.0, NSMaxY(headLineRect), containerWidth, + NSMaxY(tailLineRect) - NSMaxY(headLineRect)); } } else { - textPolygon.trailing = trailingLineRect; - if (fabs(NSMinX(leadingLineRect) - NSMinX(trailingLineRect)) < 1) { + textPolygon.tail = tailLineRect; + if (fabs(NSMinX(headLineRect) - NSMinX(tailLineRect)) < 1) { textPolygon.body = - NSMakeRect(0.0, NSMinY(leadingLineRect), containerWidth, - NSMinY(trailingLineRect) - NSMinY(leadingLineRect)); + NSMakeRect(0.0, NSMinY(headLineRect), containerWidth, + NSMinY(tailLineRect) - NSMinY(headLineRect)); } else { - textPolygon.leading = leadingLineRect; - if (![trailingLineRange - containsLocation:leadingLineRange.endLocation]) { + textPolygon.head = headLineRect; + if (![tailLineRange containsLocation:headLineRange.endLocation]) { textPolygon.body = - NSMakeRect(0.0, NSMaxY(leadingLineRect), containerWidth, - NSMinY(trailingLineRect) - NSMaxY(leadingLineRect)); + NSMakeRect(0.0, NSMaxY(headLineRect), containerWidth, + NSMinY(tailLineRect) - NSMaxY(headLineRect)); } } } } } else { - NSLayoutManager* layoutManager = _textView.layoutManager; - NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:charRange - actualCharacterRange:NULL]; - NSRange leadingLineRange = NSMakeRange(NSNotFound, 0); - NSRect leadingLineRect = - [layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphRange.location - effectiveRange:&leadingLineRange]; - CGFloat headX = - [layoutManager locationForGlyphAtIndex:glyphRange.location].x; - if (NSMaxRange(leadingLineRange) >= NSMaxRange(glyphRange)) { - CGFloat tailX = - NSMaxRange(glyphRange) < NSMaxRange(leadingLineRange) - ? [layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x - : NSWidth(leadingLineRect); - textPolygon.body = NSMakeRect(headX, NSMinY(leadingLineRect), - tailX - headX, NSHeight(leadingLineRect)); + NSRange glyphRange = + [view.layoutManager glyphRangeForCharacterRange:charRange + actualCharacterRange:NULL]; + NSRange headLineRange = NSMakeRange(NSNotFound, 0); + NSRect headLineRect = [view.layoutManager + lineFragmentUsedRectForGlyphAtIndex:glyphRange.location + effectiveRange:&headLineRange]; + CGFloat leading = + [view.layoutManager locationForGlyphAtIndex:glyphRange.location].x; + if (NSMaxRange(headLineRange) >= NSMaxRange(glyphRange)) { + CGFloat trailing = + NSMaxRange(glyphRange) < NSMaxRange(headLineRange) + ? [view.layoutManager + locationForGlyphAtIndex:NSMaxRange(glyphRange)] + .x + : NSMaxX(headLineRect); + textPolygon.body = NSMakeRect(leading, NSMinY(headLineRect), + trailing - leading, NSHeight(headLineRect)); } else { - CGFloat containerWidth = NSMaxX(_contentRect) - _trailPadding; - NSRange trailingLineRange = NSMakeRange(NSNotFound, 0); - NSRect trailingLineRect = [layoutManager + CGFloat containerWidth = NSWidth( + [view.layoutManager usedRectForTextContainer:view.textContainer]); + NSRange tailLineRange = NSMakeRange(NSNotFound, 0); + NSRect tailLineRect = [view.layoutManager lineFragmentUsedRectForGlyphAtIndex:NSMaxRange(glyphRange) - 1 - effectiveRange:&trailingLineRange]; - CGFloat tailX = - NSMaxRange(glyphRange) < NSMaxRange(trailingLineRange) - ? [layoutManager locationForGlyphAtIndex:NSMaxRange(glyphRange)].x - : NSWidth(trailingLineRect); - if (NSMaxRange(trailingLineRange) == NSMaxRange(glyphRange)) { - if (glyphRange.location == leadingLineRange.location) { + effectiveRange:&tailLineRange]; + CGFloat trailing = + NSMaxRange(glyphRange) < NSMaxRange(tailLineRange) + ? [view.layoutManager + locationForGlyphAtIndex:NSMaxRange(glyphRange)] + .x + : NSMaxX(tailLineRect); + if (NSMaxRange(tailLineRange) == NSMaxRange(glyphRange)) { + if (glyphRange.location == headLineRange.location) { textPolygon.body = - NSMakeRect(0.0, NSMinY(leadingLineRect), containerWidth, - NSMaxY(trailingLineRect) - NSMinY(leadingLineRect)); + NSMakeRect(0.0, NSMinY(headLineRect), containerWidth, + NSMaxY(tailLineRect) - NSMinY(headLineRect)); } else { - textPolygon.leading = - NSMakeRect(headX, NSMinY(leadingLineRect), containerWidth - headX, - NSHeight(leadingLineRect)); + textPolygon.head = + NSMakeRect(leading, NSMinY(headLineRect), + containerWidth - leading, NSHeight(headLineRect)); textPolygon.body = - NSMakeRect(0.0, NSMaxY(leadingLineRect), containerWidth, - NSMaxY(trailingLineRect) - NSMaxY(leadingLineRect)); + NSMakeRect(0.0, NSMaxY(headLineRect), containerWidth, + NSMaxY(tailLineRect) - NSMaxY(headLineRect)); } } else { - textPolygon.trailing = NSMakeRect(0.0, NSMinY(trailingLineRect), tailX, - NSHeight(trailingLineRect)); - if (glyphRange.location == leadingLineRange.location) { + textPolygon.tail = NSMakeRect(0.0, NSMinY(tailLineRect), trailing, + NSHeight(tailLineRect)); + if (glyphRange.location == headLineRange.location) { textPolygon.body = - NSMakeRect(0.0, NSMinY(leadingLineRect), containerWidth, - NSMinY(trailingLineRect) - NSMinY(leadingLineRect)); + NSMakeRect(0.0, NSMinY(headLineRect), containerWidth, + NSMinY(tailLineRect) - NSMinY(headLineRect)); } else { - textPolygon.leading = - NSMakeRect(headX, NSMinY(leadingLineRect), containerWidth - headX, - NSHeight(leadingLineRect)); - if (trailingLineRange.location > NSMaxRange(leadingLineRange)) { + textPolygon.head = + NSMakeRect(leading, NSMinY(headLineRect), + containerWidth - leading, NSHeight(headLineRect)); + if (tailLineRange.location > NSMaxRange(headLineRange)) { textPolygon.body = - NSMakeRect(0.0, NSMaxY(leadingLineRect), containerWidth, - NSMinY(trailingLineRect) - NSMaxY(leadingLineRect)); + NSMakeRect(0.0, NSMaxY(headLineRect), containerWidth, + NSMinY(tailLineRect) - NSMaxY(headLineRect)); } } } @@ -2826,82 +2978,36 @@ - (SquirrelTextPolygon)textPolygonForRange:(NSRange)charRange { return textPolygon; } -- (void)estimateBoundsForPreedit:(NSRange)preeditRange - candidates:(SquirrelCandidateRanges*)candidateRanges - truncation:(BOOL*)truncated - count:(NSUInteger)candidateCount - paging:(NSRange)pagingRange { - _preeditRange = preeditRange; - _candidateRanges = candidateRanges; - _truncated = truncated; - _candidateCount = candidateCount; - _pagingRange = pagingRange; - [self layoutContents]; - if (_currentTheme.linear && (candidateCount > 0 || preeditRange.length > 0)) { - CGFloat width = 0.0; - if (preeditRange.length > 0) { - width = ceil(NSMaxX([self blockRectForRange:preeditRange])); - } - if (candidateCount > 0) { - BOOL isTruncated = truncated[0]; - NSUInteger start = candidateRanges[0].location; - for (NSUInteger i = 1; i <= candidateCount; ++i) { - if (i == candidateCount || truncated[i] != isTruncated) { - NSRect candidateRect = [self - blockRectForRange:NSMakeRange(start, - NSMaxRange(candidateRanges[i - 1]) - - start)]; - width = - fmax(width, ceil(NSMaxX(candidateRect)) - - (isTruncated ? 0.0 : _currentTheme.fullWidth)); - if (i < candidateCount) { - isTruncated = truncated[i]; - start = candidateRanges[i].location; - } - } - } - } - if (pagingRange.length > 0) { - width = fmax(width, ceil(NSMaxX([self blockRectForRange:pagingRange]))); - } - _trailPadding = fmax(NSMaxX(_contentRect) - width, 0.0); - } else { - _trailPadding = 0.0; - } -} - -// Will triger - (void)updateLayer -- (void)drawViewWithInsets:(NSEdgeInsets)marginInsets - hilitedIndex:(NSUInteger)hilitedIndex - hilitedPreeditRange:(NSRange)hilitedPreeditRange { - _marginInsets = marginInsets; +// Will triger `- (void)updateLayer` +- (void)drawViewWithHilitedIndex:(NSUInteger)hilitedIndex + hilitedPreeditRange:(NSRange)hilitedPreeditRange { _hilitedIndex = hilitedIndex; _hilitedPreeditRange = hilitedPreeditRange; _functionButton = kVoidSymbol; - // invalidate Rect beyond bound of textview to clear any out-of-bound drawing - // from last round self.needsDisplayInRect = self.bounds; - _textView.needsDisplayInRect = [self convertRect:self.bounds - toView:_textView]; - [self layoutContents]; -} - -- (void)setPreeditRange:(NSRange)preeditRange - hilitedPreeditRange:(NSRange)hilitedPreeditRange { - if (_preeditRange.length != preeditRange.length) { - for (NSUInteger i = 0; i < _candidateCount; ++i) { - _candidateRanges[i].location += - preeditRange.length - _preeditRange.length; + if (!_statusView.hidden) { + _statusView.needsDisplayInRect = _statusView.bounds; + } else { + if (!_preeditView.hidden) { + _preeditView.needsDisplayInRect = _preeditView.bounds; } - if (_pagingRange.location != NSNotFound) { - _pagingRange.location += preeditRange.length - _preeditRange.length; + // invalidate Rect beyond bound of textview to clear any out-of-bound + // drawing from last round + if (!_scrollView.hidden) { + _textView.needsDisplayInRect = + [_documentView convertRect:_documentView.bounds toView:_textView]; + } + if (!_pagingView.hidden) { + _pagingView.needsDisplayInRect = _pagingView.bounds; } } - _preeditRange = preeditRange; + [self layoutContents]; +} + +- (void)setHilitedPreeditRange:(NSRange)hilitedPreeditRange { _hilitedPreeditRange = hilitedPreeditRange; - self.needsDisplayInRect = _preeditBlock; - _textView.needsDisplayInRect = [self convertRect:_preeditBlock - toView:_textView]; + self.needsDisplayInRect = _preeditRect; + _preeditView.needsDisplayInRect = _preeditView.bounds; [self layoutContents]; } @@ -2910,22 +3016,59 @@ - (void)highlightCandidate:(NSUInteger)hilitedIndex { NSUInteger priorActivePage = _hilitedIndex / _currentTheme.pageSize; NSUInteger newActivePage = hilitedIndex / _currentTheme.pageSize; if (newActivePage != priorActivePage) { - self.needsDisplayInRect = _sectionRects[priorActivePage]; - [_textView - setNeedsDisplayInRect:[self convertRect:_sectionRects[priorActivePage] - toView:_textView] - avoidAdditionalLayout:YES]; + self.needsDisplayInRect = + [_documentView convertRect:_sectionRects[priorActivePage] + toView:self]; + _textView.needsDisplayInRect = + [_documentView convertRect:_sectionRects[priorActivePage] + toView:_textView]; + } + self.needsDisplayInRect = + [_documentView convertRect:_sectionRects[newActivePage] toView:self]; + _textView.needsDisplayInRect = + [_documentView convertRect:_sectionRects[newActivePage] + toView:_textView]; + + if (NSMinY(_sectionRects[newActivePage]) < + NSMinY(_scrollView.documentVisibleRect) - 0.1) { + NSPoint origin = _scrollView.contentView.bounds.origin; + origin.y -= NSMinY(_scrollView.documentVisibleRect) - + NSMinY(_sectionRects[newActivePage]); + [_scrollView.contentView scrollToPoint:origin]; + _scrollView.verticalScroller.doubleValue = + NSMinY(_scrollView.documentVisibleRect) / _clippedHeight; + } else if (NSMaxY(_sectionRects[newActivePage]) > + NSMaxY(_scrollView.documentVisibleRect) + 0.1) { + NSPoint origin = _scrollView.contentView.bounds.origin; + origin.y += NSMaxY(_sectionRects[newActivePage]) - + NSMaxY(_scrollView.documentVisibleRect); + [_scrollView.contentView scrollToPoint:origin]; + _scrollView.verticalScroller.doubleValue = + NSMinY(_scrollView.documentVisibleRect) / _clippedHeight; } - self.needsDisplayInRect = _sectionRects[newActivePage]; - [_textView - setNeedsDisplayInRect:[self convertRect:_sectionRects[newActivePage] - toView:_textView] - avoidAdditionalLayout:YES]; } else { - self.needsDisplayInRect = _candidateBlock; - [_textView setNeedsDisplayInRect:[self convertRect:_candidateBlock - toView:_textView] - avoidAdditionalLayout:YES]; + self.needsDisplayInRect = _candidatesRect; + _textView.needsDisplayInRect = + [_documentView convertRect:_documentView.bounds toView:_textView]; + + SquirrelTextPolygon polygons = _candidatePolygons[hilitedIndex]; + CGFloat top = NSIsEmptyRect(polygons.head) ? NSMinY(polygons.body) + : NSMinY(polygons.head); + CGFloat bottom = NSIsEmptyRect(polygons.tail) ? NSMaxY(polygons.body) + : NSMaxY(polygons.tail); + if (NSMinY(_scrollView.documentVisibleRect) > top + 0.1) { + NSPoint origin = _scrollView.contentView.bounds.origin; + origin.y -= NSMinY(_scrollView.documentVisibleRect) - top; + [_scrollView.contentView scrollToPoint:origin]; + _scrollView.verticalScroller.doubleValue = + NSMinY(_scrollView.documentVisibleRect) / _clippedHeight; + } else if (NSMaxY(_scrollView.documentVisibleRect) < bottom - 0.1) { + NSPoint origin = _scrollView.contentView.bounds.origin; + origin.y += bottom - NSMaxY(_scrollView.documentVisibleRect); + [_scrollView.contentView scrollToPoint:origin]; + _scrollView.verticalScroller.doubleValue = + NSMinY(_scrollView.documentVisibleRect) / _clippedHeight; + } } _hilitedIndex = hilitedIndex; } @@ -2934,45 +3077,77 @@ - (void)highlightFunctionButton:(SquirrelIndex)functionButton { for (SquirrelIndex index : (SquirrelIndex[2]){_functionButton, functionButton}) { switch (index) { + case kBackSpaceKey: + case kEscapeKey: + self.needsDisplayInRect = _deleteBackRect; + [_preeditView setNeedsDisplayInRect:[self convertRect:_deleteBackRect + toView:_preeditView] + avoidAdditionalLayout:YES]; + break; case kPageUpKey: case kHomeKey: self.needsDisplayInRect = _pageUpRect; - [_textView setNeedsDisplayInRect:[self convertRect:_pageUpRect - toView:_textView] - avoidAdditionalLayout:YES]; + [_pagingView setNeedsDisplayInRect:[self convertRect:_pageUpRect + toView:_pagingView] + avoidAdditionalLayout:YES]; break; case kPageDownKey: case kEndKey: self.needsDisplayInRect = _pageDownRect; - [_textView setNeedsDisplayInRect:[self convertRect:_pageDownRect - toView:_textView] - avoidAdditionalLayout:YES]; - break; - case kBackSpaceKey: - case kEscapeKey: - self.needsDisplayInRect = _deleteBackRect; - [_textView setNeedsDisplayInRect:[self convertRect:_deleteBackRect - toView:_textView] - avoidAdditionalLayout:YES]; + [_pagingView setNeedsDisplayInRect:[self convertRect:_pageDownRect + toView:_pagingView] + avoidAdditionalLayout:YES]; break; case kExpandButton: case kCompressButton: case kLockButton: self.needsDisplayInRect = _expanderRect; - [_textView setNeedsDisplayInRect:[self convertRect:_expanderRect - toView:_textView] - avoidAdditionalLayout:YES]; + [_pagingView setNeedsDisplayInRect:[self convertRect:_expanderRect + toView:_pagingView] + avoidAdditionalLayout:YES]; + break; + default: break; } } _functionButton = functionButton; } -// Bezier cubic curve, which has continuous roundness +static NSBezierPath* squirclePath(NSRect rect, CGFloat cornerRadius) { + NSPoint vertices[4]; + rectVertices(rect, vertices); + return squirclePath(vertices, 4, cornerRadius); +} + +static NSBezierPath* squirclePath(SquirrelTextPolygon polygon, + CGFloat cornerRadius) { + NSBezierPath* path; + if (NSIsEmptyRect(polygon.body) && !NSIsEmptyRect(polygon.head) && + !NSIsEmptyRect(polygon.tail) && + NSMaxX(polygon.tail) < NSMinX(polygon.head)) { + NSPoint headVertices[4], tailVertices[4]; + rectVertices(polygon.head, headVertices); + rectVertices(polygon.tail, tailVertices); + path = squirclePath(headVertices, 4, cornerRadius); + [path appendBezierPath:squirclePath(tailVertices, 4, cornerRadius)]; + } else { + NSUInteger numVert = clamp((NSIsEmptyRect(polygon.head) ? 0 : 4UL) + + (NSIsEmptyRect(polygon.body) ? 0 : 2UL) + + (NSIsEmptyRect(polygon.tail) ? 0 : 4UL), + 4UL, 8UL); + NSPoint vertices[numVert]; + textPolygonVertices(polygon, vertices); + path = squirclePath(vertices, numVert, cornerRadius); + } + return path; +} + +// Bezier squircle curves, whose rounded corners are smooth (continously +// differentiable) static NSBezierPath* squirclePath(NSPointArray vertices, - NSInteger numVert, + NSUInteger numVert, CGFloat radius) { - if (vertices == NULL) { + if (vertices == NULL || numVert < 4) { return nil; } NSBezierPath* path = NSBezierPath.bezierPath; @@ -2992,7 +3167,7 @@ - (void)highlightFunctionButton:(SquirrelIndex)functionButton { endPoint = NSMakePoint(nextPoint.x, point.y + nextDiff.dy * 0.5); } [path moveToPoint:endPoint]; - for (NSInteger i = 0; i < numVert; ++i) { + for (NSUInteger i = 0; i < numVert; ++i) { lastDiff = nextDiff; point = nextPoint; nextPoint = vertices[(i + 1) % numVert]; @@ -3041,70 +3216,70 @@ static void rectVertices(NSRect rect, NSPointArray vertices) { static void textPolygonVertices(SquirrelTextPolygon textPolygon, NSPointArray vertices) { - switch ((NSIsEmptyRect(textPolygon.leading) << 2) | + switch ((NSIsEmptyRect(textPolygon.head) << 2) | (NSIsEmptyRect(textPolygon.body) << 1) | - (NSIsEmptyRect(textPolygon.trailing) << 0)) { + (NSIsEmptyRect(textPolygon.tail) << 0)) { case 0b011: - rectVertices(textPolygon.leading, vertices); + rectVertices(textPolygon.head, vertices); break; case 0b110: - rectVertices(textPolygon.trailing, vertices); + rectVertices(textPolygon.tail, vertices); break; case 0b101: rectVertices(textPolygon.body, vertices); break; case 0b001: { - NSPoint leadingVertices[4], bodyVertices[4]; - rectVertices(textPolygon.leading, leadingVertices); + NSPoint headVertices[4], bodyVertices[4]; + rectVertices(textPolygon.head, headVertices); rectVertices(textPolygon.body, bodyVertices); - vertices[0] = leadingVertices[0]; - vertices[1] = leadingVertices[1]; + vertices[0] = headVertices[0]; + vertices[1] = headVertices[1]; vertices[2] = bodyVertices[0]; vertices[3] = bodyVertices[1]; vertices[4] = bodyVertices[2]; - vertices[5] = leadingVertices[3]; + vertices[5] = headVertices[3]; } break; case 0b100: { - NSPoint bodyVertices[4], trailingVertices[4]; + NSPoint bodyVertices[4], tailVertices[4]; rectVertices(textPolygon.body, bodyVertices); - rectVertices(textPolygon.trailing, trailingVertices); + rectVertices(textPolygon.tail, tailVertices); vertices[0] = bodyVertices[0]; - vertices[1] = trailingVertices[1]; - vertices[2] = trailingVertices[2]; - vertices[3] = trailingVertices[3]; + vertices[1] = tailVertices[1]; + vertices[2] = tailVertices[2]; + vertices[3] = tailVertices[3]; vertices[4] = bodyVertices[2]; vertices[5] = bodyVertices[3]; } break; case 0b010: - if (NSMinX(textPolygon.leading) <= NSMaxX(textPolygon.trailing)) { - NSPoint leadingVertices[4], trailingVertices[4]; - rectVertices(textPolygon.leading, leadingVertices); - rectVertices(textPolygon.trailing, trailingVertices); - vertices[0] = leadingVertices[0]; - vertices[1] = leadingVertices[1]; - vertices[2] = trailingVertices[0]; - vertices[3] = trailingVertices[1]; - vertices[4] = trailingVertices[2]; - vertices[5] = trailingVertices[3]; - vertices[6] = leadingVertices[2]; - vertices[7] = leadingVertices[3]; + if (NSMinX(textPolygon.head) <= NSMaxX(textPolygon.tail)) { + NSPoint headVertices[4], tailVertices[4]; + rectVertices(textPolygon.head, headVertices); + rectVertices(textPolygon.tail, tailVertices); + vertices[0] = headVertices[0]; + vertices[1] = headVertices[1]; + vertices[2] = tailVertices[0]; + vertices[3] = tailVertices[1]; + vertices[4] = tailVertices[2]; + vertices[5] = tailVertices[3]; + vertices[6] = headVertices[2]; + vertices[7] = headVertices[3]; } else { vertices = NULL; } break; case 0b000: { - NSPoint leadingVertices[4], bodyVertices[4], trailingVertices[4]; - rectVertices(textPolygon.leading, leadingVertices); + NSPoint headVertices[4], bodyVertices[4], tailVertices[4]; + rectVertices(textPolygon.head, headVertices); rectVertices(textPolygon.body, bodyVertices); - rectVertices(textPolygon.trailing, trailingVertices); - vertices[0] = leadingVertices[0]; - vertices[1] = leadingVertices[1]; + rectVertices(textPolygon.tail, tailVertices); + vertices[0] = headVertices[0]; + vertices[1] = headVertices[1]; vertices[2] = bodyVertices[0]; - vertices[3] = trailingVertices[1]; - vertices[4] = trailingVertices[2]; - vertices[5] = trailingVertices[3]; + vertices[3] = tailVertices[1]; + vertices[4] = tailVertices[2]; + vertices[5] = tailVertices[3]; vertices[6] = bodyVertices[2]; - vertices[7] = leadingVertices[3]; + vertices[7] = headVertices[3]; } break; default: vertices = NULL; @@ -3153,9 +3328,7 @@ - (CAShapeLayer*)getFunctionButtonLayer { if (!NSIsEmptyRect(buttonRect) && buttonColor) { CGFloat cornerRadius = fmin(_currentTheme.hilitedCornerRadius, NSHeight(buttonRect) * 0.5); - NSPoint buttonVertices[4]; - rectVertices(buttonRect, buttonVertices); - NSBezierPath* buttonPath = squirclePath(buttonVertices, 4, cornerRadius); + NSBezierPath* buttonPath = squirclePath(buttonRect, cornerRadius); CAShapeLayer* functionButtonLayer = CAShapeLayer.alloc.init; functionButtonLayer.path = buttonPath.quartzPath; functionButtonLayer.fillColor = buttonColor.CGColor; @@ -3173,421 +3346,286 @@ - (void)updateLayer { backgroundRect = [self backingAlignedRect:backgroundRect options:NSAlignAllEdgesNearest]; - NSRange visibleRange; - if (@available(macOS 12.0, *)) { - visibleRange = - [self getCharRangeFromTextRange:_textView.textLayoutManager - .textViewportLayoutController - .viewportRange]; - } else { - NSRange containerGlyphRange = NSMakeRange(NSNotFound, 0); - [_textView.layoutManager textContainerForGlyphAtIndex:0 - effectiveRange:&containerGlyphRange]; - visibleRange = - [_textView.layoutManager characterRangeForGlyphRange:containerGlyphRange - actualGlyphRange:NULL]; - } - NSRange preeditRange = NSIntersectionRange(_preeditRange, visibleRange); - NSRange candidateBlockRange; - if (_candidateCount > 0) { - NSUInteger candidateBlockLength = - NSMaxRange(_candidateRanges[_candidateCount - 1]) - - _candidateRanges[0].location; - candidateBlockRange = NSIntersectionRange( - NSMakeRange(_candidateRanges[0].location, candidateBlockLength), - visibleRange); - } else { - candidateBlockRange = NSMakeRange(NSNotFound, 0); - } - NSRange pagingRange = NSIntersectionRange(_pagingRange, visibleRange); - - // Draw preedit Rect - _preeditBlock = NSZeroRect; + /*** Preedit Rects **/ _deleteBackRect = NSZeroRect; NSBezierPath* hilitedPreeditPath; - if (preeditRange.length > 0) { - NSRect innerBox = [self blockRectForRange:preeditRange]; - _preeditBlock = NSMakeRect( - backgroundRect.origin.x, backgroundRect.origin.y, - backgroundRect.size.width, - innerBox.size.height + - (candidateBlockRange.length > 0 ? theme.preeditLinespace : 0.0)); - _preeditBlock = [self backingAlignedRect:_preeditBlock - options:NSAlignAllEdgesNearest]; - - // Draw hilited part of preedit text - NSRange hilitedPreeditRange = - NSIntersectionRange(_hilitedPreeditRange, visibleRange); + if (!_preeditView.hidden) { + _preeditRect.size.width = NSWidth(backgroundRect); + _preeditRect = [self backingAlignedRect:_preeditRect + options:NSAlignAllEdgesNearest]; + // Draw the highlighted part of preedit text CGFloat cornerRadius = fmin(theme.hilitedCornerRadius, theme.preeditParagraphStyle.minimumLineHeight * 0.5); - if (hilitedPreeditRange.length > 0 && theme.hilitedPreeditBackColor) { + if (_hilitedPreeditRange.length > 0 && theme.hilitedPreeditBackColor) { CGFloat padding = ceil(theme.preeditParagraphStyle.minimumLineHeight * 0.05); - innerBox.origin.x += _marginInsets.left - padding; + NSRect innerBox = _preeditRect; + innerBox.origin.x += ceil(theme.fullWidth * 0.5) - padding; innerBox.size.width = - backgroundRect.size.width - theme.fullWidth + padding * 2; - innerBox.origin.y += _marginInsets.top; + NSWidth(backgroundRect) - theme.fullWidth + padding * 2; innerBox = [self backingAlignedRect:innerBox options:NSAlignAllEdgesNearest]; SquirrelTextPolygon textPolygon = - [self textPolygonForRange:hilitedPreeditRange]; - NSInteger numVert = 0; - if (!NSIsEmptyRect(textPolygon.leading)) { - textPolygon.leading.origin.x += _marginInsets.left - padding; - textPolygon.leading.origin.y += _marginInsets.top; - textPolygon.leading.size.width += padding * 2; - textPolygon.leading = [self - backingAlignedRect:NSIntersectionRect(textPolygon.leading, innerBox) + [self textPolygonForRange:_hilitedPreeditRange inView:_preeditView]; + if (!NSIsEmptyRect(textPolygon.head)) { + textPolygon.head.origin.x += + theme.borderInsets.width + ceil(theme.fullWidth * 0.5) - padding; + textPolygon.head.origin.y += theme.borderInsets.height; + textPolygon.head.size.width += padding * 2; + textPolygon.head = [self + backingAlignedRect:NSIntersectionRect(textPolygon.head, innerBox) options:NSAlignAllEdgesNearest]; - numVert += 4; } if (!NSIsEmptyRect(textPolygon.body)) { - textPolygon.body.origin.x += _marginInsets.left - padding; - textPolygon.body.origin.y += _marginInsets.top; + textPolygon.body.origin.x += + theme.borderInsets.width + ceil(theme.fullWidth * 0.5) - padding; + textPolygon.body.origin.y += theme.borderInsets.height; textPolygon.body.size.width += padding; - if (!NSIsEmptyRect(textPolygon.trailing) || - NSMaxRange(hilitedPreeditRange) + 2 == NSMaxRange(preeditRange)) { + if (!NSIsEmptyRect(textPolygon.tail) || + NSMaxRange(_hilitedPreeditRange) + 2 == _preeditContents.length) { textPolygon.body.size.width += padding; } textPolygon.body = [self backingAlignedRect:NSIntersectionRect(textPolygon.body, innerBox) options:NSAlignAllEdgesNearest]; - numVert += 2; } - if (!NSIsEmptyRect(textPolygon.trailing)) { - textPolygon.trailing.origin.x += _marginInsets.left - padding; - textPolygon.trailing.origin.y += _marginInsets.top; - textPolygon.trailing.size.width += padding; - if (NSMaxRange(hilitedPreeditRange) + 2 == NSMaxRange(preeditRange)) { - textPolygon.trailing.size.width += padding; + if (!NSIsEmptyRect(textPolygon.tail)) { + textPolygon.tail.origin.x += + theme.borderInsets.width + ceil(theme.fullWidth * 0.5) - padding; + textPolygon.tail.origin.y += theme.borderInsets.height; + textPolygon.tail.size.width += padding; + if (NSMaxRange(_hilitedPreeditRange) + 2 == _preeditContents.length) { + textPolygon.tail.size.width += padding; } - textPolygon.trailing = - [self backingAlignedRect:NSIntersectionRect(textPolygon.trailing, - innerBox) - options:NSAlignAllEdgesNearest]; - numVert += 4; - } - - // Handles the special case where containing boxes are separated - if (NSIsEmptyRect(textPolygon.body) && - !NSIsEmptyRect(textPolygon.leading) && - !NSIsEmptyRect(textPolygon.trailing) && - NSMaxX(textPolygon.trailing) < NSMinX(textPolygon.leading)) { - NSPoint leadingVertices[4], trailingVertices[4]; - rectVertices(textPolygon.leading, leadingVertices); - rectVertices(textPolygon.trailing, trailingVertices); - hilitedPreeditPath = squirclePath(leadingVertices, 4, cornerRadius); - [hilitedPreeditPath - appendBezierPath:squirclePath(trailingVertices, 4, cornerRadius)]; - } else { - numVert = numVert > 8 ? 8 : numVert < 4 ? 4 : numVert; - NSPoint polygonVertices[numVert]; - textPolygonVertices(textPolygon, polygonVertices); - hilitedPreeditPath = - squirclePath(polygonVertices, numVert, cornerRadius); + textPolygon.tail = [self + backingAlignedRect:NSIntersectionRect(textPolygon.tail, innerBox) + options:NSAlignAllEdgesNearest]; } + hilitedPreeditPath = squirclePath(textPolygon, cornerRadius); } _deleteBackRect = - [self blockRectForRange:NSMakeRange(NSMaxRange(preeditRange) - 1, 1)]; - _deleteBackRect.size.width += floor(theme.fullWidth * 0.5); + [self blockRectForRange:NSMakeRange(_preeditContents.length - 1, 1) + inView:_preeditView]; + _deleteBackRect.size.width += theme.fullWidth; _deleteBackRect.origin.x = NSMaxX(backgroundRect) - NSWidth(_deleteBackRect); - _deleteBackRect.origin.y += _marginInsets.top; + _deleteBackRect.origin.y += theme.borderInsets.height; _deleteBackRect = [self - backingAlignedRect:NSIntersectionRect(_deleteBackRect, _preeditBlock) + backingAlignedRect:NSIntersectionRect(_deleteBackRect, _preeditRect) options:NSAlignAllEdgesNearest]; } - // Draw candidate Rect - _candidateBlock = NSZeroRect; + /*** Candidates Rects, all in documentView coordinates (except for + * `candidatesRect`) ***/ _candidatePolygons = NULL; _sectionRects = NULL; _tabularIndices = NULL; NSBezierPath *candidateBlockPath, *hilitedCandidatePath; NSBezierPath *gridPath, *activePagePath; - if (candidateBlockRange.length > 0) { - _candidateBlock = [self blockRectForRange:candidateBlockRange]; - _candidateBlock.size.width = backgroundRect.size.width; - _candidateBlock.origin.x = backgroundRect.origin.x; - _candidateBlock.origin.y = preeditRange.length == 0 ? NSMinY(backgroundRect) - : NSMaxY(_preeditBlock); - if (pagingRange.length == 0) { - _candidateBlock.size.height = - NSMaxY(backgroundRect) - NSMinY(_candidateBlock); - } else if (!theme.linear) { - _candidateBlock.size.height += theme.linespace; - } - _candidateBlock = [self - backingAlignedRect:NSIntersectionRect(_candidateBlock, backgroundRect) + if (!_scrollView.hidden) { + _candidatesRect.size.width = NSWidth(backgroundRect); + _candidatesRect = [self + backingAlignedRect:NSIntersectionRect(_candidatesRect, backgroundRect) options:NSAlignAllEdgesNearest]; + _documentRect.size.width = NSWidth(backgroundRect); NSPoint candidateBlockVertices[4]; - rectVertices(_candidateBlock, candidateBlockVertices); + rectVertices(_candidatesRect, candidateBlockVertices); CGFloat blockCornerRadius = - fmin(theme.hilitedCornerRadius, NSHeight(_candidateBlock) * 0.5); + fmin(theme.hilitedCornerRadius, NSHeight(_candidatesRect) * 0.5); candidateBlockPath = squirclePath(candidateBlockVertices, 4, blockCornerRadius); - - // Draw candidate highlight rect + // Store candidate enclosing polygons and draw the ones highlighted CGFloat cornerRadius = fmin(theme.hilitedCornerRadius, theme.candidateParagraphStyle.minimumLineHeight * 0.5); _candidatePolygons = new SquirrelTextPolygon[_candidateCount]; - if (theme.linear) { + if (theme.linear) { // linear layout CGFloat gridOriginY; CGFloat tabInterval; NSUInteger lineNum = 0; - NSRect sectionRect = _candidateBlock; + NSRect sectionRect = _documentRect; if (theme.tabular) { _tabularIndices = new SquirrelTabularIndex[_candidateCount]; _sectionRects = new NSRect[_candidateCount / theme.pageSize]; gridPath = NSBezierPath.bezierPath; - gridOriginY = NSMinY(_candidateBlock); + gridOriginY = NSMinY(_documentRect); tabInterval = theme.fullWidth * 2; sectionRect.size.height = 0; } for (NSUInteger i = 0; i < _candidateCount; ++i) { - NSRange candidateRange = - NSIntersectionRange(NSMakeRange(_candidateRanges[i].location, - _candidateRanges[i].length), - visibleRange); - if (candidateRange.length == 0) { - _candidateCount = i; - break; - } + NSRange candidateRange = RangeCandidate(_candidateRanges[i]); SquirrelTextPolygon candidatePolygon = - [self textPolygonForRange:candidateRange]; - if (!NSIsEmptyRect(candidatePolygon.leading)) { - candidatePolygon.leading.origin.x += theme.borderInsets.width; - candidatePolygon.leading.size.width += theme.fullWidth; - candidatePolygon.leading.origin.y += _marginInsets.top; - candidatePolygon.leading = [self - backingAlignedRect:NSIntersectionRect(candidatePolygon.leading, - _candidateBlock) - options:NSAlignAllEdgesNearest]; + [self textPolygonForRange:candidateRange inView:_textView]; + if (!NSIsEmptyRect(candidatePolygon.head)) { + candidatePolygon.head.size.width += theme.fullWidth; + candidatePolygon.head = + [_documentView backingAlignedRect:candidatePolygon.head + options:NSAlignAllEdgesNearest]; } - if (!NSIsEmptyRect(candidatePolygon.trailing)) { - candidatePolygon.trailing.origin.x += theme.borderInsets.width; - candidatePolygon.trailing.origin.y += _marginInsets.top; - candidatePolygon.trailing = [self - backingAlignedRect:NSIntersectionRect(candidatePolygon.trailing, - _candidateBlock) - options:NSAlignAllEdgesNearest]; + if (!NSIsEmptyRect(candidatePolygon.tail)) { + candidatePolygon.tail = + [_documentView backingAlignedRect:candidatePolygon.tail + options:NSAlignAllEdgesNearest]; } if (!NSIsEmptyRect(candidatePolygon.body)) { - candidatePolygon.body.origin.x += theme.borderInsets.width; if (_truncated[i]) { - candidatePolygon.body.size.width = - NSMaxX(_candidateBlock) - NSMinX(candidatePolygon.body); - } else if (!NSIsEmptyRect(candidatePolygon.trailing)) { + candidatePolygon.body.size.width = NSWidth(_documentRect); + } else if (!NSIsEmptyRect(candidatePolygon.tail)) { candidatePolygon.body.size.width += theme.fullWidth; } - candidatePolygon.body.origin.y += _marginInsets.top; candidatePolygon.body = - [self backingAlignedRect:NSIntersectionRect(candidatePolygon.body, - _candidateBlock) - options:NSAlignAllEdgesNearest]; + [_documentView backingAlignedRect:candidatePolygon.body + options:NSAlignAllEdgesNearest]; } if (theme.tabular) { if (_expanded) { if (i % theme.pageSize == 0) { - sectionRect.origin.y += NSHeight(sectionRect); + sectionRect.origin.y = ceil(NSMaxY(sectionRect)); } else if (i % theme.pageSize == theme.pageSize - 1) { sectionRect.size.height = - NSMaxY(NSIsEmptyRect(candidatePolygon.trailing) + NSMaxY(NSIsEmptyRect(candidatePolygon.tail) ? candidatePolygon.body - : candidatePolygon.trailing) - + : candidatePolygon.tail) - NSMinY(sectionRect); NSUInteger sec = i / theme.pageSize; _sectionRects[sec] = sectionRect; if (sec == _hilitedIndex / theme.pageSize) { - NSPoint activePageVertices[4]; - rectVertices(sectionRect, activePageVertices); CGFloat pageCornerRadius = fmin(theme.hilitedCornerRadius, NSHeight(sectionRect) * 0.5); - activePagePath = - squirclePath(activePageVertices, 4, pageCornerRadius); + activePagePath = squirclePath(sectionRect, pageCornerRadius); } } } - CGFloat bottomEdge = NSMaxY(NSIsEmptyRect(candidatePolygon.trailing) + CGFloat bottomEdge = NSMaxY(NSIsEmptyRect(candidatePolygon.tail) ? candidatePolygon.body - : candidatePolygon.trailing); + : candidatePolygon.tail); if (fabs(bottomEdge - gridOriginY) > 2) { lineNum += i > 0 ? 1 : 0; // horizontal border except for the last line - if (fabs(bottomEdge - NSMaxY(_candidateBlock)) > 2) { - [gridPath moveToPoint:NSMakePoint(NSMinX(_candidateBlock) + - ceil(theme.fullWidth * 0.5), + if (bottomEdge < NSMaxY(_documentRect) - 2) { + [gridPath moveToPoint:NSMakePoint(ceil(theme.fullWidth * 0.5), bottomEdge)]; [gridPath - lineToPoint:NSMakePoint(NSMaxX(_candidateBlock) - + lineToPoint:NSMakePoint(NSMaxX(_documentRect) - floor(theme.fullWidth * 0.5), bottomEdge)]; } gridOriginY = bottomEdge; } - NSPoint headOrigin = (NSIsEmptyRect(candidatePolygon.leading) - ? candidatePolygon.body - : candidatePolygon.leading) - .origin; - NSUInteger headTabColumn = (NSUInteger)round( - (headOrigin.x - _marginInsets.left) / tabInterval); + NSPoint leadOrigin = + (NSIsEmptyRect(candidatePolygon.head) ? candidatePolygon.body + : candidatePolygon.head) + .origin; + NSUInteger leadTabColumn = + (NSUInteger)round(leadOrigin.x / tabInterval); // vertical bar - if (headOrigin.x > NSMinX(_candidateBlock) + theme.fullWidth) { + if (leadOrigin.x > NSMinX(_documentRect) + theme.fullWidth) { [gridPath - moveToPoint:NSMakePoint(headOrigin.x, - headOrigin.y + cornerRadius * 0.8)]; + moveToPoint:NSMakePoint(leadOrigin.x, + leadOrigin.y + cornerRadius * 0.8)]; [gridPath lineToPoint:NSMakePoint( - headOrigin.x, - NSMaxY(NSIsEmptyRect(candidatePolygon.leading) + leadOrigin.x, + NSMaxY(NSIsEmptyRect(candidatePolygon.head) ? candidatePolygon.body - : candidatePolygon.leading) - + : candidatePolygon.head) - cornerRadius * 0.8)]; } _tabularIndices[i] = (SquirrelTabularIndex){ - .index = i, .lineNum = lineNum, .tabNum = headTabColumn}; + .index = i, .lineNum = lineNum, .tabNum = leadTabColumn}; } _candidatePolygons[i] = candidatePolygon; } - if (_hilitedIndex < _candidateCount) { - NSInteger numVert = - (NSIsEmptyRect(_candidatePolygons[_hilitedIndex].leading) ? 0 : 4) + - (NSIsEmptyRect(_candidatePolygons[_hilitedIndex].body) ? 0 : 2) + - (NSIsEmptyRect(_candidatePolygons[_hilitedIndex].trailing) ? 0 : 4); - // Handles the special case where containing boxes are separated - if (numVert == 8 && - NSMaxX(_candidatePolygons[_hilitedIndex].trailing) < - NSMinX(_candidatePolygons[_hilitedIndex].leading)) { - NSPoint leadingVertices[4], trailingVertices[4]; - rectVertices(_candidatePolygons[_hilitedIndex].leading, - leadingVertices); - rectVertices(_candidatePolygons[_hilitedIndex].trailing, - trailingVertices); - hilitedCandidatePath = squirclePath(leadingVertices, 4, cornerRadius); - [hilitedCandidatePath - appendBezierPath:squirclePath(trailingVertices, 4, cornerRadius)]; - } else { - numVert = numVert > 8 ? 8 : numVert < 4 ? 4 : numVert; - NSPoint polygonVertices[numVert]; - textPolygonVertices(_candidatePolygons[_hilitedIndex], - polygonVertices); - hilitedCandidatePath = - squirclePath(polygonVertices, numVert, cornerRadius); - } - } + hilitedCandidatePath = + squirclePath(_candidatePolygons[_hilitedIndex], cornerRadius); } else { // stacked layout for (NSUInteger i = 0; i < _candidateCount; ++i) { - NSRange candidateRange = - NSIntersectionRange(NSMakeRange(_candidateRanges[i].location, - _candidateRanges[i].length), - visibleRange); - candidateRange = NSIntersectionRange(candidateRange, visibleRange); - if (candidateRange.length == 0) { - _candidateCount = i; - break; - } - NSRect candidateRect = [self blockRectForRange:candidateRange]; - candidateRect.size.width = backgroundRect.size.width; - candidateRect.origin.x = backgroundRect.origin.x; - candidateRect.origin.y += - _marginInsets.top - ceil(theme.linespace * 0.5); + NSRange candidateRange = RangeCandidate(_candidateRanges[i]); + NSRect candidateRect = [self blockRectForRange:candidateRange + inView:_textView]; + candidateRect.size.width = NSWidth(_documentRect); candidateRect.size.height += theme.linespace; candidateRect = - [self backingAlignedRect:NSIntersectionRect(candidateRect, - _candidateBlock) - options:NSAlignAllEdgesNearest]; + [_documentView backingAlignedRect:candidateRect + options:NSAlignAllEdgesNearest]; _candidatePolygons[i] = (SquirrelTextPolygon){NSZeroRect, candidateRect, NSZeroRect}; } - if (_hilitedIndex < _candidateCount) { - NSPoint candidateVertices[4]; - rectVertices(_candidatePolygons[_hilitedIndex].body, candidateVertices); - hilitedCandidatePath = squirclePath(candidateVertices, 4, cornerRadius); - } + hilitedCandidatePath = + squirclePath(_candidatePolygons[_hilitedIndex].body, cornerRadius); } } - // Draw paging Rect - _pagingBlock = NSZeroRect; + /*** Paging Rects ***/ _pageUpRect = NSZeroRect; _pageDownRect = NSZeroRect; _expanderRect = NSZeroRect; - if (pagingRange.length > 0) { + if (!_pagingView.hidden) { if (theme.linear) { - _pagingBlock = [self blockRectForRange:pagingRange]; - _pagingBlock.size.width += theme.fullWidth; - _pagingBlock.origin.x = NSMaxX(backgroundRect) - NSWidth(_pagingBlock); + _pagingRect.origin.x = NSMaxX(backgroundRect) - NSWidth(_pagingRect); } else { - _pagingBlock = backgroundRect; + _pagingRect.size.width = NSWidth(backgroundRect); } - _pagingBlock.origin.y = NSMaxY(_candidateBlock); - _pagingBlock.size.height = NSMaxY(backgroundRect) - NSMaxY(_candidateBlock); if (theme.showPaging) { - _pageUpRect = - [self blockRectForRange:NSMakeRange(pagingRange.location, 1)]; + _pageUpRect = [self blockRectForRange:NSMakeRange(0, 1) + inView:_pagingView]; _pageDownRect = - [self blockRectForRange:NSMakeRange(NSMaxRange(pagingRange) - 1, 1)]; - _pageDownRect.origin.x += _marginInsets.left; - _pageDownRect.size.width += ceil(theme.fullWidth * 0.5); - _pageDownRect.origin.y += _marginInsets.top; - _pageUpRect.origin.x += theme.borderInsets.width; + [self blockRectForRange:NSMakeRange(_pagingContents.length - 1, 1) + inView:_pagingView]; + _pageDownRect.origin.x += NSMinX(_pagingRect); + _pageDownRect.size.width += theme.fullWidth; + _pageDownRect.origin.y += NSMinY(_pagingRect); + _pageUpRect.origin.x += NSMinX(_pagingRect); // bypass the bug of getting wrong glyph position when tab is presented _pageUpRect.size.width = NSWidth(_pageDownRect); - _pageUpRect.origin.y += _marginInsets.top; + _pageUpRect.origin.y += NSMinY(_pagingRect); _pageUpRect = - [self backingAlignedRect:NSIntersectionRect(_pageUpRect, _pagingBlock) + [self backingAlignedRect:NSIntersectionRect(_pageUpRect, _pagingRect) options:NSAlignAllEdgesNearest]; _pageDownRect = [self - backingAlignedRect:NSIntersectionRect(_pageDownRect, _pagingBlock) + backingAlignedRect:NSIntersectionRect(_pageDownRect, _pagingRect) options:NSAlignAllEdgesNearest]; } if (theme.tabular) { _expanderRect = - [self blockRectForRange:NSMakeRange(pagingRange.location + - pagingRange.length / 2, - 1)]; - _expanderRect.origin.x += theme.borderInsets.width; + [self blockRectForRange:NSMakeRange(_pagingContents.length / 2, 1) + inView:_pagingView]; + _expanderRect.origin.x += NSMinX(_pagingRect); _expanderRect.size.width += theme.fullWidth; - _expanderRect.origin.y += _marginInsets.top; + _expanderRect.origin.y += NSMinY(_pagingRect); _expanderRect = [self - backingAlignedRect:NSIntersectionRect(_expanderRect, backgroundRect) + backingAlignedRect:NSIntersectionRect(_expanderRect, _pagingRect) options:NSAlignAllEdgesNearest]; } } - // Draw borders + /*** Border Rects ***/ CGFloat outerCornerRadius = fmin(theme.cornerRadius, NSHeight(panelRect) * 0.5); CGFloat innerCornerRadius = - fmax(fmin(theme.hilitedCornerRadius, NSHeight(backgroundRect) * 0.5), - outerCornerRadius - - fmin(theme.borderInsets.width, theme.borderInsets.height)); + clamp(theme.hilitedCornerRadius, + outerCornerRadius - + fmin(theme.borderInsets.width, theme.borderInsets.height), + NSHeight(backgroundRect) * 0.5); NSBezierPath *panelPath, *backgroundPath; - if (!theme.linear || pagingRange.length == 0) { - NSPoint panelVertices[4], backgroundVertices[4]; - rectVertices(panelRect, panelVertices); - rectVertices(backgroundRect, backgroundVertices); - panelPath = squirclePath(panelVertices, 4, outerCornerRadius); - backgroundPath = squirclePath(backgroundVertices, 4, innerCornerRadius); + if (!theme.linear || _pagingView.hidden) { + panelPath = squirclePath(panelRect, outerCornerRadius); + backgroundPath = squirclePath(backgroundRect, innerCornerRadius); } else { - NSPoint panelVertices[6], backgroundVertices[6]; NSRect mainPanelRect = panelRect; - mainPanelRect.size.height -= NSHeight(_pagingBlock); + mainPanelRect.size.height -= NSHeight(_pagingRect); NSRect tailPanelRect = - NSInsetRect(NSOffsetRect(_pagingBlock, 0, theme.borderInsets.height), + NSInsetRect(NSOffsetRect(_pagingRect, 0, theme.borderInsets.height), -theme.borderInsets.width, 0); - textPolygonVertices( + panelPath = squirclePath( (SquirrelTextPolygon){mainPanelRect, tailPanelRect, NSZeroRect}, - panelVertices); - panelPath = squirclePath(panelVertices, 6, outerCornerRadius); + outerCornerRadius); NSRect mainBackgroundRect = backgroundRect; - mainBackgroundRect.size.height -= NSHeight(_pagingBlock); - textPolygonVertices( - (SquirrelTextPolygon){mainBackgroundRect, _pagingBlock, NSZeroRect}, - backgroundVertices); - backgroundPath = squirclePath(backgroundVertices, 6, innerCornerRadius); + mainBackgroundRect.size.height -= NSHeight(_pagingRect); + backgroundPath = squirclePath( + (SquirrelTextPolygon){mainBackgroundRect, _pagingRect, NSZeroRect}, + innerCornerRadius); } NSBezierPath* borderPath = panelPath.copy; [borderPath appendBezierPath:backgroundPath]; @@ -3597,7 +3635,7 @@ - (void)updateLayer { [flip scaleXBy:1 yBy:-1]; NSBezierPath* shapePath = [flip transformBezierPath:panelPath]; - // Set layers + /*** Draw into layers ***/ _shape.path = shapePath.quartzPath; _shape.fillColor = NSColor.whiteColor.CGColor; self.layer.sublayers = nil; @@ -3608,7 +3646,7 @@ - (void)updateLayer { shapeLayer.fillColor = NSColor.whiteColor.CGColor; BackLayers.mask = shapeLayer; if (@available(macOS 10.14, *)) { - BackLayers.opacity = 1.0f - (float)theme.translucency; + BackLayers.opacity = 1.0f - theme.translucency; BackLayers.allowsGroupOpacity = YES; } [self.layer addSublayer:BackLayers]; @@ -3630,16 +3668,14 @@ - (void)updateLayer { } // background color layer CAShapeLayer* backColorLayer = CAShapeLayer.alloc.init; - if ((!NSIsEmptyRect(_preeditBlock) || !NSIsEmptyRect(_pagingBlock) || + if ((!NSIsEmptyRect(_preeditRect) || !NSIsEmptyRect(_pagingRect) || !NSIsEmptyRect(_expanderRect)) && theme.preeditBackColor) { - if (candidateBlockPath) { + if (candidateBlockPath != nil) { NSBezierPath* nonCandidatePath = backgroundPath.copy; [nonCandidatePath appendBezierPath:candidateBlockPath]; backColorLayer.path = nonCandidatePath.quartzPath; backColorLayer.fillRule = kCAFillRuleEvenOdd; - backColorLayer.strokeColor = theme.preeditBackColor.CGColor; - backColorLayer.lineWidth = 0.5; backColorLayer.fillColor = theme.preeditBackColor.CGColor; [BackLayers addSublayer:backColorLayer]; // candidate block's background color layer @@ -3649,15 +3685,11 @@ - (void)updateLayer { [BackLayers addSublayer:candidateLayer]; } else { backColorLayer.path = backgroundPath.quartzPath; - backColorLayer.strokeColor = theme.preeditBackColor.CGColor; - backColorLayer.lineWidth = 0.5; backColorLayer.fillColor = theme.preeditBackColor.CGColor; [BackLayers addSublayer:backColorLayer]; } } else { backColorLayer.path = backgroundPath.quartzPath; - backColorLayer.strokeColor = theme.backColor.CGColor; - backColorLayer.lineWidth = 0.5; backColorLayer.fillColor = theme.backColor.CGColor; [BackLayers addSublayer:backColorLayer]; } @@ -3675,54 +3707,60 @@ - (void)updateLayer { ForeLayers.mask = maskLayer; [self.layer addSublayer:ForeLayers]; // highlighted preedit layer - if (hilitedPreeditPath && theme.hilitedPreeditBackColor) { + if (hilitedPreeditPath != nil && theme.hilitedPreeditBackColor != nil) { CAShapeLayer* hilitedPreeditLayer = CAShapeLayer.alloc.init; hilitedPreeditLayer.path = hilitedPreeditPath.quartzPath; hilitedPreeditLayer.fillColor = theme.hilitedPreeditBackColor.CGColor; [ForeLayers addSublayer:hilitedPreeditLayer]; } // highlighted candidate layer - if (hilitedCandidatePath && theme.hilitedCandidateBackColor) { - if (activePagePath) { - CAShapeLayer* activePageLayer = CAShapeLayer.alloc.init; - activePageLayer.path = activePagePath.quartzPath; - activePageLayer.fillColor = - [[theme.hilitedCandidateBackColor - blendedColorWithFraction:0.8 - ofColor:[theme.backColor - colorWithAlphaComponent:1.0]] - colorWithAlphaComponent:theme.backColor.alphaComponent] + if (!_scrollView.hidden) { + _documentView.layer.sublayers = nil; + if (hilitedCandidatePath != nil && theme.hilitedCandidateBackColor != nil) { + if (activePagePath != nil) { + CAShapeLayer* activePageLayer = CAShapeLayer.alloc.init; + activePageLayer.path = activePagePath.quartzPath; + activePageLayer.fillColor = + [[theme.hilitedCandidateBackColor + blendedColorWithFraction:0.8 + ofColor:[theme.backColor + colorWithAlphaComponent:1.0]] + colorWithAlphaComponent:theme.backColor.alphaComponent] + .CGColor; + if (@available(macOS 10.14, *)) { + activePageLayer.opacity = 1.0f - theme.translucency; + } + [_documentView.layer addSublayer:activePageLayer]; + } + CAShapeLayer* hilitedCandidateLayer = CAShapeLayer.alloc.init; + hilitedCandidateLayer.path = hilitedCandidatePath.quartzPath; + hilitedCandidateLayer.fillColor = theme.hilitedCandidateBackColor.CGColor; + [_documentView.layer addSublayer:hilitedCandidateLayer]; + } + // grids (in candidate block) layer + if (gridPath != nil) { + CAShapeLayer* gridLayer = CAShapeLayer.alloc.init; + gridLayer.path = gridPath.quartzPath; + gridLayer.lineWidth = 1.0; + gridLayer.strokeColor = + [theme.commentForeColor blendedColorWithFraction:0.8 + ofColor:theme.backColor] .CGColor; - [BackLayers addSublayer:activePageLayer]; + [_documentView.layer addSublayer:gridLayer]; } - CAShapeLayer* hilitedCandidateLayer = CAShapeLayer.alloc.init; - hilitedCandidateLayer.path = hilitedCandidatePath.quartzPath; - hilitedCandidateLayer.fillColor = theme.hilitedCandidateBackColor.CGColor; - [ForeLayers addSublayer:hilitedCandidateLayer]; + [_documentView.layer addSublayer:_textView.layer]; } + // function buttons (page up, page down, backspace) layer if (_functionButton != kVoidSymbol) { - CAShapeLayer* functionButtonLayer = [self getFunctionButtonLayer]; - if (functionButtonLayer) { + if (CAShapeLayer* functionButtonLayer = [self getFunctionButtonLayer]) { [ForeLayers addSublayer:functionButtonLayer]; } } - // grids (in candidate block) layer - if (gridPath) { - CAShapeLayer* gridLayer = CAShapeLayer.alloc.init; - gridLayer.path = gridPath.quartzPath; - gridLayer.lineWidth = 1.0; - gridLayer.strokeColor = - [theme.commentForeColor blendedColorWithFraction:0.8 - ofColor:theme.backColor] - .CGColor; - [ForeLayers addSublayer:gridLayer]; - } // logo at the beginning for status message - if (NSIsEmptyRect(_preeditBlock) && NSIsEmptyRect(_candidateBlock)) { + if (!_statusView.hidden) { CALayer* logoLayer = CALayer.alloc.init; - CGFloat height = - [theme.statusAttrs[NSParagraphStyleAttributeName] minimumLineHeight]; + CGFloat height = theme.statusParagraphStyle.minimumLineHeight; NSRect logoRect = NSMakeRect(backgroundRect.origin.x, backgroundRect.origin.y, height, height); logoLayer.frame = [self @@ -3742,39 +3780,43 @@ - (void)updateLayer { } - (SquirrelIndex)getIndexFromMouseSpot:(NSPoint)spot { - NSPoint point = [self convertPoint:spot fromView:nil]; - if (NSMouseInRect(point, self.bounds, YES)) { - if (NSMouseInRect(point, _preeditBlock, YES)) { - return NSMouseInRect(point, _deleteBackRect, YES) ? kBackSpaceKey - : kCodeInputArea; + if (NSMouseInRect(spot, self.bounds, YES)) { + if (NSMouseInRect(spot, _preeditRect, YES)) { + return NSMouseInRect(spot, _deleteBackRect, YES) ? kBackSpaceKey + : kCodeInputArea; } - if (NSMouseInRect(point, _expanderRect, YES)) { + if (NSMouseInRect(spot, _expanderRect, YES)) { return kExpandButton; } - if (NSMouseInRect(point, _pageUpRect, YES)) { + if (NSMouseInRect(spot, _pageUpRect, YES)) { return kPageUpKey; } - if (NSMouseInRect(point, _pageDownRect, YES)) { + if (NSMouseInRect(spot, _pageDownRect, YES)) { return kPageDownKey; } - for (NSUInteger i = 0; i < _candidateCount; ++i) { - if (NSMouseInRect(point, _candidatePolygons[i].body, YES) || - NSMouseInRect(point, _candidatePolygons[i].leading, YES) || - NSMouseInRect(point, _candidatePolygons[i].trailing, YES)) { - return i; + if (NSMouseInRect(spot, _candidatesRect, YES)) { + spot.x += NSMinX(_scrollView.documentVisibleRect) - + NSMinX(_candidatesRect) - ceil(_currentTheme.fullWidth * 0.5); + spot.y += NSMinY(_scrollView.documentVisibleRect) - + NSMinY(_candidatesRect) - floor(_currentTheme.linespace * 0.5); + for (NSUInteger i = 0; i < _candidateCount; ++i) { + if (NSMouseInRect(spot, _candidatePolygons[i].body, YES) || + NSMouseInRect(spot, _candidatePolygons[i].head, YES) || + NSMouseInRect(spot, _candidatePolygons[i].tail, YES)) { + return (SquirrelIndex)i; + } } } } - return NSNotFound; + return kVoidSymbol; } @end // SquirrelView /* In order to put SquirrelPanel above client app windows, - SquirrelPanel needs to be assigned a window level higher - than kCGHelpWindowLevelKey that the system tooltips use. - This class makes system-alike tooltips above SquirrelPanel - */ + SquirrelPanel needs to be assigned a window level higher + than kCGHelpWindowLevelKey that the system tooltips use. + This class makes system-alike tooltips above SquirrelPanel */ @interface SquirrelToolTip : NSWindow @property(nonatomic, strong, readonly, nullable, direct) NSTimer* displayTimer; @@ -3794,11 +3836,10 @@ @implementation SquirrelToolTip { } - (instancetype)init { - self = [super initWithContentRect:NSZeroRect - styleMask:NSWindowStyleMaskNonactivatingPanel - backing:NSBackingStoreBuffered - defer:YES]; - if (self) { + if (self = [super initWithContentRect:NSZeroRect + styleMask:NSWindowStyleMaskNonactivatingPanel + backing:NSBackingStoreBuffered + defer:YES]) { self.backgroundColor = NSColor.clearColor; self.opaque = YES; self.hasShadow = YES; @@ -4036,9 +4077,10 @@ - (void)observeValueForKeyPath:(NSString*)keyPath [clientAppearance bestMatchFromAppearancesWithNames:@[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]]; - SquirrelAppear appear = - [appearName isEqualToString:NSAppearanceNameDarkAqua] ? darkAppear - : defaultAppear; + SquirrelAppearance appear = + [appearName isEqualToString:NSAppearanceNameDarkAqua] + ? kDarkAppearance + : kDefaultAppearance; if (appear != _view.appear) { _view.appear = appear; self.appearance = [NSAppearance appearanceNamed:appearName]; @@ -4056,12 +4098,11 @@ - (void)observeValueForKeyPath:(NSString*)keyPath } - (instancetype)init { - self = [super initWithContentRect:_IbeamRect - styleMask:NSWindowStyleMaskNonactivatingPanel | - NSWindowStyleMaskBorderless - backing:NSBackingStoreBuffered - defer:YES]; - if (self) { + if (self = [super initWithContentRect:_IbeamRect + styleMask:NSWindowStyleMaskNonactivatingPanel | + NSWindowStyleMaskBorderless + backing:NSBackingStoreBuffered + defer:YES]) { self.level = CGWindowLevelForKey(kCGCursorWindowLevelKey) - 100; self.hasShadow = NO; self.opaque = NO; @@ -4069,8 +4110,8 @@ - (instancetype)init { self.delegate = self; self.acceptsMouseMovedEvents = YES; - NSView* contentView = NSView.alloc.init; - _view = [SquirrelView.alloc initWithFrame:self.contentView.bounds]; + NSFlippedView* contentView = NSFlippedView.alloc.init; + _view = SquirrelView.alloc.init; if (@available(macOS 10.14, *)) { _back = NSVisualEffectView.alloc.init; _back.blendingMode = NSVisualEffectBlendingModeBehindWindow; @@ -4082,7 +4123,10 @@ - (instancetype)init { [contentView addSubview:_back]; } [contentView addSubview:_view]; - [contentView addSubview:_view.textView]; + [contentView addSubview:_view.preeditView]; + [contentView addSubview:_view.scrollView]; + [contentView addSubview:_view.pagingView]; + [contentView addSubview:_view.statusView]; self.contentView = contentView; _optionSwitcher = SquirrelOptionSwitcher.alloc.init; @@ -4140,50 +4184,46 @@ - (NSUInteger)candidateIndexOnDirection:(SquirrelIndex)arrowKey { // handle mouse interaction events - (void)sendEvent:(NSEvent*)event { SquirrelTheme* theme = _view.currentTheme; - static SquirrelIndex cursorIndex = NSNotFound; + static SquirrelIndex cursorIndex = kVoidSymbol; switch (event.type) { case NSEventTypeLeftMouseDown: if (event.clickCount == 1 && cursorIndex == kCodeInputArea) { - NSPoint spot = - [_view.textView convertPoint:self.mouseLocationOutsideOfEventStream - fromView:nil]; + NSPoint spot = [_view.preeditView + convertPoint:self.mouseLocationOutsideOfEventStream + fromView:nil]; NSUInteger inputIndex = - [_view.textView characterIndexForInsertionAtPoint:spot]; + [_view.preeditView characterIndexForInsertionAtPoint:spot]; if (inputIndex == 0) { [_inputController performAction:kPROCESS onIndex:kHomeKey]; } else if (inputIndex < _caretPos) { - [_inputController moveCursor:_caretPos - toPosition:inputIndex - inlinePreedit:NO - inlineCandidate:NO]; - } else if (inputIndex >= _view.preeditRange.length) { + [_inputController moveCursor:_caretPos toPosition:inputIndex]; + } else if (inputIndex >= _view.preeditContents.length - 2) { [_inputController performAction:kPROCESS onIndex:kEndKey]; } else if (inputIndex > _caretPos + 1) { - [_inputController moveCursor:_caretPos - toPosition:inputIndex - 1 - inlinePreedit:NO - inlineCandidate:NO]; + [_inputController moveCursor:_caretPos toPosition:inputIndex - 1]; } } break; case NSEventTypeLeftMouseUp: - if (event.clickCount == 1 && cursorIndex != NSNotFound) { + if (event.clickCount == 1 && cursorIndex != kVoidSymbol) { if (cursorIndex == _highlightedIndex) { - [_inputController performAction:kSELECT - onIndex:cursorIndex + _indexRange.location]; + [_inputController + performAction:kSELECT + onIndex:(SquirrelIndex)(cursorIndex + + _indexRange.location)]; } else if (cursorIndex == _functionButton) { if (cursorIndex == kExpandButton) { if (_locked) { self.locked = NO; - [_view.textStorage + [_view.pagingContents replaceCharactersInRange:NSMakeRange( - _view.pagingRange.location + - _view.pagingRange.length / 2, + _view.pagingContents.length / 2, 1) withAttributedString:_view.expanded ? theme.symbolCompress : theme.symbolExpand]; - _view.textView.needsDisplayInRect = - [_view convertRect:_view.expanderRect toView:_view.textView]; + _view.pagingView.needsDisplayInRect = + [_view convertRect:_view.expanderRect + toView:_view.pagingView]; } else { self.expanded = !_view.expanded; self.sectionNum = 0; @@ -4194,10 +4234,12 @@ - (void)sendEvent:(NSEvent*)event { } break; case NSEventTypeRightMouseUp: - if (event.clickCount == 1 && cursorIndex != NSNotFound) { + if (event.clickCount == 1 && cursorIndex != kVoidSymbol) { if (cursorIndex == _highlightedIndex) { - [_inputController performAction:kDELETE - onIndex:cursorIndex + _indexRange.location]; + [_inputController + performAction:kDELETE + onIndex:(SquirrelIndex)(cursorIndex + + _indexRange.location)]; } else if (cursorIndex == _functionButton) { switch (_functionButton) { case kPageUpKey: @@ -4208,24 +4250,21 @@ - (void)sendEvent:(NSEvent*)event { break; case kExpandButton: self.locked = !_locked; - [_view.textStorage + [_view.pagingContents replaceCharactersInRange:NSMakeRange( - _view.pagingRange.location + - _view.pagingRange.length / 2, + _view.pagingContents.length / 2, 1) withAttributedString:_locked ? theme.symbolLock : _view.expanded ? theme.symbolCompress : theme.symbolExpand]; - [_view.textStorage + [_view.pagingContents addAttribute:NSForegroundColorAttributeName value:theme.hilitedPreeditForeColor - range:NSMakeRange(_view.pagingRange.location + - _view.pagingRange.length / 2, - 1)]; - [_view.textView + range:NSMakeRange(_view.pagingContents.length / 2, 1)]; + [_view.pagingView setNeedsDisplayInRect:[_view convertRect:_view.expanderRect - toView:_view.textView] + toView:_view.pagingView] avoidAdditionalLayout:YES]; [_inputController performAction:kPROCESS onIndex:kLockButton]; break; @@ -4246,32 +4285,32 @@ - (void)sendEvent:(NSEvent*)event { NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagOption; cursorIndex = - [_view getIndexFromMouseSpot:self.mouseLocationOutsideOfEventStream]; + [_view getIndexFromMouseSpot: + [_view convertPoint:self.mouseLocationOutsideOfEventStream + fromView:nil]]; if (cursorIndex != _highlightedIndex && cursorIndex != _functionButton) { [_toolTip hide]; } else if (noDelay) { [_toolTip.displayTimer fire]; } - if (cursorIndex >= 0 && cursorIndex < _indexRange.length && + if (cursorIndex >= 0 && cursorIndex < _view.candidateCount && _highlightedIndex != cursorIndex) { [self highlightFunctionButton:kVoidSymbol delayToolTip:!noDelay]; if (theme.linear && _view.truncated[cursorIndex]) { [_toolTip - showWithToolTip: - [_view.textStorage.mutableString - substringWithRange:NSMakeRange( - _view.candidateRanges[cursorIndex] - .location, - _view.candidateRanges[cursorIndex] - .length)] + showWithToolTip:[_view.contents.mutableString + substringWithRange: + RangeCandidate( + _view.candidateRanges[cursorIndex])] withDelay:NO]; } else if (noDelay) { [_toolTip showWithToolTip:NSLocalizedString(@"candidate", nil) withDelay:!noDelay]; } self.sectionNum = cursorIndex / theme.pageSize; - [_inputController performAction:kHIGHLIGHT - onIndex:cursorIndex + _indexRange.location]; + [_inputController + performAction:kHIGHLIGHT + onIndex:(SquirrelIndex)(cursorIndex + _indexRange.location)]; } else if ((cursorIndex == kPageUpKey || cursorIndex == kPageDownKey || cursorIndex == kExpandButton || cursorIndex == kBackSpaceKey) && @@ -4283,49 +4322,106 @@ - (void)sendEvent:(NSEvent*)event { [_toolTip.displayTimer invalidate]; break; case NSEventTypeLeftMouseDragged: - // reset the remember_size references after moving the panel + // reset the `remember_size` references after moving the panel _maxSize = NSZeroSize; [self performWindowDragWithEvent:event]; break; case NSEventTypeScrollWheel: { - CGFloat scrollThreshold = - theme.candidateParagraphStyle.minimumLineHeight + - theme.candidateParagraphStyle.lineSpacing; - static NSPoint scrollLocus = NSZeroPoint; + CGFloat scrollThreshold = _view.scrollView.lineScroll; + static NSPoint scrollLocus; + static BOOL scrollByLine; if (event.phase == NSEventPhaseBegan) { scrollLocus = NSZeroPoint; + scrollByLine = NO; } else if ((event.phase == NSEventPhaseNone || event.momentumPhase == NSEventPhaseNone) && !isnan(scrollLocus.x) && !isnan(scrollLocus.y)) { + CGFloat scrollDistance = 0.0; // determine scrolling direction by confining to sectors within ±30º of // any axis if (fabs(event.scrollingDeltaX) > fabs(event.scrollingDeltaY) * sqrt(3.0)) { - scrollLocus.x += event.scrollingDeltaX * - (event.hasPreciseScrollingDeltas ? 1 : 10); + scrollDistance = + event.scrollingDeltaX * + (event.hasPreciseScrollingDeltas ? 1 : scrollThreshold); + scrollLocus.x += scrollDistance; } else if (fabs(event.scrollingDeltaY) > fabs(event.scrollingDeltaX) * sqrt(3.0)) { - scrollLocus.y += event.scrollingDeltaY * - (event.hasPreciseScrollingDeltas ? 1 : 10); + scrollDistance = + event.scrollingDeltaY * + (event.hasPreciseScrollingDeltas ? 1 : scrollThreshold); + scrollLocus.y += scrollDistance; } // compare accumulated locus length against threshold and limit paging // to max once if (scrollLocus.x > scrollThreshold) { - [_inputController - performAction:kPROCESS - onIndex:(theme.vertical ? kPageDownKey : kPageUpKey)]; - scrollLocus = NSMakePoint(NAN, NAN); + if (theme.vertical && NSMaxY(_view.scrollView.documentVisibleRect) < + NSMaxY(_view.documentRect) - 0.1) { + scrollByLine = YES; + NSPoint origin = _view.scrollView.contentView.bounds.origin; + origin.y += fmin(scrollDistance, + NSMaxY(_view.documentRect) - + NSMaxY(_view.scrollView.documentVisibleRect)); + [_view.scrollView.contentView scrollToPoint:origin]; + _view.scrollView.verticalScroller.doubleValue = + NSMinY(_view.scrollView.documentVisibleRect) / + _view.clippedHeight; + } else if (!scrollByLine) { + [_inputController + performAction:kPROCESS + onIndex:(theme.vertical ? kPageDownKey : kPageUpKey)]; + scrollLocus = NSMakePoint(NAN, NAN); + } } else if (scrollLocus.y > scrollThreshold) { - [_inputController performAction:kPROCESS onIndex:kPageUpKey]; - scrollLocus = NSMakePoint(NAN, NAN); + if (NSMinY(_view.scrollView.documentVisibleRect) > + NSMinY(_view.documentRect) + 0.1) { + scrollByLine = YES; + NSPoint origin = _view.scrollView.contentView.bounds.origin; + origin.y -= fmin(scrollDistance, + NSMinY(_view.scrollView.documentVisibleRect) - + NSMinY(_view.documentRect)); + [_view.scrollView.contentView scrollToPoint:origin]; + _view.scrollView.verticalScroller.doubleValue = + NSMinY(_view.scrollView.documentVisibleRect) / + _view.clippedHeight; + } else if (!scrollByLine) { + [_inputController performAction:kPROCESS onIndex:kPageUpKey]; + scrollLocus = NSMakePoint(NAN, NAN); + } } else if (scrollLocus.x < -scrollThreshold) { - [_inputController - performAction:kPROCESS - onIndex:(theme.vertical ? kPageUpKey : kPageDownKey)]; - scrollLocus = NSMakePoint(NAN, NAN); + if (theme.vertical && NSMinY(_view.scrollView.documentVisibleRect) > + NSMinY(_view.documentRect) + 0.1) { + scrollByLine = YES; + NSPoint origin = _view.scrollView.contentView.bounds.origin; + origin.y += fmax(scrollDistance, + NSMinY(_view.documentRect) - + NSMinY(_view.scrollView.documentVisibleRect)); + [_view.scrollView.contentView scrollToPoint:origin]; + _view.scrollView.verticalScroller.doubleValue = + NSMinY(_view.scrollView.documentVisibleRect) / + _view.clippedHeight; + } else if (!scrollByLine) { + [_inputController + performAction:kPROCESS + onIndex:(theme.vertical ? kPageUpKey : kPageDownKey)]; + scrollLocus = NSMakePoint(NAN, NAN); + } } else if (scrollLocus.y < -scrollThreshold) { - [_inputController performAction:kPROCESS onIndex:kPageDownKey]; - scrollLocus = NSMakePoint(NAN, NAN); + if (NSMaxY(_view.scrollView.documentVisibleRect) < + NSMaxY(_view.documentRect) - 0.1) { + scrollByLine = YES; + NSPoint origin = _view.scrollView.contentView.bounds.origin; + origin.y -= fmax(scrollDistance, + NSMaxY(_view.scrollView.documentVisibleRect) - + NSMaxY(_view.documentRect)); + [_view.scrollView.contentView scrollToPoint:origin]; + _view.scrollView.verticalScroller.doubleValue = + NSMinY(_view.scrollView.documentVisibleRect) / + _view.clippedHeight; + } else if (!scrollByLine) { + [_inputController performAction:kPROCESS onIndex:kPageDownKey]; + scrollLocus = NSMakePoint(NAN, NAN); + } } } } break; @@ -4347,66 +4443,44 @@ - (void)highlightCandidate:(NSUInteger)highlightedIndex NSUInteger priorIndex = i + priorSectionNum * theme.pageSize; if ((_sectionNum != priorSectionNum || priorIndex == priorHilitedIndex) && priorIndex < _indexRange.length) { + SquirrelCandidateRanges priorRange = _view.candidateRanges[priorIndex]; NSColor* labelColor = priorIndex == priorHilitedIndex && _sectionNum == priorSectionNum ? theme.labelForeColor : theme.dimmedLabelForeColor; - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:labelColor - range:NSMakeRange(_view.candidateRanges[priorIndex].location, - _view.candidateRanges[priorIndex].text)]; + [_view.contents addAttribute:NSForegroundColorAttributeName + value:labelColor + range:RangeLabel(priorRange)]; if (priorIndex == priorHilitedIndex) { - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:theme.textForeColor - range:NSMakeRange( - _view.candidateRanges[priorIndex].location + - _view.candidateRanges[priorIndex].text, - _view.candidateRanges[priorIndex].comment - - _view.candidateRanges[priorIndex].text)]; - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:theme.commentForeColor - range:NSMakeRange( - _view.candidateRanges[priorIndex].location + - _view.candidateRanges[priorIndex].comment, - _view.candidateRanges[priorIndex].length - - _view.candidateRanges[priorIndex].comment)]; + [_view.contents addAttribute:NSForegroundColorAttributeName + value:theme.textForeColor + range:RangeText(priorRange)]; + [_view.contents addAttribute:NSForegroundColorAttributeName + value:theme.commentForeColor + range:RangeComment(priorRange)]; } } NSUInteger newIndex = i + _sectionNum * theme.pageSize; - if ((_sectionNum != priorSectionNum || newIndex == _highlightedIndex) && + if ((_sectionNum != priorSectionNum || newIndex == highlightedIndex) && newIndex < _indexRange.length) { - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:newIndex == _highlightedIndex - ? theme.hilitedLabelForeColor - : theme.labelForeColor - range:NSMakeRange(_view.candidateRanges[newIndex].location, - _view.candidateRanges[newIndex].text)]; - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:newIndex == _highlightedIndex - ? theme.hilitedTextForeColor - : theme.textForeColor - range:NSMakeRange(_view.candidateRanges[newIndex].location + - _view.candidateRanges[newIndex].text, - _view.candidateRanges[newIndex].comment - - _view.candidateRanges[newIndex].text)]; - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:newIndex == _highlightedIndex - ? theme.hilitedCommentForeColor - : theme.commentForeColor - range:NSMakeRange( - _view.candidateRanges[newIndex].location + - _view.candidateRanges[newIndex].comment, - _view.candidateRanges[newIndex].length - - _view.candidateRanges[newIndex].comment)]; + SquirrelCandidateRanges newRange = _view.candidateRanges[newIndex]; + NSColor* labelColor = newIndex == highlightedIndex + ? theme.hilitedLabelForeColor + : theme.labelForeColor; + [_view.contents addAttribute:NSForegroundColorAttributeName + value:labelColor + range:RangeLabel(newRange)]; + if (newIndex == highlightedIndex) { + [_view.contents addAttribute:NSForegroundColorAttributeName + value:theme.hilitedTextForeColor + range:RangeText(newRange)]; + [_view.contents addAttribute:NSForegroundColorAttributeName + value:theme.hilitedCommentForeColor + range:RangeComment(newRange)]; + } } } - [_view highlightCandidate:_highlightedIndex]; + [_view highlightCandidate:highlightedIndex]; } - (void)highlightFunctionButton:(SquirrelIndex)functionButton @@ -4417,61 +4491,55 @@ - (void)highlightFunctionButton:(SquirrelIndex)functionButton SquirrelTheme* theme = _view.currentTheme; switch (_functionButton) { case kPageUpKey: - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:theme.preeditForeColor - range:NSMakeRange(_view.pagingRange.location, 1)]; + [_view.pagingContents addAttribute:NSForegroundColorAttributeName + value:theme.preeditForeColor + range:NSMakeRange(0, 1)]; break; case kPageDownKey: - [_view.textStorage + [_view.pagingContents addAttribute:NSForegroundColorAttributeName value:theme.preeditForeColor - range:NSMakeRange(NSMaxRange(_view.pagingRange) - 1, 1)]; + range:NSMakeRange(_view.pagingContents.length - 1, 1)]; break; case kExpandButton: - [_view.textStorage + [_view.pagingContents addAttribute:NSForegroundColorAttributeName value:theme.preeditForeColor - range:NSMakeRange(_view.pagingRange.location + - _view.pagingRange.length / 2, - 1)]; + range:NSMakeRange(_view.pagingContents.length / 2, 1)]; break; case kBackSpaceKey: - [_view.textStorage + [_view.preeditContents addAttribute:NSForegroundColorAttributeName value:theme.preeditForeColor - range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, 1)]; + range:NSMakeRange(_view.preeditContents.length - 1, 1)]; break; } _functionButton = functionButton; switch (_functionButton) { case kPageUpKey: - [_view.textStorage - addAttribute:NSForegroundColorAttributeName - value:theme.hilitedPreeditForeColor - range:NSMakeRange(_view.pagingRange.location, 1)]; + [_view.pagingContents addAttribute:NSForegroundColorAttributeName + value:theme.hilitedPreeditForeColor + range:NSMakeRange(0, 1)]; functionButton = _pageNum == 0 ? kHomeKey : kPageUpKey; [_toolTip showWithToolTip:NSLocalizedString( _pageNum == 0 ? @"home" : @"page_up", nil) withDelay:delay]; break; case kPageDownKey: - [_view.textStorage + [_view.pagingContents addAttribute:NSForegroundColorAttributeName value:theme.hilitedPreeditForeColor - range:NSMakeRange(NSMaxRange(_view.pagingRange) - 1, 1)]; + range:NSMakeRange(_view.pagingContents.length - 1, 1)]; functionButton = _finalPage ? kEndKey : kPageDownKey; [_toolTip showWithToolTip:NSLocalizedString( _finalPage ? @"end" : @"page_down", nil) withDelay:delay]; break; case kExpandButton: - [_view.textStorage + [_view.pagingContents addAttribute:NSForegroundColorAttributeName value:theme.hilitedPreeditForeColor - range:NSMakeRange(_view.pagingRange.location + - _view.pagingRange.length / 2, - 1)]; + range:NSMakeRange(_view.pagingContents.length / 2, 1)]; functionButton = _locked ? kLockButton : _view.expanded ? kCompressButton : kExpandButton; @@ -4482,10 +4550,10 @@ - (void)highlightFunctionButton:(SquirrelIndex)functionButton withDelay:delay]; break; case kBackSpaceKey: - [_view.textStorage + [_view.preeditContents addAttribute:NSForegroundColorAttributeName value:theme.hilitedPreeditForeColor - range:NSMakeRange(NSMaxRange(_view.preeditRange) - 1, 1)]; + range:NSMakeRange(_view.preeditContents.length - 1, 1)]; functionButton = _caretPos == NSNotFound || _caretPos == 0 ? kEscapeKey : kBackSpaceKey; @@ -4516,15 +4584,38 @@ - (void)updateDisplayParameters __attribute__((objc_direct)) { _initPosition = YES; _maxSize = NSZeroSize; - // size limits on textContainer - NSRect screenRect = _screen.visibleFrame; SquirrelTheme* theme = _view.currentTheme; _view.textView.layoutOrientation = (NSTextLayoutOrientation)theme.vertical; + _view.preeditView.layoutOrientation = (NSTextLayoutOrientation)theme.vertical; + _view.pagingView.layoutOrientation = (NSTextLayoutOrientation)theme.vertical; + _view.statusView.layoutOrientation = (NSTextLayoutOrientation)theme.vertical; // rotate the view, the core in vertical mode! - self.contentView.boundsRotation = theme.vertical ? -90.0 : 0.0; + self.contentView.boundsRotation = theme.vertical ? 90.0 : 0.0; _view.textView.boundsRotation = 0.0; + _view.preeditView.boundsRotation = 0.0; + _view.pagingView.boundsRotation = 0.0; + _view.statusView.boundsRotation = 0.0; _view.textView.boundsOrigin = NSZeroPoint; + _view.preeditView.boundsOrigin = NSZeroPoint; + _view.pagingView.boundsOrigin = NSZeroPoint; + _view.statusView.boundsOrigin = NSZeroPoint; + + _view.scrollView.lineScroll = theme.candidateParagraphStyle.minimumLineHeight; + if (@available(macOS 12.0, *)) { + SquirrelTextLayoutManager* textLayoutManager = + (SquirrelTextLayoutManager*)_view.textView.textLayoutManager; + textLayoutManager.contentBlock = + theme.linear ? kLinearCandidatesBlock : kStackedCandidatesBlock; + } else { + SquirrelLayoutManager* layoutManager = + (SquirrelLayoutManager*)_view.textView.layoutManager; + layoutManager.contentBlock = + theme.linear ? kLinearCandidatesBlock : kStackedCandidatesBlock; + ; + } + // size limits on textContainer + NSRect screenRect = _screen.visibleFrame; CGFloat textWidthRatio = fmin(0.8, 1.0 / (theme.vertical ? 4 : 3) + [theme.textAttrs[NSFontAttributeName] pointSize] / 144.0); @@ -4541,17 +4632,19 @@ - (void)updateDisplayParameters __attribute__((objc_direct)) { (theme.fullWidth * 2) - theme.fullWidth; } - CGFloat textHeightLimit = - ceil((theme.vertical ? NSWidth(screenRect) : NSHeight(screenRect)) * 0.8 - - theme.borderInsets.height * 2 - theme.linespace); - _view.textView.textContainer.size = - NSMakeSize(_textWidthLimit, textHeightLimit); + _view.textView.textContainer.size = NSMakeSize(_textWidthLimit, CGFLOAT_MAX); + _view.preeditView.textContainer.size = + NSMakeSize(_textWidthLimit, CGFLOAT_MAX); + _view.pagingView.textContainer.size = + NSMakeSize(_textWidthLimit, CGFLOAT_MAX); + _view.statusView.textContainer.size = + NSMakeSize(_textWidthLimit, CGFLOAT_MAX); // opacity and transluecency + self.alphaValue = theme.opacity; if (@available(macOS 10.14, *)) { - _back.hidden = theme.translucency < 0.001; + _back.hidden = theme.translucency < 0.001f; } - self.alphaValue = theme.opacity; // resize background image, if any if (theme.backImage.valid) { @@ -4576,7 +4669,7 @@ - (void)show __attribute__((objc_direct)) { } // Break line if the text is too long, based on screen size. SquirrelTheme* theme = _view.currentTheme; - NSEdgeInsets insets = _view.marginInsets; + NSSize border = theme.borderInsets; CGFloat textWidthRatio = fmin(0.8, 1.0 / (theme.vertical ? 4 : 3) + [theme.textAttrs[NSFontAttributeName] pointSize] / 144.0); @@ -4586,7 +4679,6 @@ - (void)show __attribute__((objc_direct)) { // squirrel panel position BOOL sweepVertical = NSWidth(_IbeamRect) > NSHeight(_IbeamRect); NSRect contentRect = _view.contentRect; - contentRect.size.width -= _view.trailPadding; // fixed line length (text width), but not applicable to status message if (theme.lineLength > 0.1 && _statusMessage == nil) { contentRect.size.width = _textWidthLimit; @@ -4594,35 +4686,32 @@ - (void)show __attribute__((objc_direct)) { // remember panel size (fix the top leading anchor of the panel in screen // coordiantes) but only when the text would expand on the side of upstream // (i.e. towards the beginning of text) - if (theme.rememberSize && _statusMessage == nil) { - if (theme.lineLength < 0.1 && - (theme.vertical - ? (sweepVertical - ? (NSMinY(_IbeamRect) - - fmax(NSWidth(contentRect), _maxSize.width) - - insets.right < - NSMinY(screenRect)) - : (NSMinY(_IbeamRect) - kOffsetGap - - NSHeight(screenRect) * textWidthRatio - insets.left - - insets.right < - NSMinY(screenRect))) - : (sweepVertical - ? (NSMinX(_IbeamRect) - kOffsetGap - - NSWidth(screenRect) * textWidthRatio - insets.left - - insets.right >= - NSMinX(screenRect)) - : (NSMaxX(_IbeamRect) + - fmax(NSWidth(contentRect), _maxSize.width) + - insets.right > - NSMaxX(screenRect))))) { + if (theme.rememberSize && _view.statusView.hidden) { + if (theme.lineLength < 0.1 && theme.vertical + ? sweepVertical ? (NSMinY(_IbeamRect) - + fmax(NSWidth(contentRect), _maxSize.width) - + border.width - floor(theme.fullWidth * 0.5) < + NSMinY(screenRect)) + : (NSMinY(_IbeamRect) - kOffsetGap - + NSHeight(screenRect) * textWidthRatio - + border.width * 2 - theme.fullWidth < + NSMinY(screenRect)) + : sweepVertical + ? (NSMinX(_IbeamRect) - kOffsetGap - + NSWidth(screenRect) * textWidthRatio - border.width * 2 - + theme.fullWidth >= + NSMinX(screenRect)) + : (NSMaxX(_IbeamRect) + fmax(NSWidth(contentRect), _maxSize.width) + + border.width + floor(theme.fullWidth * 0.5) > + NSMaxX(screenRect))) { if (NSWidth(contentRect) >= _maxSize.width) { _maxSize.width = NSWidth(contentRect); } else { contentRect.size.width = _maxSize.width; } } - CGFloat textHeight = fmax(NSHeight(contentRect), _maxSize.height) + - insets.top + insets.bottom; + CGFloat textHeight = + fmax(NSHeight(contentRect), _maxSize.height) + border.height * 2; if (theme.vertical ? (NSMinX(_IbeamRect) - textHeight - (sweepVertical ? kOffsetGap : 0) < NSMinX(screenRect)) @@ -4642,14 +4731,13 @@ - (void)show __attribute__((objc_direct)) { // following system UI, middle-align status message with cursor _initPosition = YES; if (theme.vertical) { - windowRect.size.width = - NSHeight(contentRect) + insets.top + insets.bottom; + windowRect.size.width = NSHeight(contentRect) + border.height * 2; windowRect.size.height = - NSWidth(contentRect) + insets.left + insets.right; + NSWidth(contentRect) + border.width * 2 + theme.fullWidth; } else { - windowRect.size.width = NSWidth(contentRect) + insets.left + insets.right; - windowRect.size.height = - NSHeight(contentRect) + insets.top + insets.bottom; + windowRect.size.width = + NSWidth(contentRect) + border.width * 2 + theme.fullWidth; + windowRect.size.height = NSHeight(contentRect) + border.height * 2; } if (sweepVertical) { // vertically centre-align (MidY) in screen coordinates @@ -4665,21 +4753,21 @@ - (void)show __attribute__((objc_direct)) { } else { if (theme.vertical) { // anchor is the top right corner in screen coordinates (MaxX, MaxY) - windowRect = - NSMakeRect(NSMaxX(self.frame) - NSHeight(contentRect) - insets.top - - insets.bottom, - NSMaxY(self.frame) - NSWidth(contentRect) - insets.left - - insets.right, - NSHeight(contentRect) + insets.top + insets.bottom, - NSWidth(contentRect) + insets.left + insets.right); - _initPosition |= NSIntersectsRect(windowRect, _IbeamRect); + windowRect = NSMakeRect( + NSMaxX(self.frame) - NSHeight(contentRect) - border.height * 2, + NSMaxY(self.frame) - NSWidth(contentRect) - border.width * 2 - + theme.fullWidth, + NSHeight(contentRect) + border.height * 2, + NSWidth(contentRect) + border.width * 2 + theme.fullWidth); + _initPosition |= NSIntersectsRect(windowRect, _IbeamRect) || + !NSContainsRect(screenRect, windowRect); if (_initPosition) { if (!sweepVertical) { // To avoid jumping up and down while typing, use the lower screen // when typing on upper, and vice versa if (NSMinY(_IbeamRect) - kOffsetGap - - NSHeight(screenRect) * textWidthRatio - insets.left - - insets.right < + NSHeight(screenRect) * textWidthRatio - border.width * 2 - + theme.fullWidth < NSMinY(screenRect)) { windowRect.origin.y = NSMaxY(_IbeamRect) + kOffsetGap; } else { @@ -4688,7 +4776,7 @@ - (void)show __attribute__((objc_direct)) { } // Make the right edge of candidate block fixed at the left of cursor windowRect.origin.x = - NSMinX(_IbeamRect) + insets.top - NSWidth(windowRect); + NSMinX(_IbeamRect) + border.height - NSWidth(windowRect); } else { if (NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect) < NSMinX(screenRect)) { @@ -4697,26 +4785,27 @@ - (void)show __attribute__((objc_direct)) { windowRect.origin.x = NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect); } - windowRect.origin.y = - NSMinY(_IbeamRect) + insets.left - NSHeight(windowRect); + windowRect.origin.y = NSMinY(_IbeamRect) + border.width + + ceil(theme.fullWidth * 0.5) - + NSHeight(windowRect); } } } else { // anchor is the top left corner in screen coordinates (MinX, MaxY) - windowRect = - NSMakeRect(NSMinX(self.frame), - NSMaxY(self.frame) - NSHeight(contentRect) - insets.top - - insets.bottom, - NSWidth(contentRect) + insets.left + insets.right, - NSHeight(contentRect) + insets.top + insets.bottom); - _initPosition |= NSIntersectsRect(windowRect, _IbeamRect); + windowRect = NSMakeRect( + NSMinX(self.frame), + NSMaxY(self.frame) - NSHeight(contentRect) - border.height * 2, + NSWidth(contentRect) + border.width * 2 + theme.fullWidth, + NSHeight(contentRect) + border.height * 2); + _initPosition |= NSIntersectsRect(windowRect, _IbeamRect) || + !NSContainsRect(screenRect, windowRect); if (_initPosition) { if (sweepVertical) { // To avoid jumping left and right while typing, use the lefter screen // when typing on righter, and vice versa if (NSMinX(_IbeamRect) - kOffsetGap - - NSWidth(screenRect) * textWidthRatio - insets.left - - insets.right >= + NSWidth(screenRect) * textWidthRatio - border.width * 2 - + theme.fullWidth >= NSMinX(screenRect)) { windowRect.origin.x = NSMinX(_IbeamRect) - kOffsetGap - NSWidth(windowRect); @@ -4724,7 +4813,7 @@ - (void)show __attribute__((objc_direct)) { windowRect.origin.x = NSMaxX(_IbeamRect) + kOffsetGap; } windowRect.origin.y = - NSMinY(_IbeamRect) + insets.top - NSHeight(windowRect); + NSMinY(_IbeamRect) + border.height - NSHeight(windowRect); } else { if (NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect) < NSMinY(screenRect)) { @@ -4733,19 +4822,19 @@ - (void)show __attribute__((objc_direct)) { windowRect.origin.y = NSMinY(_IbeamRect) - kOffsetGap - NSHeight(windowRect); } - windowRect.origin.x = NSMaxX(_IbeamRect) - insets.left; + windowRect.origin.x = + NSMaxX(_IbeamRect) - border.width - ceil(theme.fullWidth * 0.5); } } } } - if (_view.preeditRange.length > 0) { + if (!_view.preeditView.hidden) { if (_initPosition) { _anchorOffset = 0.0; } if (theme.vertical != sweepVertical) { - CGFloat anchorOffset = - NSHeight([_view blockRectForRange:_view.preeditRange]); + CGFloat anchorOffset = NSHeight(_view.preeditRect); if (theme.vertical) { windowRect.origin.x += anchorOffset - _anchorOffset; } else { @@ -4800,19 +4889,59 @@ - (void)show __attribute__((objc_direct)) { theme.vertical ? NSMakePoint(0.0, NSWidth(windowRect)) : NSZeroPoint; NSRect viewRect = self.contentView.bounds; _view.frame = viewRect; - _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 (!_back.hidden) { - _back.frame = viewRect; - } + if (!_view.statusView.hidden) { + _view.statusView.frame = NSMakeRect( + NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5) - + _view.statusView.textContainerOrigin.x, + NSMinY(viewRect) + border.height - + _view.statusView.textContainerOrigin.y, + NSWidth(viewRect) - border.width * 2 - theme.fullWidth, + NSHeight(viewRect) - border.height * 2); + } + if (!_view.preeditView.hidden) { + _view.preeditView.frame = NSMakeRect( + NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5) - + _view.preeditView.textContainerOrigin.x, + NSMinY(viewRect) + border.height - + _view.preeditView.textContainerOrigin.y, + NSWidth(viewRect) - border.width * 2 - theme.fullWidth, + NSHeight(_view.preeditRect)); + } + if (!_view.pagingView.hidden) { + CGFloat leadOrigin = + theme.linear + ? NSMaxX(viewRect) - NSWidth(_view.pagingRect) - border.width + + ceil(theme.fullWidth * 0.5) + : NSMinX(viewRect) + border.width + ceil(theme.fullWidth * 0.5); + _view.pagingView.frame = NSMakeRect( + leadOrigin - _view.pagingView.textContainerOrigin.x, + NSMaxY(viewRect) - border.height - NSHeight(_view.pagingRect) - + _view.pagingView.textContainerOrigin.y, + (theme.linear ? NSWidth(_view.pagingRect) + : NSWidth(viewRect) - border.width * 2) - + theme.fullWidth, + NSHeight(_view.pagingRect)); + } + if (!_view.scrollView.hidden) { + _view.scrollView.frame = NSMakeRect( + NSMinX(viewRect) + border.width, + NSMinY(viewRect) + NSMinY(_view.candidatesRect), + NSWidth(viewRect) - border.width * 2, NSHeight(_view.candidatesRect)); + _view.documentView.frame = + NSMakeRect(0.0, 0.0, NSWidth(viewRect) - border.width * 2, + NSHeight(_view.documentRect)); + _view.textView.frame = NSMakeRect( + ceil(theme.fullWidth * 0.5) - _view.textView.textContainerOrigin.x, + ceil(theme.linespace * 0.5) - _view.textView.textContainerOrigin.y, + NSWidth(viewRect) - border.width * 2 - theme.fullWidth, + NSHeight(_view.documentRect) - theme.linespace); + } + if (!_back.hidden) { + _back.frame = viewRect; } [self orderFront:nil]; // reset to initial position after showing status message - _initPosition = _statusMessage != nil; + _initPosition = !_view.statusView.hidden; _needsRedraw = NO; // voila ! } @@ -4830,8 +4959,20 @@ - (void)hide __attribute__((objc_direct)) { self.sectionNum = 0; } +static CGFloat textWidth(NSAttributedString* string, BOOL vertical) { + if (vertical) { + NSMutableAttributedString* verticalString = string.mutableCopy; + [verticalString addAttribute:NSVerticalGlyphFormAttributeName + value:@YES + range:NSMakeRange(0, verticalString.length)]; + return ceil(verticalString.size.width); + } else { + return ceil(string.size.width); + } +} + // Main function to add attributes to text output from librime -- (void)showPreedit:(NSString*)preeditString +- (void)showPreedit:(NSString*)preedit selRange:(NSRange)selRange caretPos:(NSUInteger)caretPos candidateIndices:(NSRange)indexRange @@ -4844,14 +4985,18 @@ - (void)showPreedit:(NSString*)preeditString _pageNum = pageNum; _finalPage = finalPage; _functionButton = kVoidSymbol; - if (indexRange.length > 0 || preeditString.length > 0) { + if (indexRange.length > 0 || preedit.length > 0) { _statusMessage = nil; + if (_view.statusContents.length > 0) { + [_view.statusContents + deleteCharactersInRange:NSMakeRange(0, _view.statusContents.length)]; + } if (_statusTimer.valid) { [_statusTimer invalidate]; _statusTimer = nil; } } else { - if (_statusMessage) { + if (_statusMessage != nil) { [self showStatus:_statusMessage]; _statusMessage = nil; } else if (!_statusTimer.valid) { @@ -4861,89 +5006,84 @@ - (void)showPreedit:(NSString*)preeditString } SquirrelTheme* theme = _view.currentTheme; - NSTextStorage* contents = _view.textStorage; NSParagraphStyle* rulerAttrsPreedit; - NSSize priorSize = contents.length > 0 ? _view.contentRect.size : NSZeroSize; - if ((indexRange.length == 0 && preeditString && - _view.preeditRange.length > 0) || - !updateCandidates) { - rulerAttrsPreedit = [contents attribute:NSParagraphStyleAttributeName - atIndex:0 - effectiveRange:NULL]; + NSSize priorSize = _view.candidateCount > 0 || !_view.preeditView.hidden + ? _view.contentRect.size + : NSZeroSize; + if ((indexRange.length == 0 || !updateCandidates) && preedit.length > 0 && + !_view.preeditView.hidden) { + rulerAttrsPreedit = + [_view.preeditContents attribute:NSParagraphStyleAttributeName + atIndex:0 + effectiveRange:NULL]; } SquirrelCandidateRanges* candidateRanges; BOOL* truncated; if (updateCandidates) { - contents.attributedString = NSAttributedString.alloc.init; + [_view.contents + deleteCharactersInRange:NSMakeRange(0, _view.contents.length)]; if (theme.lineLength > 0.1) { _maxSize.width = fmin(theme.lineLength, _textWidthLimit); } _indexRange = indexRange; _highlightedIndex = highlightedIndex; - candidateRanges = indexRange.length > 0 - ? new SquirrelCandidateRanges[indexRange.length] - : NULL; - truncated = indexRange.length > 0 ? new BOOL[indexRange.length] : NULL; + candidateRanges = new SquirrelCandidateRanges[indexRange.length]; + truncated = new BOOL[indexRange.length]; } - NSRange preeditRange = NSMakeRange(NSNotFound, 0); - NSRange pagingRange = NSMakeRange(NSNotFound, 0); - NSUInteger candidatesStart = 0; - NSUInteger pagingStart = 0; // preedit - if (preeditString) { - NSMutableAttributedString* preedit = - [NSMutableAttributedString.alloc initWithString:preeditString - attributes:theme.preeditAttrs]; - [preedit.mutableString + if (preedit.length > 0) { + _view.preeditContents.attributedString = + [NSAttributedString.alloc initWithString:preedit + attributes:theme.preeditAttrs]; + [_view.preeditContents.mutableString appendString:rulerAttrsPreedit ? @"\t" : kFullWidthSpace]; if (selRange.length > 0) { - [preedit addAttribute:NSForegroundColorAttributeName - value:theme.hilitedPreeditForeColor - range:selRange]; + [_view.preeditContents addAttribute:NSForegroundColorAttributeName + value:theme.hilitedPreeditForeColor + range:selRange]; NSNumber* padding = @(ceil(theme.preeditParagraphStyle.minimumLineHeight * 0.05)); if (selRange.location > 0) { - [preedit addAttribute:NSKernAttributeName - value:padding - range:NSMakeRange(selRange.location - 1, 1)]; + [_view.preeditContents + addAttribute:NSKernAttributeName + value:padding + range:NSMakeRange(selRange.location - 1, 1)]; } - if (NSMaxRange(selRange) < preedit.length) { - [preedit addAttribute:NSKernAttributeName - value:padding - range:NSMakeRange(NSMaxRange(selRange) - 1, 1)]; + if (NSMaxRange(selRange) < _view.preeditContents.length) { + [_view.preeditContents + addAttribute:NSKernAttributeName + value:padding + range:NSMakeRange(NSMaxRange(selRange) - 1, 1)]; } } - [preedit appendAttributedString:caretPos == NSNotFound || caretPos == 0 - ? theme.symbolDeleteStroke - : theme.symbolDeleteFill]; + [_view.preeditContents + appendAttributedString:caretPos == NSNotFound || caretPos == 0 + ? theme.symbolDeleteStroke + : theme.symbolDeleteFill]; // force caret to be rendered sideways, instead of uprights, in vertical // orientation if (theme.vertical && caretPos != NSNotFound) { - [preedit addAttribute:NSVerticalGlyphFormAttributeName - value:@(NO) - range:NSMakeRange(caretPos, 1)]; + [_view.preeditContents addAttribute:NSVerticalGlyphFormAttributeName + value:@NO + range:NSMakeRange(caretPos, 1)]; } - preeditRange = NSMakeRange(0, preedit.length); - if (rulerAttrsPreedit) { - [preedit addAttribute:NSParagraphStyleAttributeName - value:rulerAttrsPreedit - range:preeditRange]; + if (rulerAttrsPreedit != nil) { + [_view.preeditContents + addAttribute:NSParagraphStyleAttributeName + value:rulerAttrsPreedit + range:NSMakeRange(0, _view.preeditContents.length)]; } - if (updateCandidates) { - [contents appendAttributedString:preedit]; - if (indexRange.length > 0) { - [contents.mutableString appendString:@"\n"]; - } else { - self.sectionNum = 0; - goto AdjustAlignment; - } + if (updateCandidates && indexRange.length == 0) { + self.sectionNum = 0; + goto AdjustAlignment; } else { - [contents replaceCharactersInRange:_view.preeditRange - withAttributedString:preedit]; - [_view setPreeditRange:preeditRange hilitedPreeditRange:selRange]; + [_view setHilitedPreeditRange:selRange]; } + } else if (_view.preeditContents.length > 0) { + [_view.preeditContents + deleteCharactersInRange:NSMakeRange(0, _view.preeditContents.length)]; } if (!updateCandidates) { @@ -4957,7 +5097,6 @@ - (void)showPreedit:(NSString*)preeditString } // candidate items - candidatesStart = contents.length; for (NSUInteger idx = 0; idx < indexRange.length; ++idx) { NSUInteger col = idx % theme.pageSize; NSMutableAttributedString* candidate = @@ -5002,7 +5141,7 @@ - (void)showPreedit:(NSString*)preeditString NSUInteger start = candidateRanges[0].location; for (NSUInteger i = 1; i <= idx; ++i) { if (i == idx || truncated[i] != isTruncated) { - [contents + [_view.contents addAttribute:NSParagraphStyleAttributeName value:isTruncated ? theme.truncatedParagraphStyle : theme.candidateParagraphStyle @@ -5016,46 +5155,45 @@ - (void)showPreedit:(NSString*)preeditString } } } else { - [contents - addAttribute:NSParagraphStyleAttributeName - value:theme.candidateParagraphStyle - range:NSMakeRange(candidatesStart, - contents.length - candidatesStart)]; + [_view.contents addAttribute:NSParagraphStyleAttributeName + value:theme.candidateParagraphStyle + range:NSMakeRange(0, _view.contents.length)]; } } } // store final in-candidate locations of label, text, and comment textRange = [candidate.mutableString rangeOfString:text]; - if (idx > 0 && (!theme.linear || !truncated[idx - 1])) { - // separator: linear = "\u3000\x1D"; tabular = "\u3000\t\x1D"; stacked = - // "\n" - [contents appendAttributedString:theme.separator]; - if (theme.linear && col == 0) { - [contents.mutableString appendString:@"\n"]; - } + if (idx > 0 && col == 0 && theme.linear && !truncated[idx - 1]) { + [_view.contents.mutableString appendString:@"\n"]; } - NSUInteger candidateStart = contents.length; + NSUInteger candidateStart = _view.contents.length; SquirrelCandidateRanges ranges = {.location = candidateStart, .text = textRange.location, .comment = NSMaxRange(textRange)}; - [contents appendAttributedString:candidate]; + [_view.contents appendAttributedString:candidate]; // for linear layout, middle-truncate candidates that are longer than one // line if (theme.linear && - ceil(candidate.size.width) > - _textWidthLimit - theme.fullWidth * (theme.tabular ? 2 : 1) - 0.1) { + textWidth(candidate, theme.vertical) > + _textWidthLimit - theme.fullWidth * (theme.tabular ? 3 : 2)) { truncated[idx] = YES; - ranges.length = contents.length - candidateStart; + ranges.length = _view.contents.length - candidateStart; candidateRanges[idx] = ranges; if (idx < indexRange.length - 1 || theme.tabular || theme.showPaging) { - [contents.mutableString appendString:@"\n"]; + [_view.contents.mutableString appendString:@"\n"]; } - [contents addAttribute:NSParagraphStyleAttributeName - value:theme.truncatedParagraphStyle - range:NSMakeRange(candidateStart, - contents.length - candidateStart)]; + [_view.contents + addAttribute:NSParagraphStyleAttributeName + value:theme.truncatedParagraphStyle + range:NSMakeRange(candidateStart, + _view.contents.length - candidateStart)]; } else { + if (theme.linear || idx < indexRange.length - 1) { + // separator: linear = "\u3000\x1D"; tabular = "\u3000\t\x1D"; stacked = + // "\n" + [_view.contents appendAttributedString:theme.separator]; + } truncated[idx] = NO; ranges.length = candidate.length + (theme.tabular ? 3 : theme.linear ? 2 @@ -5066,128 +5204,91 @@ - (void)showPreedit:(NSString*)preeditString // paging indication if (theme.tabular || theme.showPaging) { - NSMutableAttributedString* paging; if (theme.tabular) { - paging = [NSMutableAttributedString.alloc - initWithAttributedString:_locked ? theme.symbolLock - : _view.expanded ? theme.symbolCompress - : theme.symbolExpand]; + _view.pagingContents.attributedString = _locked ? theme.symbolLock + : _view.expanded + ? theme.symbolCompress + : theme.symbolExpand; } else { NSAttributedString* pageNumString = [NSAttributedString.alloc initWithString:[NSString stringWithFormat:@"%lu", pageNum + 1] attributes:theme.pagingAttrs]; - if (theme.vertical) { - paging = [NSMutableAttributedString.alloc - initWithAttributedString: - [pageNumString attributedStringHorizontalInVerticalForms]]; - } else { - paging = [NSMutableAttributedString.alloc - initWithAttributedString:pageNumString]; - } + _view.pagingContents.attributedString = + theme.vertical + ? pageNumString.attributedStringHorizontalInVerticalForms + : pageNumString; } if (theme.showPaging) { - [paging insertAttributedString:_pageNum > 0 ? theme.symbolBackFill - : theme.symbolBackStroke - atIndex:0]; - [paging.mutableString insertString:kFullWidthSpace atIndex:1]; - [paging.mutableString appendString:kFullWidthSpace]; - [paging appendAttributedString:_finalPage ? theme.symbolForwardStroke - : theme.symbolForwardFill]; - } - if (!theme.linear || !truncated[indexRange.length - 1]) { - [contents appendAttributedString:theme.separator]; - if (theme.linear) { - [contents replaceCharactersInRange:NSMakeRange(contents.length, 0) - withString:@"\n"]; - } - } - pagingStart = contents.length; - if (theme.linear) { - [contents appendAttributedString:[NSAttributedString.alloc - initWithString:kFullWidthSpace - attributes:theme.pagingAttrs]]; + [_view.pagingContents + insertAttributedString:_pageNum > 0 ? theme.symbolBackFill + : theme.symbolBackStroke + atIndex:0]; + [_view.pagingContents.mutableString insertString:kFullWidthSpace + atIndex:1]; + [_view.pagingContents.mutableString appendString:kFullWidthSpace]; + [_view.pagingContents + appendAttributedString:_finalPage ? theme.symbolForwardStroke + : theme.symbolForwardFill]; } - [contents appendAttributedString:paging]; - pagingRange = NSMakeRange(contents.length - paging.length, paging.length); - } else if (theme.linear && !truncated[indexRange.length - 1]) { - [contents appendAttributedString:theme.separator]; + } else if (_view.pagingContents.length > 0) { + [_view.pagingContents + deleteCharactersInRange:NSMakeRange(0, _view.pagingContents.length)]; } AdjustAlignment: - [_view estimateBoundsForPreedit:preeditRange - candidates:candidateRanges - truncation:truncated - count:indexRange.length - paging:pagingRange]; + [_view estimateBoundsOnScreen:_screen.visibleFrame + withPreedit:preedit.length > 0 + candidates:candidateRanges + truncation:truncated + count:indexRange.length + paging:indexRange.length > 0 && + (theme.tabular || theme.showPaging)]; CGFloat textWidth = - fmin(fmax(NSMaxX(_view.contentRect) - _view.trailPadding, _maxSize.width), - _textWidthLimit); + clamp(NSWidth(_view.contentRect), _maxSize.width, _textWidthLimit); // right-align the backward delete symbol - if (preeditRange.length > 0 && - NSMaxX([_view blockRectForRange:NSMakeRange(preeditRange.length - 1, - 1)]) < textWidth - 0.1) { - [contents replaceCharactersInRange:NSMakeRange(preeditRange.length - 2, 1) - withString:@"\t"]; + if (preedit.length > 0 && rulerAttrsPreedit == nil) { + [_view.preeditContents + replaceCharactersInRange:NSMakeRange(_view.preeditContents.length - 2, + 1) + withString:@"\t"]; NSMutableParagraphStyle* rulerAttrs = theme.preeditParagraphStyle.mutableCopy; rulerAttrs.tabStops = @[ [NSTextTab.alloc initWithTextAlignment:NSTextAlignmentRight location:textWidth options:@{}] ]; - [contents addAttribute:NSParagraphStyleAttributeName - value:rulerAttrs - range:preeditRange]; + [_view.preeditContents + addAttribute:NSParagraphStyleAttributeName + value:rulerAttrs + range:NSMakeRange(0, _view.preeditContents.length)]; } - if (pagingRange.length > 0 && - NSMaxX([_view blockRectForRange:pagingRange]) < textWidth - 0.1) { + if (!theme.linear && theme.showPaging) { NSMutableParagraphStyle* rulerAttrsPaging = theme.pagingParagraphStyle.mutableCopy; - if (theme.linear) { - [contents replaceCharactersInRange:NSMakeRange(pagingStart, 1) - withString:@"\t"]; - rulerAttrsPaging.tabStops = - @[ [NSTextTab.alloc initWithTextAlignment:NSTextAlignmentRight - location:textWidth - options:@{}] ]; - } else { - [contents replaceCharactersInRange:NSMakeRange(pagingStart + 1, 1) - withString:@"\t"]; - [contents replaceCharactersInRange:NSMakeRange(contents.length - 2, 1) - withString:@"\t"]; - rulerAttrsPaging.tabStops = @[ - [NSTextTab.alloc initWithTextAlignment:NSTextAlignmentCenter - location:textWidth * 0.5 - options:@{}], - [NSTextTab.alloc initWithTextAlignment:NSTextAlignmentRight - location:textWidth - options:@{}] - ]; - } - [contents + [_view.pagingContents replaceCharactersInRange:NSMakeRange(1, 1) + withString:@"\t"]; + [_view.pagingContents + replaceCharactersInRange:NSMakeRange(_view.pagingContents.length - 2, 1) + withString:@"\t"]; + rulerAttrsPaging.tabStops = @[ + [NSTextTab.alloc initWithTextAlignment:NSTextAlignmentCenter + location:textWidth * 0.5 + options:@{}], + [NSTextTab.alloc initWithTextAlignment:NSTextAlignmentRight + location:textWidth + options:@{}] + ]; + [_view.pagingContents addAttribute:NSParagraphStyleAttributeName value:rulerAttrsPaging - range:NSMakeRange(pagingStart, contents.length - pagingStart)]; - } - - // text done! - CGFloat topMargin = - preeditString || theme.linear ? 0.0 : ceil(theme.linespace * 0.5); - CGFloat bottomMargin = - !theme.linear && indexRange.length > 0 && pagingRange.length == 0 - ? floor(theme.linespace * 0.5) - : 0.0; - NSEdgeInsets insets = - NSEdgeInsetsMake(theme.borderInsets.height + topMargin, - theme.borderInsets.width + ceil(theme.fullWidth * 0.5), - theme.borderInsets.height + bottomMargin, - theme.borderInsets.width + floor(theme.fullWidth * 0.5)); + range:NSMakeRange(0, _view.pagingContents.length)]; + } self.animationBehavior = caretPos == NSNotFound ? NSWindowAnimationBehaviorUtilityWindow : NSWindowAnimationBehaviorDefault; - [_view drawViewWithInsets:insets - hilitedIndex:highlightedIndex - hilitedPreeditRange:selRange]; + [_view drawViewWithHilitedIndex:highlightedIndex + hilitedPreeditRange:selRange]; NSSize newSize = _view.contentRect.size; _needsRedraw |= !NSEqualSizes(priorSize, newSize); [self show]; @@ -5216,24 +5317,25 @@ - (void)updateStatusLong:(NSString*)messageLong } - (void)showStatus:(NSString*)message __attribute__((objc_direct)) { - SquirrelTheme* theme = _view.currentTheme; - NSTextStorage* contents = _view.textStorage; - NSSize priorSize = contents.length > 0 ? _view.contentRect.size : NSZeroSize; + NSSize priorSize = + !_view.statusView.hidden ? _view.contentRect.size : NSZeroSize; + + [_view.contents + deleteCharactersInRange:NSMakeRange(0, _view.contents.length)]; + [_view.preeditContents + deleteCharactersInRange:NSMakeRange(0, _view.preeditContents.length)]; + [_view.pagingContents + deleteCharactersInRange:NSMakeRange(0, _view.pagingContents.length)]; - contents.attributedString = [NSAttributedString.alloc + _view.statusContents.attributedString = [NSAttributedString.alloc initWithString:[NSString stringWithFormat:@"\u3000\u2002%@", message] - attributes:theme.statusAttrs]; - - [_view estimateBoundsForPreedit:NSMakeRange(NSNotFound, 0) - candidates:NULL - truncation:NULL - count:0 - paging:NSMakeRange(NSNotFound, 0)]; - NSEdgeInsets insets = - NSEdgeInsetsMake(theme.borderInsets.height, - theme.borderInsets.width + ceil(theme.fullWidth * 0.5), - theme.borderInsets.height, - theme.borderInsets.width + floor(theme.fullWidth * 0.5)); + attributes:_view.currentTheme.statusAttrs]; + [_view estimateBoundsOnScreen:_screen.visibleFrame + withPreedit:NO + candidates:NULL + truncation:NULL + count:0 + paging:NO]; // disable remember_size and fixed line_length for status messages _initPosition = YES; @@ -5242,9 +5344,8 @@ - (void)showStatus:(NSString*)message __attribute__((objc_direct)) { [_statusTimer invalidate]; } self.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; - [_view drawViewWithInsets:insets - hilitedIndex:NSNotFound - hilitedPreeditRange:NSMakeRange(NSNotFound, 0)]; + [_view drawViewWithHilitedIndex:NSNotFound + hilitedPreeditRange:NSMakeRange(NSNotFound, 0)]; NSSize newSize = _view.contentRect.size; _needsRedraw |= !NSEqualSizes(priorSize, newSize); [self show]; @@ -5281,13 +5382,13 @@ - (void)loadConfig:(SquirrelConfig*)config { updateWithConfig:config styleOptions:_optionSwitcher.optionStates scriptVariant:_optionSwitcher.currentScriptVariant - forAppearance:defaultAppear]; + forAppearance:kDefaultAppearance]; if (@available(macOS 10.14, *)) { [SquirrelView.darkTheme updateWithConfig:config styleOptions:_optionSwitcher.optionStates scriptVariant:_optionSwitcher.currentScriptVariant - forAppearance:darkAppear]; + forAppearance:kDarkAppearance]; } [self getLocked]; [self updateDisplayParameters]; diff --git a/input_source.mm b/input_source.mm index 03269d57e..1fa3ca425 100644 --- a/input_source.mm +++ b/input_source.mm @@ -19,20 +19,19 @@ typedef CF_OPTIONS(CFIndex, RimeInputMode) { RimeInputMode GetEnabledInputModes(void); void RegisterInputSource(void) { - if (GetEnabledInputModes()) { // Already registered + if (GetEnabledInputModes() != 0) { // Already registered return; } CFURLRef installPathURL = CFURLCreateFromFileSystemRepresentation( NULL, (UInt8*)kInstallPath, (CFIndex)strlen(kInstallPath), false); - if (installPathURL) { + if (installPathURL != NULL) { TISRegisterInputSource((CFURLRef)CFAutorelease(installPathURL)); NSLog(@"Registered input source from %s", kInstallPath); } } void EnableInputSource(void) { - RimeInputMode input_modes_enabled = GetEnabledInputModes(); - if (input_modes_enabled != 0) { + if (GetEnabledInputModes() != 0) { // keep user's manually enabled input modes return; } @@ -44,11 +43,16 @@ void EnableInputSource(void) { CFBundleCopyLocalizationsForPreferences(localizations, NULL); if (CFArrayGetCount(preferred) > 0) { CFStringRef language = (CFStringRef)CFArrayGetValueAtIndex(preferred, 0); - if (!CFStringCompare(language, CFSTR("zh-Hans"), 0)) { + if (CFStringCompare(language, CFSTR("zh-Hans"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo) { input_modes_to_enable |= HANS_INPUT_MODE; - } else if (!CFStringCompare(language, CFSTR("zh-Hant"), 0)) { + } else if (CFStringCompare(language, CFSTR("zh-Hant"), + kCFCompareCaseInsensitive) == + kCFCompareEqualTo) { input_modes_to_enable |= HANT_INPUT_MODE; - } else if (!CFStringCompare(language, CFSTR("zh-HK"), 0)) { + } else if (CFStringCompare(language, CFSTR("zh-HK"), + kCFCompareCaseInsensitive) == + kCFCompareEqualTo) { input_modes_to_enable |= CANT_INPUT_MODE; } } else { @@ -66,17 +70,16 @@ void EnableInputSource(void) { CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty( inputSource, kTISPropertyInputSourceID); // NSLog(@"Examining input source: %@", sourceID); - if ((!CFStringCompare(sourceID, kHansInputModeID, 0) && + if ((CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo && (input_modes_to_enable & HANS_INPUT_MODE)) || - (!CFStringCompare(sourceID, kHantInputModeID, 0) && + (CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo && (input_modes_to_enable & HANT_INPUT_MODE)) || - (!CFStringCompare(sourceID, kCantInputModeID, 0) && + (CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo && (input_modes_to_enable & CANT_INPUT_MODE))) { CFBooleanRef isEnabled = (CFBooleanRef)TISGetInputSourceProperty( inputSource, kTISPropertyInputSourceIsEnabled); if (!CFBooleanGetValue(isEnabled)) { - OSStatus enableError = TISEnableInputSource(inputSource); - if (enableError) { + if (OSStatus enableError = TISEnableInputSource(inputSource) != 0) { NSLog(@"Failed to enable input source: %@ (%@)", sourceID, [NSError errorWithDomain:NSOSStatusErrorDomain code:enableError @@ -100,18 +103,21 @@ void SelectInputSource(void) { CFBundleCopyLocalizationsForPreferences(localizations, NULL); for (CFIndex i = 0; i < CFArrayGetCount(preferred); ++i) { CFStringRef language = (CFStringRef)CFArrayGetValueAtIndex(preferred, i); - if (!CFStringCompare(language, CFSTR("zh-Hans"), 0) && + if (CFStringCompare(language, CFSTR("zh-Hans"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo && (enabled_input_modes & HANS_INPUT_MODE)) { input_mode_to_select = HANS_INPUT_MODE; break; - } - if (!CFStringCompare(language, CFSTR("zh-Hant"), 0) && - (enabled_input_modes & HANT_INPUT_MODE)) { + } else if (CFStringCompare(language, CFSTR("zh-Hant"), + kCFCompareCaseInsensitive) == + kCFCompareEqualTo && + (enabled_input_modes & HANT_INPUT_MODE)) { input_mode_to_select = HANT_INPUT_MODE; break; - } - if (!CFStringCompare(language, CFSTR("zh-HK"), 0) && - (enabled_input_modes & CANT_INPUT_MODE)) { + } else if (CFStringCompare(language, CFSTR("zh-HK"), + kCFCompareCaseInsensitive) == + kCFCompareEqualTo && + (enabled_input_modes & CANT_INPUT_MODE)) { input_mode_to_select = CANT_INPUT_MODE; break; } @@ -132,11 +138,11 @@ void SelectInputSource(void) { CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty( inputSource, kTISPropertyInputSourceID); // NSLog(@"Examining input source: %@", sourceID); - if ((!CFStringCompare(sourceID, kHansInputModeID, 0) && + if ((CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo && ((input_mode_to_select & HANS_INPUT_MODE) != 0)) || - (!CFStringCompare(sourceID, kHantInputModeID, 0) && + (CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo && ((input_mode_to_select & HANT_INPUT_MODE) != 0)) || - (!CFStringCompare(sourceID, kCantInputModeID, 0) && + (CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo && ((input_mode_to_select & CANT_INPUT_MODE) != 0))) { // select the first enabled input mode in Squirrel. CFBooleanRef isSelectable = (CFBooleanRef)TISGetInputSourceProperty( @@ -144,8 +150,7 @@ void SelectInputSource(void) { CFBooleanRef isSelected = (CFBooleanRef)TISGetInputSourceProperty( inputSource, kTISPropertyInputSourceIsSelected); if (!CFBooleanGetValue(isSelected) && CFBooleanGetValue(isSelectable)) { - OSStatus selectError = TISSelectInputSource(inputSource); - if (selectError) { + if (OSStatus selectError = TISSelectInputSource(inputSource) != 0) { NSLog(@"Failed to select input source: %@ (%@)", sourceID, [NSError errorWithDomain:NSOSStatusErrorDomain code:selectError @@ -172,11 +177,10 @@ void DisableInputSource(void) { CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty( inputSource, kTISPropertyInputSourceID); // NSLog(@"Examining input source: %@", sourceID); - if (!CFStringCompare(sourceID, kHansInputModeID, 0) || - !CFStringCompare(sourceID, kHantInputModeID, 0) || - !CFStringCompare(sourceID, kCantInputModeID, 0)) { - OSStatus disableError = TISDisableInputSource(inputSource); - if (disableError) { + if (CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo || + CFStringCompare(sourceID, kHantInputModeID, 0) == kCFCompareEqualTo || + CFStringCompare(sourceID, kCantInputModeID, 0) == kCFCompareEqualTo) { + if (OSStatus disableError = TISDisableInputSource(inputSource) != 0) { NSLog(@"Failed to disable input source: %@ (%@)", sourceID, [NSError errorWithDomain:NSOSStatusErrorDomain code:disableError @@ -202,16 +206,14 @@ RimeInputMode GetEnabledInputModes(void) { CFStringRef sourceID = (CFStringRef)TISGetInputSourceProperty( inputSource, kTISPropertyInputSourceID); // NSLog(@"Examining input source: %@", sourceID); - if (!CFStringCompare(sourceID, kHansInputModeID, 0) || - !CFStringCompare(sourceID, kHantInputModeID, 0) || - !CFStringCompare(sourceID, kCantInputModeID, 0)) { - if (!CFStringCompare(sourceID, kHansInputModeID, 0)) { - input_modes |= HANS_INPUT_MODE; - } else if (!CFStringCompare(sourceID, kHantInputModeID, 0)) { - input_modes |= HANT_INPUT_MODE; - } else if (!CFStringCompare(sourceID, kCantInputModeID, 0)) { - input_modes |= CANT_INPUT_MODE; - } + if (CFStringCompare(sourceID, kHansInputModeID, 0) == kCFCompareEqualTo) { + input_modes |= HANS_INPUT_MODE; + } else if (CFStringCompare(sourceID, kHantInputModeID, 0) == + kCFCompareEqualTo) { + input_modes |= HANT_INPUT_MODE; + } else if (CFStringCompare(sourceID, kCantInputModeID, 0) == + kCFCompareEqualTo) { + input_modes |= CANT_INPUT_MODE; } } CFRelease(sourceList); diff --git a/macos_keycode.mm b/macos_keycode.mm index 5ac8bf926..78dcbfd9f 100644 --- a/macos_keycode.mm +++ b/macos_keycode.mm @@ -173,6 +173,7 @@ int rime_keycode_from_mac_keycode(ushort mac_keycode) { return XK_Eisu_toggle; case kVK_JIS_Kana: return XK_Kana_Shift; + default: return 0; } @@ -278,11 +279,12 @@ int rime_keycode_from_keychar(unichar keychar, bool shift, bool caps) { }; int rime_modifiers_from_name(const char* modifier_name) { - if (!modifier_name) + if (modifier_name == NULL) { return 0; + } for (int i = 0; i < 6; ++i) { - if (!strcmp(modifier_name, rime_modidifers[i])) { - return (1 << (i < 4 ? i : i + 22)); + if (strcmp(modifier_name, rime_modidifers[i]) == 0) { + return 1 << (i < 4 ? i : i + 22); } } return 0; diff --git a/main.mm b/main.mm index 35e6224e0..462d7fa13 100644 --- a/main.mm +++ b/main.mm @@ -14,7 +14,7 @@ static NSString* const kConnectionName = @"Squirrel_1_Connection"; int main(int argc, char* argv[]) { - if (argc > 1 && !strcmp("--quit", argv[1])) { + if (argc > 1 && strcmp("--quit", argv[1]) == 0) { NSString* bundleId = NSBundle.mainBundle.bundleIdentifier; NSArray* runningSquirrels = [NSRunningApplication runningApplicationsWithBundleIdentifier:bundleId]; @@ -24,35 +24,35 @@ int main(int argc, char* argv[]) { return 0; } - if (argc > 1 && !strcmp("--reload", argv[1])) { + if (argc > 1 && strcmp("--reload", argv[1]) == 0) { [NSDistributedNotificationCenter.defaultCenter postNotificationName:@"SquirrelReloadNotification" object:nil]; return 0; } - if (argc > 1 && (!strcmp("--register-input-source", argv[1]) || - !strcmp("--install", argv[1]))) { + if (argc > 1 && (strcmp("--register-input-source", argv[1]) == 0 || + strcmp("--install", argv[1]) == 0)) { RegisterInputSource(); return 0; } - if (argc > 1 && !strcmp("--enable-input-source", argv[1])) { + if (argc > 1 && strcmp("--enable-input-source", argv[1]) == 0) { EnableInputSource(); return 0; } - if (argc > 1 && !strcmp("--disable-input-source", argv[1])) { + if (argc > 1 && strcmp("--disable-input-source", argv[1]) == 0) { DisableInputSource(); return 0; } - if (argc > 1 && !strcmp("--select-input-source", argv[1])) { + if (argc > 1 && strcmp("--select-input-source", argv[1]) == 0) { SelectInputSource(); return 0; } - if (argc > 1 && !strcmp("--build", argv[1])) { + if (argc > 1 && strcmp("--build", argv[1]) == 0) { // notification show_notification("deploy_update"); // build all schemas in current directory @@ -63,7 +63,7 @@ int main(int argc, char* argv[]) { return rime_get_api()->deploy() ? 0 : 1; } - if (argc > 1 && !strcmp("--sync", argv[1])) { + if (argc > 1 && strcmp("--sync", argv[1]) == 0) { [NSDistributedNotificationCenter.defaultCenter postNotificationName:@"SquirrelSyncNotification" object:nil];