From 5ef0b1887189a3d9f2233618eb35dcdbd9cef57e Mon Sep 17 00:00:00 2001 From: LEO Yoon-Tsaw Date: Sat, 25 May 2024 22:47:19 -0400 Subject: [PATCH] Linting --- sources/BridgingFunctions.swift | 2 +- sources/InputSource.swift | 16 +- sources/MacOSKeyCodes.swift | 26 +-- sources/Main.swift | 16 +- sources/SquirrelApplicationDelegate.swift | 80 +++---- sources/SquirrelConfig.swift | 58 ++--- sources/SquirrelInputController.swift | 152 ++++++------- sources/SquirrelPanel.swift | 126 ++++++----- sources/SquirrelTheme.swift | 77 ++++--- sources/SquirrelView.swift | 260 +++++++++++----------- 10 files changed, 395 insertions(+), 418 deletions(-) diff --git a/sources/BridgingFunctions.swift b/sources/BridgingFunctions.swift index 6f04e84b3..7c3d40930 100644 --- a/sources/BridgingFunctions.swift +++ b/sources/BridgingFunctions.swift @@ -30,7 +30,7 @@ extension DataSizeable { value.data_size = Int32(MemoryLayout.size - offset) return value } - + mutating func setCString(_ swiftString: String, to keypath: WritableKeyPath?>) { swiftString.withCString { cStr in // Duplicate the string to create a persisting C string diff --git a/sources/InputSource.swift b/sources/InputSource.swift index 208063e84..cf96eda89 100644 --- a/sources/InputSource.swift +++ b/sources/InputSource.swift @@ -26,7 +26,7 @@ final class SquirrelInstaller { } return inputSources }() - + func enabledModes() -> [InputMode] { var enabledModes = Set() for (mode, inputSource) in getInputSource(modes: InputMode.allCases) { @@ -39,7 +39,7 @@ final class SquirrelInstaller { } return Array(enabledModes) } - + func register() { let enabledInputModes = enabledModes() if !enabledInputModes.isEmpty { @@ -50,7 +50,7 @@ final class SquirrelInstaller { TISRegisterInputSource(SquirrelApp.appDir as CFURL) print("Registered input source from \(SquirrelApp.appDir)") } - + func enable(modes: [InputMode] = []) { let enabledInputModes = enabledModes() if !enabledInputModes.isEmpty && modes.isEmpty { @@ -62,11 +62,11 @@ final class SquirrelInstaller { for (mode, inputSource) in getInputSource(modes: modesToEnable) { if let enabled = getBool(for: inputSource, key: kTISPropertyInputSourceIsEnabled), !enabled { let error = TISEnableInputSource(inputSource) - print("Enable \(error == noErr ? "succeeds" : "fails") for input source: \(mode.rawValue)"); + print("Enable \(error == noErr ? "succeeds" : "fails") for input source: \(mode.rawValue)") } } } - + func select(mode: InputMode? = nil) { let enabledInputModes = enabledModes() let modeToSelect = mode ?? .primary @@ -90,7 +90,7 @@ final class SquirrelInstaller { } } } - + func disable(modes: [InputMode] = []) { let modesToDisable = modes.isEmpty ? InputMode.allCases : modes for (mode, inputSource) in getInputSource(modes: modesToDisable) { @@ -100,7 +100,7 @@ final class SquirrelInstaller { } } } - + private func getInputSource(modes: [InputMode]) -> [InputMode: TISInputSource] { var matchingSources = [InputMode: TISInputSource]() for mode in modes { @@ -110,7 +110,7 @@ final class SquirrelInstaller { } return matchingSources } - + private func getBool(for inputSource: TISInputSource, key: CFString!) -> Bool? { let enabledRef = TISGetInputSourceProperty(inputSource, key) guard let enabled = unsafeBitCast(enabledRef, to: CFBoolean?.self) else { return nil } diff --git a/sources/MacOSKeyCodes.swift b/sources/MacOSKeyCodes.swift index 0613fc648..5cf3ca5ac 100644 --- a/sources/MacOSKeyCodes.swift +++ b/sources/MacOSKeyCodes.swift @@ -9,7 +9,7 @@ import Carbon import AppKit struct SquirrelKeycode { - + static func osxModifiersToRime(modifiers: NSEvent.ModifierFlags) -> UInt32 { var ret: UInt32 = 0 if modifiers.contains(.capsLock) { @@ -29,19 +29,19 @@ struct SquirrelKeycode { } return ret } - + static func osxKeycodeToRime(keycode: UInt16, keychar: Character?, shift: Bool, caps: Bool) -> UInt32 { if let code = keycodeMappings[Int(keycode)] { return UInt32(code) } - + if let keychar = keychar, keychar.isASCII, let codeValue = keychar.unicodeScalars.first?.value { // NOTE: IBus/Rime use different keycodes for uppercase/lowercase letters. if keychar.isLowercase && (shift || caps) { // lowercase -> Uppercase return keychar.uppercased().unicodeScalars.first!.value } - + switch codeValue { case 0x20...0x7e: return codeValue @@ -57,11 +57,11 @@ struct SquirrelKeycode { break } } - + return UInt32(XK_VoidSymbol) } - - private static let keycodeMappings: Dictionary = [ + + private static let keycodeMappings: [Int: Int32] = [ // modifiers kVK_CapsLock: XK_Caps_Lock, kVK_Command: XK_Super_L, // XK_Meta_L? @@ -73,7 +73,7 @@ struct SquirrelKeycode { kVK_RightOption: XK_Alt_R, kVK_Shift: XK_Shift_L, kVK_RightShift: XK_Shift_R, - + // special kVK_Delete: XK_BackSpace, kVK_Escape: XK_Escape, @@ -82,7 +82,7 @@ struct SquirrelKeycode { kVK_Return: XK_Return, kVK_Space: XK_space, kVK_Tab: XK_Tab, - + // function kVK_F1: XK_F1, kVK_F2: XK_F2, @@ -104,7 +104,7 @@ struct SquirrelKeycode { kVK_F18: XK_F18, kVK_F19: XK_F19, kVK_F20: XK_F20, - + // cursor kVK_UpArrow: XK_Up, kVK_DownArrow: XK_Down, @@ -114,7 +114,7 @@ struct SquirrelKeycode { kVK_PageDown: XK_Page_Down, kVK_Home: XK_Home, kVK_End: XK_End, - + // keypad kVK_ANSI_Keypad0: XK_KP_0, kVK_ANSI_Keypad1: XK_KP_1, @@ -134,13 +134,13 @@ struct SquirrelKeycode { kVK_ANSI_KeypadPlus: XK_KP_Add, kVK_ANSI_KeypadDivide: XK_KP_Divide, kVK_ANSI_KeypadEnter: XK_KP_Enter, - + // other kVK_ISO_Section: XK_section, kVK_JIS_Yen: XK_yen, kVK_JIS_Underscore: XK_underscore, kVK_JIS_KeypadComma: XK_comma, kVK_JIS_Eisu: XK_Eisu_Shift, - kVK_JIS_Kana: XK_Kana_Shift, + kVK_JIS_Kana: XK_Kana_Shift ] } diff --git a/sources/Main.swift b/sources/Main.swift index 22dfe6291..005593681 100644 --- a/sources/Main.swift +++ b/sources/Main.swift @@ -18,10 +18,10 @@ struct SquirrelApp { static let appDir = "/Library/Input Library/Squirrel.app".withCString { dir in URL(fileURLWithFileSystemRepresentation: dir, isDirectory: false, relativeTo: nil) } - + static func main() { let rimeAPI: RimeApi_stdbool = rime_get_api_stdbool().pointee - + let handled = autoreleasepool { let installer = SquirrelInstaller() let args = CommandLine.arguments @@ -67,7 +67,7 @@ struct SquirrelApp { return true case "--build": // Notification - SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_update", comment: ""), msgId: "deploy") + SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_update", comment: "")) // Build all schemas in current directory var builderTraits = RimeTraits.rimeStructInit() builderTraits.setCString("rime.squirrel-builder", to: \.app_name) @@ -90,7 +90,7 @@ struct SquirrelApp { if handled { return } - + autoreleasepool { // find the bundle identifier and then initialize the input method server let main = Bundle.main @@ -102,10 +102,10 @@ struct SquirrelApp { let delegate = SquirrelApplicationDelegate() app.delegate = delegate app.setActivationPolicy(.accessory) - + // opencc will be configured with relative dictionary paths FileManager.default.changeCurrentDirectoryPath(main.sharedSupportPath!) - + if NSApp.squirrelAppDelegate.problematicLaunchDetected() { print("Problematic launch detected!") let args = ["Problematic launch detected! Squirrel may be suffering a crash due to improper configuration. Revert previous modifications to see if the problem recurs."] @@ -121,7 +121,7 @@ struct SquirrelApp { NSApp.squirrelAppDelegate.loadSettings() print("Squirrel reporting!") } - + // finally run everything app.run() print("Squirrel is quitting...") @@ -129,7 +129,7 @@ struct SquirrelApp { } return } - + static let helpDoc = """ Supported arguments: Perform actions: diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index f9b1952c9..ff674b725 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -13,7 +13,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta static let rimeWikiURL = URL(string: "https://github.com/rime/home/wiki")! static let updateNotificationIdentifier = "SquirrelUpdateNotification" static let notificationIdentifier = "SquirrelNotification" - + let rimeAPI: RimeApi_stdbool = rime_get_api_stdbool().pointee var config: SquirrelConfig? var panel: SquirrelPanel? @@ -22,7 +22,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta var supportsGentleScheduledUpdateReminders: Bool { true } - + func standardUserDriverWillHandleShowingUpdate(_ handleShowingUpdate: Bool, forUpdate update: SUAppcastItem, state: SPUUserUpdateState) { NSApp.setActivationPolicy(.regular) if !state.userInitiated { @@ -34,56 +34,56 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta UNUserNotificationCenter.current().add(request) } } - + func standardUserDriverDidReceiveUserAttention(forUpdate update: SUAppcastItem) { NSApp.dockTile.badgeLabel = "" UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Self.updateNotificationIdentifier]) } - + func standardUserDriverWillFinishUpdateSession() { NSApp.setActivationPolicy(.accessory) } - + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { if response.notification.request.identifier == Self.updateNotificationIdentifier && response.actionIdentifier == UNNotificationDefaultActionIdentifier { updateController.updater.checkForUpdates() } - + completionHandler() } - + func applicationWillFinishLaunching(_ notification: Notification) { panel = SquirrelPanel(position: .zero) addObservers() } - + func applicationWillTerminate(_ notification: Notification) { NotificationCenter.default.removeObserver(self) DistributedNotificationCenter.default().removeObserver(self) panel?.hide() } - + func deploy() { print("Start maintenance...") self.shutdownRime() self.startRime(fullCheck: true) self.loadSettings() } - + func syncUserData() { print("Sync user data") _ = rimeAPI.sync_user_data() } - + func openLogFolder() { let logDir = FileManager.default.temporaryDirectory NSWorkspace.shared.open(logDir) } - + func openRimeFolder() { NSWorkspace.shared.open(SquirrelApp.userDir) } - + func checkForUpdates() { if updateController.updater.canCheckForUpdates { print("Checking for updates") @@ -92,14 +92,14 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta print("Cannot check for updates") } } - + func openWiki() { NSWorkspace.shared.open(Self.rimeWikiURL) } - - static func showMessage(msgText: String?, msgId: String?) { + + static func showMessage(msgText: String?) { let center = UNUserNotificationCenter.current() - center.requestAuthorization(options: [.alert, .provisional]) { granted, error in + center.requestAuthorization(options: [.alert, .provisional]) { _, error in if let error = error { print("User notification authorization error: \(error.localizedDescription)") } @@ -121,7 +121,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } } } - + func setupRime() { let userDataDir = SquirrelApp.userDir let fileManager = FileManager.default @@ -135,7 +135,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta let notification_handler: @convention(c) (UnsafeMutableRawPointer?, RimeSessionId, UnsafePointer?, UnsafePointer?) -> Void = notificationHandler let context_object = Unmanaged.passUnretained(self).toOpaque() rimeAPI.set_notification_handler(notification_handler, context_object) - + var squirrelTraits = RimeTraits.rimeStructInit() squirrelTraits.setCString(Bundle.main.sharedSupportPath!, to: \.shared_data_dir) squirrelTraits.setCString(userDataDir.path(), to: \.user_data_dir) @@ -145,7 +145,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta squirrelTraits.setCString("rime.squirrel", to: \.app_name) rimeAPI.setup(&squirrelTraits) } - + func startRime(fullCheck: Bool) { print("Initializing la rime...") rimeAPI.initialize(nil) @@ -158,20 +158,20 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta // print("[DEBUG] maintenance fails") } } - + func loadSettings() { config = SquirrelConfig() if !config!.openBaseConfig() { return } - + enableNotifications = config!.getString("show_notifications_when") != "never" if let panel = panel, let config = self.config { panel.load(config: config, forDarkMode: false) panel.load(config: config, forDarkMode: true) } } - + func loadSettings(for schemaID: String) { if schemaID.count == 0 || schemaID.first == "." { return @@ -188,12 +188,12 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } schema.close() } - + // prevent freezing the system func problematicLaunchDetected() -> Bool { var detected = false let logFile = FileManager.default.temporaryDirectory.appendingPathComponent("squirrel_launch.json", conformingTo: .json) - //print("[DEBUG] archive: \(logFile)") + // print("[DEBUG] archive: \(logFile)") do { let archive = try Data(contentsOf: logFile, options: [.uncached]) let decoder = JSONDecoder() @@ -203,7 +203,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta detected = true } } catch let error as NSError where error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError { - + } catch { print("Error occurred during processing launch time archive: \(error.localizedDescription)") return detected @@ -218,19 +218,19 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } return detected } - + // add an awakeFromNib item so that we can set the action method. Note that // any menuItems without an action will be disabled when displayed in the Text // Input Menu. func addObservers() { let center = NSWorkspace.shared.notificationCenter center.addObserver(forName: NSWorkspace.willPowerOffNotification, object: nil, queue: nil, using: workspaceWillPowerOff) - + let notifCenter = DistributedNotificationCenter.default() notifCenter.addObserver(forName: .init("SquirrelReloadNotification"), object: nil, queue: nil, using: rimeNeedsReload) notifCenter.addObserver(forName: .init("SquirrelSyncNotification"), object: nil, queue: nil, using: rimeNeedsSync) } - + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { print("Squirrel is quitting.") rimeAPI.cleanup_all_sessions() @@ -241,17 +241,17 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessionId: RimeSessionId, messageTypeC: UnsafePointer?, messageValueC: UnsafePointer?) { let delegate: SquirrelApplicationDelegate = Unmanaged.fromOpaque(contextObject!).takeUnretainedValue() - + let messageType = messageTypeC.map { String(cString: $0) } let messageValue = messageValueC.map { String(cString: $0) } if messageType == "deploy" { switch messageValue { case "start": - SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_start", comment: ""), msgId: messageType) + SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_start", comment: "")) case "success": - SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_success", comment: ""), msgId: messageType) + SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_success", comment: "")) case "failure": - SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_failure", comment: ""), msgId: messageType) + SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_failure", comment: "")) default: break } @@ -290,23 +290,23 @@ private extension SquirrelApplicationDelegate { panel?.updateStatus(long: msgTextLong ?? "", short: msgTextShort ?? "") } } - + func shutdownRime() { config?.close() rimeAPI.finalize() } - - func workspaceWillPowerOff(notification: Notification) { + + func workspaceWillPowerOff(_: Notification) { print("Finalizing before logging out.") self.shutdownRime() } - - func rimeNeedsReload(notification: Notification) { + + func rimeNeedsReload(_: Notification) { print("Reloading rime on demand.") self.deploy() } - - func rimeNeedsSync(notification: Notification) { + + func rimeNeedsSync(_: Notification) { print("Sync rime on demand.") self.syncUserData() } diff --git a/sources/SquirrelConfig.swift b/sources/SquirrelConfig.swift index 45cb1da0d..65d5d7abd 100644 --- a/sources/SquirrelConfig.swift +++ b/sources/SquirrelConfig.swift @@ -10,28 +10,26 @@ import AppKit final class SquirrelConfig { private let rimeAPI: RimeApi_stdbool = rime_get_api_stdbool().pointee private(set) var isOpen = false - var schemaID: String = "" - - private var cache: Dictionary = [:] + + private var cache: [String: Any] = [:] private var config: RimeConfig = .init() private var baseConfig: SquirrelConfig? - + func openBaseConfig() -> Bool { close() isOpen = rimeAPI.config_open("squirrel", &config) return isOpen } - + func open(schemaID: String, baseConfig: SquirrelConfig?) -> Bool { close() isOpen = rimeAPI.schema_open(schemaID, &config) if isOpen { - self.schemaID = schemaID self.baseConfig = baseConfig } return isOpen } - + func close() { if isOpen { _ = rimeAPI.config_close(&config) @@ -39,11 +37,11 @@ final class SquirrelConfig { isOpen = false } } - + deinit { close() } - + func has(section: String) -> Bool { if isOpen { var iterator: RimeConfigIterator = .init() @@ -54,7 +52,7 @@ final class SquirrelConfig { } return false } - + func getBool(_ option: String) -> Bool? { if let cachedValue = cachedValue(of: Bool.self, forKey: option) { return cachedValue @@ -66,19 +64,7 @@ final class SquirrelConfig { } return baseConfig?.getBool(option) } - - func getInt(_ option: String) -> Int? { - if let cachedValue = cachedValue(of: Int.self, forKey: option) { - return cachedValue - } - var value: Int32 = 0 - if isOpen && rimeAPI.config_get_int(&config, option, &value) { - cache[option] = value - return Int(value) - } - return baseConfig?.getInt(option) - } - + func getDouble(_ option: String) -> CGFloat? { if let cachedValue = cachedValue(of: Double.self, forKey: option) { return cachedValue @@ -90,7 +76,7 @@ final class SquirrelConfig { } return baseConfig?.getDouble(option) } - + func getString(_ option: String) -> String? { if let cachedValue = cachedValue(of: String.self, forKey: option) { return cachedValue @@ -101,7 +87,7 @@ final class SquirrelConfig { } return baseConfig?.getString(option) } - + func getColor(_ option: String, inSpace colorSpace: SquirrelTheme.RimeColorSpace) -> NSColor? { if let cachedValue = cachedValue(of: NSColor.self, forKey: option) { return cachedValue @@ -112,10 +98,10 @@ final class SquirrelConfig { } return baseConfig?.getColor(option, inSpace: colorSpace) } - - func getAppOptions(_ appName: String) -> Dictionary { + + func getAppOptions(_ appName: String) -> [String: Bool] { let rootKey = "app_options/\(appName)" - var appOptions = [String : Bool]() + var appOptions = [String: Bool]() var iterator = RimeConfigIterator() _ = rimeAPI.config_begin_map(&iterator, &config, rootKey) while rimeAPI.config_next(&iterator) { @@ -127,7 +113,7 @@ final class SquirrelConfig { rimeAPI.config_end(&iterator) return appOptions } - + // isLinear func updateCandidateListLayout(prefix: String) -> Bool { let candidateListLayout = getString("\(prefix)/candidate_list_layout") @@ -141,7 +127,7 @@ final class SquirrelConfig { return getBool("\(prefix)/horizontal") ?? false } } - + // isVertical func updateTextOrientation(prefix: String) -> Bool { let textOrientation = getString("\(prefix)/text_orientation") @@ -161,19 +147,19 @@ private extension SquirrelConfig { func cachedValue(of: T.Type, forKey key: String) -> T? { return cache[key] as? T } - + func color(from colorStr: String, inSpace colorSpace: SquirrelTheme.RimeColorSpace) -> NSColor? { if let matched = try? /0x([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})/.wholeMatch(in: colorStr) { - let (_, a, b, g, r) = matched.output - return color(alpha: Int(a, radix: 16)!, red: Int(r, radix: 16)!, green: Int(g, radix: 16)!, blue: Int(b, radix: 16)!, colorSpace: colorSpace) + let (_, alpha, blue, green, red) = matched.output + return color(alpha: Int(alpha, radix: 16)!, red: Int(red, radix: 16)!, green: Int(green, radix: 16)!, blue: Int(blue, radix: 16)!, colorSpace: colorSpace) } else if let matched = try? /0x([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})/.wholeMatch(in: colorStr) { - let (_, b, g, r) = matched.output - return color(alpha: 255, red: Int(r, radix: 16)!, green: Int(g, radix: 16)!, blue: Int(b, radix: 16)!, colorSpace: colorSpace) + let (_, blue, green, red) = matched.output + return color(alpha: 255, red: Int(red, radix: 16)!, green: Int(green, radix: 16)!, blue: Int(blue, radix: 16)!, colorSpace: colorSpace) } else { return nil } } - + func color(alpha: Int, red: Int, green: Int, blue: Int, colorSpace: SquirrelTheme.RimeColorSpace) -> NSColor { switch colorSpace { case .displayP3: diff --git a/sources/SquirrelInputController.swift b/sources/SquirrelInputController.swift index d90db396d..6d47f16b8 100644 --- a/sources/SquirrelInputController.swift +++ b/sources/SquirrelInputController.swift @@ -9,11 +9,11 @@ import InputMethodKit final class SquirrelInputController: IMKInputController { private static let keyRollOver = 50 - + private var client: IMKTextInput? private let rimeAPI: RimeApi_stdbool = rime_get_api_stdbool().pointee private var preedit: String = "" - private var selRange: NSRange = NSMakeRange(NSNotFound, 0) + private var selRange: NSRange = NSRange(location: NSNotFound, length: 0) private var caretPos: Int = 0 private var lastModifiers: NSEvent.ModifierFlags = .init() private var session: RimeSessionId = 0 @@ -27,30 +27,30 @@ final class SquirrelInputController: IMKInputController { private var chordTimer: Timer? private var chordDuration: TimeInterval = 0 private var currentApp: String = "" - + override func handle(_ event: NSEvent!, client sender: Any!) -> Bool { let modifiers = event.modifierFlags let changes = lastModifiers.symmetricDifference(modifiers) - + // Return true to indicate the the key input was received and dealt with. // Key processing will not continue in that case. In other words the // system will not deliver a key down event to the application. // Returning false means the original key down will be passed on to the client. var handled = false - + if session == 0 || !rimeAPI.find_session(session) { createSession() if session == 0 { return false } } - + self.client ?= sender as? IMKTextInput if let app = client?.bundleIdentifier(), currentApp != app { currentApp = app updateAppOptions() } - + switch event.type { case .flagsChanged: if lastModifiers == modifiers { @@ -62,7 +62,7 @@ final class SquirrelInputController: IMKInputController { // For flags-changed event, keyCode is available since macOS 10.15 // (#715) let rimeKeycode: UInt32 = SquirrelKeycode.osxKeycodeToRime(keycode: event.keyCode, keychar: nil, shift: false, caps: false) - + if changes.contains(.capsLock) { // NOTE: rime assumes XK_Caps_Lock to be sent before modifier changes, // while NSFlagsChanged event has the flag changed already. @@ -70,39 +70,37 @@ final class SquirrelInputController: IMKInputController { rimeModifiers ^= kLockMask.rawValue _ = processKey(rimeKeycode, modifiers: rimeModifiers) } - + // Need to process release before modifier down. Because // sometimes release event is delayed to next modifier keydown. var buffer = [(keycode: UInt32, modifier: UInt32)]() - for flag in [NSEvent.ModifierFlags.shift, .control, .option, .command] { - if changes.contains(flag) { - if modifiers.contains(flag) { // New modifier - buffer.append((keycode: rimeKeycode, modifier: rimeModifiers)) - } else { // Release - buffer.insert((keycode: rimeKeycode, modifier: rimeModifiers | kReleaseMask.rawValue), at: 0) - } + for flag in [NSEvent.ModifierFlags.shift, .control, .option, .command] where changes.contains(flag) { + if modifiers.contains(flag) { // New modifier + buffer.append((keycode: rimeKeycode, modifier: rimeModifiers)) + } else { // Release + buffer.insert((keycode: rimeKeycode, modifier: rimeModifiers | kReleaseMask.rawValue), at: 0) } } for (keycode, modifier) in buffer { _ = processKey(keycode, modifiers: modifier) } - + lastModifiers = modifiers rimeUpdate() - + case .keyDown: // ignore Command+X hotkeys. if modifiers.contains(.command) { break } - + let keyCode = event.keyCode var keyChars = event.charactersIgnoringModifiers if let code = keyChars?.first, !code.isLetter { keyChars = event.characters } // print("[DEBUG] KEYDOWN client: \(sender ?? "nil"), modifiers: \(modifiers), keyCode: \(keyCode), keyChars: [\(keyChars ?? "empty")]") - + // translate osx keyevents to rime keyevents if let char = keyChars?.first { let rimeKeycode = SquirrelKeycode.osxKeycodeToRime(keycode: keyCode, keychar: char, @@ -114,14 +112,14 @@ final class SquirrelInputController: IMKInputController { rimeUpdate() } } - + default: break } - + return handled } - + func selectCandidate(_ index: Int) -> Bool { let success = rimeAPI.select_candidate_on_current_page(session, index) if success { @@ -129,7 +127,7 @@ final class SquirrelInputController: IMKInputController { } return success } - + func page(up: Bool) -> Bool { var handled = false handled = rimeAPI.change_page(session, up) @@ -138,7 +136,7 @@ final class SquirrelInputController: IMKInputController { } return handled } - + func moveCaret(forward: Bool) -> Bool { let currentCaretPos = rimeAPI.get_caret_pos(session) guard let input = rimeAPI.get_input(session) else { return false } @@ -157,12 +155,12 @@ final class SquirrelInputController: IMKInputController { rimeUpdate() return true } - + override func recognizedEvents(_ sender: Any!) -> Int { // print("[DEBUG] recognizedEvents:") return Int(NSEvent.EventTypeMask.Element(arrayLiteral: .keyDown, .flagsChanged).rawValue) } - + override func activateServer(_ sender: Any!) { self.client ?= sender as? IMKTextInput // print("[DEBUG] activateServer:") @@ -179,26 +177,26 @@ final class SquirrelInputController: IMKInputController { } preedit = "" } - + override init!(server: IMKServer!, delegate: Any!, client: Any!) { self.client = client as? IMKTextInput // print("[DEBUG] initWithServer: \(server ?? .init()) delegate: \(delegate ?? "nil") client:\(client ?? "nil")") super.init(server: server, delegate: delegate, client: client) createSession() } - + override func deactivateServer(_ sender: Any!) { // print("[DEBUG] deactivateServer: \(sender ?? "nil")") hidePalettes() commitComposition(sender) client = nil } - + override func hidePalettes() { NSApp.squirrelAppDelegate.panel?.hide() super.hidePalettes() } - + /*! @method @abstract Called when a user action was taken that ends an input session. @@ -220,7 +218,7 @@ final class SquirrelInputController: IMKInputController { } } } - + override func menu() -> NSMenu! { let deploy = NSMenuItem(title: NSLocalizedString("Deploy", comment: "Menu item"), action: #selector(deploy), keyEquivalent: "`") deploy.target = self @@ -235,7 +233,7 @@ final class SquirrelInputController: IMKInputController { wiki.target = self let update = NSMenuItem(title: NSLocalizedString("Check for updates...", comment: "Menu item"), action: #selector(checkForUpdates), keyEquivalent: "") update.target = self - + let menu = NSMenu() menu.addItem(deploy) menu.addItem(sync) @@ -243,42 +241,42 @@ final class SquirrelInputController: IMKInputController { menu.addItem(setting) menu.addItem(wiki) menu.addItem(update) - + return menu } - + @objc func deploy() { NSApp.squirrelAppDelegate.deploy() } - + @objc func syncUserData() { NSApp.squirrelAppDelegate.syncUserData() } - + @objc func openLogFolder() { NSApp.squirrelAppDelegate.openLogFolder() } - + @objc func openRimeFolder() { NSApp.squirrelAppDelegate.openRimeFolder() } - + @objc func checkForUpdates() { NSApp.squirrelAppDelegate.checkForUpdates() } - + @objc func openWiki() { NSApp.squirrelAppDelegate.openWiki() } - + deinit { destroySession() } } private extension SquirrelInputController { - - func onChordTimer(_ timer: Timer) { + + func onChordTimer(_: Timer) { // chord release triggered by timer var processedKeys = false if chordKeyCount > 0 && session != 0 { @@ -294,13 +292,11 @@ private extension SquirrelInputController { rimeUpdate() } } - + func updateChord(keycode: UInt32, modifiers: UInt32) { // print("[DEBUG] update chord: {\(chordKeyCodes)} << \(keycode)") - for i in 0..= Self.keyRollOver { // you are cheating. only one human typist (fingers <= 10) is supported. @@ -319,7 +315,7 @@ private extension SquirrelInputController { } chordTimer = Timer.scheduledTimer(withTimeInterval: chordDuration, repeats: false, block: onChordTimer) } - + func clearChord() { chordKeyCount = 0 if let timer = chordTimer { @@ -329,19 +325,19 @@ private extension SquirrelInputController { chordTimer = nil } } - + func createSession() { guard let app = client?.bundleIdentifier() else { return } print("createSession: \(app)") currentApp = app session = rimeAPI.create_session() schemaId = "" - + if session != 0 { updateAppOptions() } } - + func updateAppOptions() { if currentApp == "" { return @@ -353,7 +349,7 @@ private extension SquirrelInputController { } } } - + func destroySession() { // print("[DEBUG] destroySession:") if session != 0 { @@ -362,10 +358,10 @@ private extension SquirrelInputController { } clearChord() } - + func processKey(_ rimeKeycode: UInt32, modifiers rimeModifiers: UInt32) -> Bool { // TODO add special key event preprocessing here - + // with linear candidate list, arrow keys may behave differently. if let panel = NSApp.squirrelAppDelegate.panel { if panel.linear != rimeAPI.get_option(session, "_linear") { @@ -376,12 +372,12 @@ private extension SquirrelInputController { rimeAPI.set_option(session, "_vertical", panel.vertical) } } - + let handled = rimeAPI.process_key(session, Int32(rimeKeycode), Int32(rimeModifiers)) // print("[DEBUG] rime_keycode: \(rimeKeycode), rime_modifiers: \(rimeModifiers), handled = \(handled)") - + // TODO add special key event postprocessing here - + if !handled { let isVimBackInCommandMode = rimeKeycode == XK_Escape || ((rimeModifiers & kControlMask.rawValue != 0) && (rimeKeycode == XK_c || rimeKeycode == XK_C || rimeKeycode == XK_bracketleft)) if isVimBackInCommandMode && rimeAPI.get_option(session, "vim_mode") && @@ -403,10 +399,10 @@ private extension SquirrelInputController { clearChord() } } - + return handled } - + func rimeConsumeCommittedText() { var commitText = RimeCommit.rimeStructInit() if rimeAPI.get_commit(session, &commitText) { @@ -416,11 +412,11 @@ private extension SquirrelInputController { } } } - + func rimeUpdate() { // print("[DEBUG] rimeUpdate") rimeConsumeCommittedText() - + var status = RimeStatus_stdbool.rimeStructInit() if rimeAPI.get_status(session, &status) { // enable schema specific ui style @@ -437,16 +433,16 @@ private extension SquirrelInputController { } _ = rimeAPI.free_status(&status) } - + var ctx = RimeContext_stdbool.rimeStructInit() if rimeAPI.get_context(session, &ctx) { // update preedit text let preedit = ctx.composition.preedit.map({ String(cString: $0) }) ?? "" - + let start = String.Index(preedit.utf8.index(preedit.utf8.startIndex, offsetBy: Int(ctx.composition.sel_start)), within: preedit) ?? preedit.startIndex let end = String.Index(preedit.utf8.index(preedit.utf8.startIndex, offsetBy: Int(ctx.composition.sel_end)), within: preedit) ?? preedit.startIndex let caretPos = String.Index(preedit.utf8.index(preedit.utf8.startIndex, offsetBy: Int(ctx.composition.cursor_pos)), within: preedit) ?? preedit.startIndex - + if inlineCandidate { var candidatePreview = ctx.commit_text_preview.map { String(cString: $0) } ?? "" if inlinePreedit { @@ -474,10 +470,10 @@ private extension SquirrelInputController { // each character in preedit. note this is a full-shape space U+3000; // using half shape characters like "..." will result in an unstable // baseline when composing Chinese characters. - show(preedit: preedit.isEmpty ? "" : " ", selRange: NSMakeRange(0, 0), caretPos: 0) + show(preedit: preedit.isEmpty ? "" : " ", selRange: NSRange(location: 0, length: 0), caretPos: 0) } } - + // update candidates let numCandidates = Int(ctx.menu.num_candidates) var candidates = [String]() @@ -502,39 +498,39 @@ private extension SquirrelInputController { hidePalettes() } } - + func commit(string: String) { guard let client = client else { return } // print("[DEBUG] commitString: \(string)") - client.insertText(string, replacementRange: NSMakeRange(NSNotFound, 0)) + client.insertText(string, replacementRange: NSRange(location: NSNotFound, length: 0)) preedit = "" hidePalettes() } - + func show(preedit: String, selRange: NSRange, caretPos: Int) { guard let client = client else { return } // print("[DEBUG] showPreeditString: '\(preedit)'") if self.preedit == preedit && self.caretPos == caretPos && self.selRange == selRange { return } - + self.preedit = preedit self.caretPos = caretPos self.selRange = selRange - + // print("[DEBUG] selRange.location = \(selRange.location), selRange.length = \(selRange.length); caretPos = \(caretPos)") let start = selRange.location let attrString = NSMutableAttributedString(string: preedit) if start > 0 { - let attrs = mark(forStyle: kTSMHiliteConvertedText, at: NSMakeRange(0, start))! as! [NSAttributedString.Key : Any] - attrString.setAttributes(attrs, range: NSMakeRange(0, start)) + let attrs = mark(forStyle: kTSMHiliteConvertedText, at: NSRange(location: 0, length: start))! as! [NSAttributedString.Key: Any] + attrString.setAttributes(attrs, range: NSRange(location: 0, length: start)) } - let remainingRange = NSMakeRange(start, preedit.utf16.count - start) - let attrs = mark(forStyle: kTSMHiliteSelectedRawText, at: remainingRange)! as! [NSAttributedString.Key : Any] + let remainingRange = NSRange(location: start, length: preedit.utf16.count - start) + let attrs = mark(forStyle: kTSMHiliteSelectedRawText, at: remainingRange)! as! [NSAttributedString.Key: Any] attrString.setAttributes(attrs, range: remainingRange) - client.setMarkedText(attrString, selectionRange: NSMakeRange(caretPos, 0), replacementRange: NSMakeRange(NSNotFound, 0)) + client.setMarkedText(attrString, selectionRange: NSRange(location: caretPos, length: 0), replacementRange: NSRange(location: NSNotFound, length: 0)) } - + func showPanel(preedit: String, selRange: NSRange, caretPos: Int, candidates: [String], comments: [String], labels: [String], highlighted: Int) { // print("[DEBUG] showPanelWithPreedit:...:") guard let client = client else { return } diff --git a/sources/SquirrelPanel.swift b/sources/SquirrelPanel.swift index 93287b7ef..da6d32fab 100644 --- a/sources/SquirrelPanel.swift +++ b/sources/SquirrelPanel.swift @@ -11,14 +11,14 @@ final class SquirrelPanel: NSPanel { private let view: SquirrelView private let back: NSVisualEffectView var inputController: SquirrelInputController? - + var position: NSRect private var screenRect: NSRect = .zero private var maxHeight: CGFloat = 0 - + private var statusMessage: String = "" private var statusTimer: Timer? - + private var preedit: String = "" private var selRange: NSRange = .init(location: NSNotFound, length: 0) private var caretPos: Int = 0 @@ -29,7 +29,7 @@ final class SquirrelPanel: NSPanel { private var cursorIndex: Int = 0 private var scrollDirection: CGVector = .zero private var scrollTime: Date = .distantPast - + init(position: NSRect) { self.position = position self.view = SquirrelView(frame: position) @@ -50,7 +50,7 @@ final class SquirrelPanel: NSPanel { contentView.addSubview(view.textView) self.contentView = contentView } - + var linear: Bool { view.currentTheme.linear } @@ -63,7 +63,7 @@ final class SquirrelPanel: NSPanel { var inlineCandidate: Bool { view.currentTheme.inlineCandidate } - + override func sendEvent(_ event: NSEvent) { switch event.type { case .leftMouseDown: @@ -130,14 +130,14 @@ final class SquirrelPanel: NSPanel { } super.sendEvent(event) } - + func hide() { statusTimer?.invalidate() statusTimer = nil orderOut(nil) maxHeight = 0 } - + // Main function to add attributes to text output from librime func update(preedit: String, selRange: NSRange, caretPos: Int, candidates: [String], comments: [String], labels: [String], highlighted index: Int, update: Bool) { if update { @@ -150,7 +150,7 @@ final class SquirrelPanel: NSPanel { self.index = index } cursorIndex = index - + if !candidates.isEmpty || !preedit.isEmpty { statusMessage = "" statusTimer?.invalidate() @@ -164,14 +164,14 @@ final class SquirrelPanel: NSPanel { } return } - + let theme = view.currentTheme currentScreen() - + let text = NSMutableAttributedString() - var preeditRange = NSMakeRange(NSNotFound, 0) - var highlightedPreeditRange = NSMakeRange(NSNotFound, 0) - + var preeditRange = NSRange(location: NSNotFound, length: 0) + var highlightedPreeditRange = NSRange(location: NSNotFound, length: 0) + // preedit if !preedit.isEmpty { let line = NSMutableAttributedString() @@ -183,27 +183,27 @@ final class SquirrelPanel: NSPanel { if selRange.length > 0 { let highlightedPreeditStart = line.length line.append(NSAttributedString(string: String(preedit[startIndex.. 1 && i < labels.count { labels[i] @@ -217,53 +217,53 @@ final class SquirrelPanel: NSPanel { } else { "" } - + let candidate = candidates[i].precomposedStringWithCanonicalMapping let comment = comments[i].precomposedStringWithCanonicalMapping - + let line = NSMutableAttributedString(string: theme.candidateFormat, attributes: labelAttrs) for range in line.string.ranges(of: /\[candidate\]/) { let convertedRange = convert(range: range, in: line.string) line.addAttributes(attrs, range: convertedRange) if candidate.count <= 5 { - line.addAttribute(.noBreak, value: true, range: NSMakeRange(convertedRange.location+1, convertedRange.length-1)) + line.addAttribute(.noBreak, value: true, range: NSRange(location: convertedRange.location+1, length: convertedRange.length-1)) } } for range in line.string.ranges(of: /\[comment\]/) { line.addAttributes(commentAttrs, range: convert(range: range, in: line.string)) } - line.mutableString.replaceOccurrences(of: "[label]", with: label, range: NSMakeRange(0, line.length)) + line.mutableString.replaceOccurrences(of: "[label]", with: label, range: NSRange(location: 0, length: line.length)) let labeledLine = line.copy() as! NSAttributedString - line.mutableString.replaceOccurrences(of: "[candidate]", with: candidate, range: NSMakeRange(0, line.length)) - line.mutableString.replaceOccurrences(of: "[comment]", with: comment, range: NSMakeRange(0, line.length)) - + line.mutableString.replaceOccurrences(of: "[candidate]", with: candidate, range: NSRange(location: 0, length: line.length)) + line.mutableString.replaceOccurrences(of: "[comment]", with: comment, range: NSRange(location: 0, length: line.length)) + if line.length <= 10 { - line.addAttribute(.noBreak, value: true, range: NSMakeRange(1, line.length-1)) + line.addAttribute(.noBreak, value: true, range: NSRange(location: 1, length: line.length-1)) } - + let lineSeparator = NSAttributedString(string: linear ? " " : "\n", attributes: attrs) if i > 0 { text.append(lineSeparator) } let str = lineSeparator.mutableCopy() as! NSMutableAttributedString if vertical { - str.addAttribute(.verticalGlyphForm, value: 1, range: NSMakeRange(0, str.length)) + str.addAttribute(.verticalGlyphForm, value: 1, range: NSRange(location: 0, length: str.length)) } view.separatorWidth = str.boundingRect(with: .zero).width - + let paragraphStyleCandidate = (i == 0 ? theme.firstParagraphStyle : theme.paragraphStyle).mutableCopy() as! NSMutableParagraphStyle if linear { paragraphStyleCandidate.paragraphSpacingBefore -= theme.linespace paragraphStyleCandidate.lineSpacing = theme.linespace } if !linear, let labelEnd = labeledLine.string.firstMatch(of: /\[(candidate|comment)\]/)?.range.lowerBound { - let labelString = labeledLine.attributedSubstring(from: NSMakeRange(0, labelEnd.utf16Offset(in: labeledLine.string))) + let labelString = labeledLine.attributedSubstring(from: NSRange(location: 0, length: labelEnd.utf16Offset(in: labeledLine.string))) let labelWidth = labelString.boundingRect(with: .zero, options: [.usesLineFragmentOrigin]).width paragraphStyleCandidate.headIndent = labelWidth } - line.addAttribute(.paragraphStyle, value: paragraphStyleCandidate, range: NSMakeRange(0, line.length)) - - candidateRanges.append(NSMakeRange(text.length, line.length)) + line.addAttribute(.paragraphStyle, value: paragraphStyleCandidate, range: NSRange(location: 0, length: line.length)) + + candidateRanges.append(NSRange(location: text.length, length: line.length)) text.append(line) } @@ -273,7 +273,7 @@ final class SquirrelPanel: NSPanel { view.drawView(candidateRanges: candidateRanges, hilightedIndex: index, preeditRange: preeditRange, highlightedPreeditRange: highlightedPreeditRange) show() } - + func updateStatus(long longMessage: String, short shortMessage: String) { let theme = view.currentTheme switch theme.statusMessageType { @@ -291,7 +291,7 @@ final class SquirrelPanel: NSPanel { } } } - + func load(config: SquirrelConfig, forDarkMode isDark: Bool) { if isDark { view.darkTheme = SquirrelTheme() @@ -309,19 +309,17 @@ private extension SquirrelPanel { point = self.convertPoint(fromScreen: point) return view.convert(point, from: nil) } - + func currentScreen() { if let screen = NSScreen.main { screenRect = screen.frame } - for screen in NSScreen.screens { - if NSPointInRect(position.origin, screen.frame) { - screenRect = screen.frame - break - } + for screen in NSScreen.screens where screen.frame.contains(position.origin) { + screenRect = screen.frame + break } } - + func maxTextWidth() -> CGFloat { let theme = view.currentTheme let font: NSFont = theme.font @@ -334,7 +332,7 @@ private extension SquirrelPanel { } return maxWidth } - + // Get the window size, the windows will be the dirtyRect in // SquirrelView.drawRect func show() { @@ -344,13 +342,13 @@ private extension SquirrelPanel { if self.appearance != requestedAppearance { self.appearance = requestedAppearance } - + // Break line if the text is too long, based on screen size. let textWidth = maxTextWidth() let maxTextHeight = vertical ? screenRect.width - theme.edgeInset.width * 2 : screenRect.height - theme.edgeInset.height * 2 - view.textContainer.size = NSMakeSize(textWidth, maxTextHeight) - - var panelRect = NSZeroRect + view.textContainer.size = NSSize(width: textWidth, height: maxTextHeight) + + var panelRect = NSRect.zero // in vertical mode, the width and height are interchanged var contentRect = view.contentRect if theme.memorizeSize && (vertical && position.midY / screenRect.height < 0.5) || @@ -359,13 +357,13 @@ private extension SquirrelPanel { maxHeight = contentRect.width } else { contentRect.size.width = maxHeight - view.textContainer.size = NSMakeSize(maxHeight, maxTextHeight) + view.textContainer.size = NSSize(width: maxHeight, height: maxTextHeight) } } if vertical { - panelRect.size = NSMakeSize(min(0.95 * screenRect.width, contentRect.height + theme.edgeInset.height * 2), - min(0.95 * screenRect.height, contentRect.width + theme.edgeInset.width * 2)) + panelRect.size = NSSize(width: min(0.95 * screenRect.width, contentRect.height + theme.edgeInset.height * 2), + height: min(0.95 * screenRect.height, contentRect.width + theme.edgeInset.width * 2)) // To avoid jumping up and down while typing, use the lower screen when // typing on upper, and vice versa if position.midY / screenRect.height >= 0.5 { @@ -380,9 +378,9 @@ private extension SquirrelPanel { panelRect.origin.x += preeditRect.height + theme.edgeInset.width } } else { - panelRect.size = NSMakeSize(min(0.95 * screenRect.width, contentRect.width + theme.edgeInset.width * 2), - min(0.95 * screenRect.height, contentRect.height + theme.edgeInset.height * 2)) - panelRect.origin = NSMakePoint(position.minX, position.minY - SquirrelTheme.offsetHeight - panelRect.height) + panelRect.size = NSSize(width: min(0.95 * screenRect.width, contentRect.width + theme.edgeInset.width * 2), + height: min(0.95 * screenRect.height, contentRect.height + theme.edgeInset.height * 2)) + panelRect.origin = NSPoint(x: position.minX, y: position.minY - SquirrelTheme.offsetHeight - panelRect.height) } if panelRect.maxX > screenRect.maxX { panelRect.origin.x = screenRect.maxX - panelRect.width @@ -404,22 +402,22 @@ private extension SquirrelPanel { panelRect.origin.y = screenRect.minY } self.setFrame(panelRect, display: true) - + // rotate the view, the core in vertical mode! if vertical { contentView!.boundsRotation = -90 - contentView!.setBoundsOrigin(NSMakePoint(0, panelRect.width)) + contentView!.setBoundsOrigin(NSPoint(x: 0, y: panelRect.width)) } else { contentView!.boundsRotation = 0 contentView!.setBoundsOrigin(.zero) } view.textView.boundsRotation = 0 view.textView.setBoundsOrigin(.zero) - + view.frame = contentView!.bounds view.textView.frame = contentView!.bounds view.textView.textContainerInset = theme.edgeInset - + if theme.translucency { back.frame = contentView!.bounds back.appearance = NSApp.effectiveAppearance @@ -432,22 +430,22 @@ private extension SquirrelPanel { orderFront(nil) // voila! } - + func show(status message: String) { let theme = view.currentTheme let text = NSMutableAttributedString(string: message, attributes: theme.attrs) - text.addAttribute(.paragraphStyle, value: theme.paragraphStyle, range: NSMakeRange(0, text.length)) + text.addAttribute(.paragraphStyle, value: theme.paragraphStyle, range: NSRange(location: 0, length: text.length)) view.textContentStorage.attributedString = text view.textView.setLayoutOrientation(vertical ? .vertical : .horizontal) - view.drawView(candidateRanges: [NSMakeRange(0, text.length)], hilightedIndex: -1, preeditRange: NSMakeRange(NSNotFound, 0), highlightedPreeditRange: NSMakeRange(NSNotFound, 0)) + view.drawView(candidateRanges: [NSRange(location: 0, length: text.length)], hilightedIndex: -1, preeditRange: NSRange(location: NSNotFound, length: 0), highlightedPreeditRange: NSRange(location: NSNotFound, length: 0)) show() - + statusTimer?.invalidate() statusTimer = Timer.scheduledTimer(withTimeInterval: SquirrelTheme.showStatusDuration, repeats: false) { _ in self.hide() } } - + func convert(range: Range, in string: String) -> NSRange { let startPos = range.lowerBound.utf16Offset(in: string) let endPos = range.upperBound.utf16Offset(in: string) diff --git a/sources/SquirrelTheme.swift b/sources/SquirrelTheme.swift index 6034ca06b..035903c6b 100644 --- a/sources/SquirrelTheme.swift +++ b/sources/SquirrelTheme.swift @@ -12,7 +12,7 @@ final class SquirrelTheme { static let defaultFontSize: CGFloat = NSFont.systemFontSize static let showStatusDuration: Double = 1.2 static let defaultFont = NSFont.userFont(ofSize: defaultFontSize)! - + enum StatusMessageType: String { case long, short, mix } @@ -26,18 +26,18 @@ final class SquirrelTheme { } } } - + var native = true var memorizeSize = true private var colorSpace: RimeColorSpace = .sRGB - + var backgroundColor: NSColor = .windowBackgroundColor var highlightedPreeditColor: NSColor? var highlightedBackColor: NSColor? = .selectedTextBackgroundColor var preeditBackgroundColor: NSColor? var candidateBackColor: NSColor? var borderColor: NSColor? - + private var textColor: NSColor = .tertiaryLabelColor private var highlightedTextColor: NSColor = .labelColor private var candidateTextColor: NSColor = .secondaryLabelColor @@ -46,7 +46,7 @@ final class SquirrelTheme { private var highlightedCandidateLabelColor: NSColor? private var commentTextColor: NSColor? = .tertiaryLabelColor private var highlightedCommentTextColor: NSColor? - + var cornerRadius: CGFloat = 0 var hilitedCornerRadius: CGFloat = 0 var surroundingExtraExpansion: CGFloat = 0 @@ -57,21 +57,21 @@ final class SquirrelTheme { var preeditLinespace: CGFloat = 0 var baseOffset: CGFloat = 0 var alpha: CGFloat = 1 - + var translucency = false var mutualExclusive = false var linear = false var vertical = false var inlinePreedit = false var inlineCandidate = false - - private var fonts = Array() - private var labelFonts = Array() - private var commentFonts = Array() - + + private var fonts = [NSFont]() + private var labelFonts = [NSFont]() + private var commentFonts = [NSFont]() + private var candidateTemplate = "[label]. [candidate] [comment]" var statusMessageType: StatusMessageType = .mix - + var font: NSFont { return combineFonts(fonts) ?? Self.defaultFont } @@ -81,47 +81,47 @@ final class SquirrelTheme { var commentFont: NSFont? { return combineFonts(commentFonts) } - var attrs: [NSAttributedString.Key : Any] { + var attrs: [NSAttributedString.Key: Any] { [.foregroundColor: candidateTextColor, .font: font, .baselineOffset: baseOffset] } - var highlightedAttrs: [NSAttributedString.Key : Any] { + var highlightedAttrs: [NSAttributedString.Key: Any] { [.foregroundColor: highlightedCandidateTextColor, .font: font, .baselineOffset: baseOffset] } - var labelAttrs: [NSAttributedString.Key : Any] { + var labelAttrs: [NSAttributedString.Key: Any] { return [.foregroundColor: candidateLabelColor ?? blendColor(foregroundColor: self.candidateTextColor, backgroundColor: self.backgroundColor), .font: labelFont ?? font, .baselineOffset: baseOffset + (labelFont != nil && !vertical ? (font.pointSize - labelFont!.pointSize) / 2 : 0)] } - var labelHighlightedAttrs: [NSAttributedString.Key : Any] { + var labelHighlightedAttrs: [NSAttributedString.Key: Any] { return [.foregroundColor: highlightedCandidateLabelColor ?? blendColor(foregroundColor: highlightedCandidateTextColor, backgroundColor: highlightedBackColor), .font: labelFont ?? font, .baselineOffset: baseOffset + (labelFont != nil && !vertical ? (font.pointSize - labelFont!.pointSize) / 2 : 0)] } - var commentAttrs: [NSAttributedString.Key : Any] { + var commentAttrs: [NSAttributedString.Key: Any] { return [.foregroundColor: commentTextColor ?? candidateTextColor, .font: commentFont ?? font, .baselineOffset: baseOffset + (commentFont != nil && !vertical ? (font.pointSize - commentFont!.pointSize) / 2 : 0)] } - var commentHighlightedAttrs: [NSAttributedString.Key : Any] { + var commentHighlightedAttrs: [NSAttributedString.Key: Any] { return [.foregroundColor: highlightedCommentTextColor ?? highlightedCandidateTextColor, .font: commentFont ?? font, .baselineOffset: baseOffset + (commentFont != nil && !vertical ? (font.pointSize - commentFont!.pointSize) / 2 : 0)] } - var preeditAttrs: [NSAttributedString.Key : Any] { + var preeditAttrs: [NSAttributedString.Key: Any] { [.foregroundColor: textColor, .font: font, .baselineOffset: baseOffset] } - var preeditHighlightedAttrs: [NSAttributedString.Key : Any] { + var preeditHighlightedAttrs: [NSAttributedString.Key: Any] { [.foregroundColor: highlightedTextColor, .font: font, .baselineOffset: baseOffset] } - + var firstParagraphStyle: NSParagraphStyle { let style = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle style.paragraphSpacing = linespace / 2 @@ -141,10 +141,10 @@ final class SquirrelTheme { return style as NSParagraphStyle } var edgeInset: NSSize { - if (self.vertical) { - return NSMakeSize(borderHeight + cornerRadius, borderWidth + cornerRadius) + if self.vertical { + return NSSize(width: borderHeight + cornerRadius, height: borderWidth + cornerRadius) } else { - return NSMakeSize(borderWidth + cornerRadius, borderHeight + cornerRadius) + return NSSize(width: borderWidth + cornerRadius, height: borderHeight + cornerRadius) } } var borderLineWidth: CGFloat { @@ -164,7 +164,7 @@ final class SquirrelTheme { candidateTemplate = newTemplate } } - + func load(config: SquirrelConfig, dark: Bool) { linear = config.updateCandidateListLayout(prefix: "style") vertical = config.updateTextOrientation(prefix: "style") @@ -173,10 +173,10 @@ final class SquirrelTheme { translucency ?= config.getBool("style/translucency") mutualExclusive ?= config.getBool("style/mutual_exclusive") memorizeSize ?= config.getBool("style/memorize_size") - + statusMessageType ?= .init(rawValue: config.getString("style/status_message_type") ?? "") candidateFormat ?= config.getString("style/candidate_format") - + alpha ?= config.getDouble("style/alpha").map { min(1, max(0, $0)) } cornerRadius ?= config.getDouble("style/corner_radius") hilitedCornerRadius ?= config.getDouble("style/hilited_corner_radius") @@ -187,14 +187,14 @@ final class SquirrelTheme { preeditLinespace ?= config.getDouble("style/spacing") baseOffset ?= config.getDouble("style/base_offset") shadowSize ?= config.getDouble("style/shadow_size").map { max(0, $0) } - + var fontName = config.getString("style/font_face") var fontSize = config.getDouble("style/font_point") var labelFontName = config.getString("style/label_font_face") var labelFontSize = config.getDouble("style/label_font_point") var commentFontName = config.getString("style/comment_font_face") var commentFontSize = config.getDouble("style/comment_font_point") - + let colorSchemeOption = dark ? "style/color_scheme_dark" : "style/color_scheme" if let colorScheme = config.getString(colorSchemeOption), colorScheme != "native" { native = false @@ -206,7 +206,7 @@ final class SquirrelTheme { preeditBackgroundColor = config.getColor("\(prefix)/preedit_back_color", inSpace: colorSpace) candidateBackColor = config.getColor("\(prefix)/candidate_back_color", inSpace: colorSpace) borderColor = config.getColor("\(prefix)/border_color", inSpace: colorSpace) - + textColor ?= config.getColor("\(prefix)/text_color", inSpace: colorSpace) highlightedTextColor = config.getColor("\(prefix)/hilited_text_color", inSpace: colorSpace) ?? textColor candidateTextColor = config.getColor("\(prefix)/candidate_text_color", inSpace: colorSpace) ?? textColor @@ -215,7 +215,7 @@ final class SquirrelTheme { highlightedCandidateLabelColor = config.getColor("\(prefix)/hilited_candidate_label_color", inSpace: colorSpace) commentTextColor = config.getColor("\(prefix)/comment_text_color", inSpace: colorSpace) highlightedCommentTextColor = config.getColor("\(prefix)/hilited_comment_text_color", inSpace: colorSpace) - + // the following per-color-scheme configurations, if exist, will // override configurations with the same name under the global 'style' // section @@ -230,7 +230,7 @@ final class SquirrelTheme { labelFontSize ?= config.getDouble("\(prefix)/label_font_point") commentFontName ?= config.getString("\(prefix)/comment_font_face") commentFontSize ?= config.getDouble("\(prefix)/comment_font_point") - + alpha ?= config.getDouble("\(prefix)/alpha").map { max(0, min(1, $0)) } cornerRadius ?= config.getDouble("\(prefix)/corner_radius") hilitedCornerRadius ?= config.getDouble("\(prefix)/hilited_corner_radius") @@ -255,20 +255,20 @@ final class SquirrelTheme { } } } - + private extension SquirrelTheme { - func combineFonts(_ fonts: Array) -> NSFont? { + func combineFonts(_ fonts: [NSFont]) -> NSFont? { if fonts.count == 0 { return nil } if fonts.count == 1 { return fonts[0] } let attribute = [NSFontDescriptor.AttributeName.cascadeList: fonts[1...].map { $0.fontDescriptor } ] let fontDescriptor = fonts[0].fontDescriptor.addingAttributes(attribute) return NSFont.init(descriptor: fontDescriptor, size: fonts[0].pointSize) } - - func decodeFonts(from fontString: String, size: CGFloat?) -> Array { + + func decodeFonts(from fontString: String, size: CGFloat?) -> [NSFont] { var seenFontFamilies = Set() let fontStrings = fontString.split(separator: ",") - var fonts = Array() + var fonts = [NSFont]() for string in fontStrings { let trimedString = string.trimmingCharacters(in: .whitespaces) if let fontFamilyName = trimedString.split(separator: "-").first.map({String($0)}) { @@ -290,7 +290,7 @@ private extension SquirrelTheme { } return fonts } - + func blendColor(foregroundColor: NSColor, backgroundColor: NSColor?) -> NSColor { let foregroundColor = foregroundColor.usingColorSpace(NSColorSpace.deviceRGB)! let backgroundColor = (backgroundColor ?? NSColor.gray).usingColorSpace(NSColorSpace.deviceRGB)! @@ -303,4 +303,3 @@ private extension SquirrelTheme { alpha: blend(foregroundColor.alphaComponent, backgroundColor.alphaComponent)) } } - diff --git a/sources/SquirrelView.swift b/sources/SquirrelView.swift index 4eaa7a65f..89ab93b83 100644 --- a/sources/SquirrelView.swift +++ b/sources/SquirrelView.swift @@ -7,7 +7,7 @@ import AppKit -fileprivate class SquirrelLayoutDelegate: NSObject, NSTextLayoutManagerDelegate { +private class SquirrelLayoutDelegate: NSObject, NSTextLayoutManagerDelegate { func textLayoutManager(_ textLayoutManager: NSTextLayoutManager, shouldBreakLineBefore location: any NSTextLocation, hyphenating: Bool) -> Bool { let index = textLayoutManager.offset(from: textLayoutManager.documentRange.location, to: location) if let attributes = textLayoutManager.textContainer?.textView?.textContentStorage?.attributedString?.attributes(at: index, effectiveRange: nil), let noBreak = attributes[.noBreak] as? Bool, noBreak { @@ -23,15 +23,15 @@ extension NSAttributedString.Key { final class SquirrelView: NSView { let textView: NSTextView - + private let squirrelLayoutDelegate: SquirrelLayoutDelegate var candidateRanges: [NSRange] = [] var hilightedIndex = 0 - var preeditRange = NSMakeRange(NSNotFound, 0) - var highlightedPreeditRange = NSMakeRange(NSNotFound, 0) + var preeditRange = NSRange(location: NSNotFound, length: 0) + var highlightedPreeditRange = NSRange(location: NSNotFound, length: 0) var separatorWidth: CGFloat = 0 var shape = CAShapeLayer() - + var lightTheme = SquirrelTheme() var darkTheme = SquirrelTheme() var currentTheme: SquirrelTheme { @@ -46,7 +46,7 @@ final class SquirrelView: NSView { var textContainer: NSTextContainer { textLayoutManager.textContainer! } - + override init(frame frameRect: NSRect) { squirrelLayoutDelegate = SquirrelLayoutDelegate() textView = NSTextView(frame: frameRect) @@ -62,21 +62,21 @@ final class SquirrelView: NSView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override var isFlipped: Bool { true } var isDark: Bool { NSApp.effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua } - + func convert(range: NSRange) -> NSTextRange? { guard range.location != NSNotFound else { return nil } guard let startLocation = textLayoutManager.location(textLayoutManager.documentRange.location, offsetBy: range.location) else { return nil } guard let endLocation = textLayoutManager.location(startLocation, offsetBy: range.length) else { return nil } return NSTextRange(location: startLocation, end: endLocation) } - + // Get the rectangle containing entire contents, expensive to calculate var contentRect: NSRect { var ranges = candidateRanges @@ -87,36 +87,36 @@ final class SquirrelView: NSView { for range in ranges { if let textRange = convert(range: range) { let rect = contentRect(range: textRange) - x0 = min(NSMinX(rect), x0) - x1 = max(NSMaxX(rect), x1) - y0 = min(NSMinY(rect), y0) - y1 = max(NSMaxY(rect), y1) + x0 = min(rect.minX, x0) + x1 = max(rect.maxX, x1) + y0 = min(rect.minY, y0) + y1 = max(rect.maxY, y1) } } - return NSMakeRect(x0, y0, x1-x0, y1-y0) + return NSRect(x: x0, y: y0, width: x1-x0, height: y1-y0) } // Get the rectangle containing the range of text, will first convert to glyph range, expensive to calculate func contentRect(range: NSTextRange) -> NSRect { var x0 = CGFloat.infinity, x1 = -CGFloat.infinity, y0 = CGFloat.infinity, y1 = -CGFloat.infinity textLayoutManager.enumerateTextSegments(in: range, type: .standard, options: .rangeNotRequired) { _, rect, _, _ in - x0 = min(NSMinX(rect), x0) - x1 = max(NSMaxX(rect), x1) - y0 = min(NSMinY(rect), y0) - y1 = max(NSMaxY(rect), y1) + x0 = min(rect.minX, x0) + x1 = max(rect.maxX, x1) + y0 = min(rect.minY, y0) + y1 = max(rect.maxY, y1) return true } - return NSMakeRect(x0, y0, x1-x0, y1-y0) + return NSRect(x: x0, y: y0, width: x1-x0, height: y1-y0) } - + // Will triger - (void)drawRect:(NSRect)dirtyRect - func drawView(candidateRanges: Array, hilightedIndex: Int, preeditRange: NSRange, highlightedPreeditRange: NSRange) { + func drawView(candidateRanges: [NSRange], hilightedIndex: Int, preeditRange: NSRange, highlightedPreeditRange: NSRange) { self.candidateRanges = candidateRanges self.hilightedIndex = hilightedIndex self.preeditRange = preeditRange self.highlightedPreeditRange = highlightedPreeditRange self.needsDisplay = true } - + // All draws happen here override func draw(_ dirtyRect: NSRect) { var backgroundPath: CGPath? @@ -125,12 +125,12 @@ final class SquirrelView: NSView { var highlightedPath: CGMutablePath? var highlightedPreeditPath: CGMutablePath? let theme = currentTheme - + let backgroundRect = dirtyRect var containingRect = dirtyRect - + // Draw preedit Rect - var preeditRect = NSZeroRect + var preeditRect = NSRect.zero if preeditRange.length > 0, let preeditTextRange = convert(range: preeditRange) { preeditRect = contentRect(range: preeditTextRange) preeditRect.size.width = backgroundRect.size.width @@ -145,19 +145,19 @@ final class SquirrelView: NSView { preeditPath = drawSmoothLines(rectVertex(of: preeditRect), straightCorner: Set(), alpha: 0, beta: 0) } } - + containingRect = carveInset(rect: containingRect) // Draw candidate Rects for i in 0.. 0 && theme.highlightedBackColor != nil) { + if candidate.length > 0 && theme.highlightedBackColor != nil { highlightedPath = drawPath(highlightedRange: candidate, backgroundRect: backgroundRect, preeditRect: preeditRect, containingRect: containingRect, extraExpansion: 0)?.mutableCopy() } } else { // Draw other highlighted Rect - if (candidate.length > 0 && theme.candidateBackColor != nil) { + if candidate.length > 0 && theme.candidateBackColor != nil { let candidatePath = drawPath(highlightedRange: candidate, backgroundRect: backgroundRect, preeditRect: preeditRect, containingRect: containingRect, extraExpansion: theme.surroundingExtraExpansion) if candidatePaths == nil { candidatePaths = CGMutablePath() @@ -168,7 +168,7 @@ final class SquirrelView: NSView { } } } - + // Draw highlighted part of preedit text if (highlightedPreeditRange.length > 0) && (theme.highlightedPreeditColor != nil), let highlightedPreeditTextRange = convert(range: highlightedPreeditRange) { var innerBox = preeditRect @@ -185,15 +185,15 @@ final class SquirrelView: NSView { outerBox.size.width -= max(0, theme.hilitedCornerRadius + theme.borderLineWidth) outerBox.origin.x += max(0, theme.hilitedCornerRadius + theme.borderLineWidth) / 2 outerBox.origin.y += max(0, theme.hilitedCornerRadius + theme.borderLineWidth) / 2 - + let (leadingRect, bodyRect, trailingRect) = multilineRects(forRange: highlightedPreeditTextRange, extraSurounding: 0, bounds: outerBox) var (highlightedPoints, highlightedPoints2, rightCorners, rightCorners2) = linearMultilineFor(body: bodyRect, leading: leadingRect, trailing: trailingRect) - + containingRect = carveInset(rect: preeditRect) highlightedPoints = expand(vertex: highlightedPoints, innerBorder: innerBox, outerBorder: outerBox) rightCorners = removeCorner(highlightedPoints: highlightedPoints, rightCorners: rightCorners, containingRect: containingRect) highlightedPreeditPath = drawSmoothLines(highlightedPoints, straightCorner: rightCorners, alpha: 0.3 * theme.hilitedCornerRadius, beta: 1.4 * theme.hilitedCornerRadius)?.mutableCopy() - if (highlightedPoints2.count > 0) { + if highlightedPoints2.count > 0 { highlightedPoints2 = expand(vertex: highlightedPoints2, innerBorder: innerBox, outerBorder: outerBox) rightCorners2 = removeCorner(highlightedPoints: highlightedPoints2, rightCorners: rightCorners2, containingRect: containingRect) let highlightedPreeditPath2 = drawSmoothLines(highlightedPoints2, straightCorner: rightCorners2, alpha: 0.3 * theme.hilitedCornerRadius, beta: 1.4 * theme.hilitedCornerRadius) @@ -202,11 +202,11 @@ final class SquirrelView: NSView { } } } - + NSBezierPath.defaultLineWidth = 0 backgroundPath = drawSmoothLines(rectVertex(of: backgroundRect), straightCorner: Set(), alpha: 0.3 * theme.cornerRadius, beta: 1.4 * theme.cornerRadius) shape.path = backgroundPath - + self.layer?.sublayers = nil let backPath = backgroundPath?.mutableCopy() if let path = preeditPath { @@ -225,7 +225,7 @@ final class SquirrelView: NSView { let panelLayerMask = shapeFromPath(path: backgroundPath) panelLayer.mask = panelLayerMask self.layer?.addSublayer(panelLayer) - + // Fill in colors if let color = theme.preeditBackgroundColor, let path = preeditPath { let layer = shapeFromPath(path: path) @@ -261,7 +261,7 @@ final class SquirrelView: NSView { if theme.shadowSize > 0 { let shadowLayer = CAShapeLayer() shadowLayer.shadowColor = NSColor.black.cgColor - shadowLayer.shadowOffset = NSMakeSize(theme.shadowSize/2, (theme.vertical ? -1 : 1) * theme.shadowSize/2) + shadowLayer.shadowOffset = NSSize(width: theme.shadowSize/2, height: (theme.vertical ? -1 : 1) * theme.shadowSize/2) shadowLayer.shadowPath = highlightedPath shadowLayer.shadowRadius = theme.shadowSize shadowLayer.shadowOpacity = 0.2 @@ -276,37 +276,35 @@ final class SquirrelView: NSView { panelLayer.addSublayer(layer) } } - + func click(at clickPoint: NSPoint) -> (Int?, Int?) { var index = 0 - var candidateIndex: Int? = nil - var preeditIndex: Int? = nil + var candidateIndex: Int? + var preeditIndex: Int? if let path = shape.path, path.contains(clickPoint) { - var point = NSMakePoint(clickPoint.x - textView.textContainerInset.width, - clickPoint.y - textView.textContainerInset.height) + var point = NSPoint(x: clickPoint.x - textView.textContainerInset.width, + y: clickPoint.y - textView.textContainerInset.height) let fragment = textLayoutManager.textLayoutFragment(for: point) if let fragment = fragment { - point = NSMakePoint(point.x - NSMinX(fragment.layoutFragmentFrame), - point.y - NSMinY(fragment.layoutFragmentFrame)) + point = NSPoint(x: point.x - fragment.layoutFragmentFrame.minX, + y: point.y - fragment.layoutFragmentFrame.minY) index = textLayoutManager.offset(from: textLayoutManager.documentRange.location, to: fragment.rangeInElement.location) - for lineFragment in fragment.textLineFragments { - if lineFragment.typographicBounds.contains(point) { - point = NSMakePoint(point.x - NSMinX(lineFragment.typographicBounds), - point.y - NSMinY(lineFragment.typographicBounds)) - index += lineFragment.characterIndex(for: point) - if index >= preeditRange.location && index < preeditRange.upperBound { - preeditIndex = index - } else { - for i in 0..= range.location && index < range.upperBound { - candidateIndex = i - break - } + for lineFragment in fragment.textLineFragments where lineFragment.typographicBounds.contains(point) { + point = NSPoint(x: point.x - lineFragment.typographicBounds.minX, + y: point.y - lineFragment.typographicBounds.minY) + index += lineFragment.characterIndex(for: point) + if index >= preeditRange.location && index < preeditRange.upperBound { + preeditIndex = index + } else { + for i in 0..= range.location && index < range.upperBound { + candidateIndex = i + break } } - break } + break } } } @@ -321,13 +319,13 @@ private extension SquirrelView { return 1 } else if number <= -2 { return -1 - }else { + } else { return number / 2 } } - + // Bezier cubic curve, which has continuous roundness - func drawSmoothLines(_ vertex: Array, straightCorner: Set, alpha: CGFloat, beta rawBeta: CGFloat) -> CGPath? { + func drawSmoothLines(_ vertex: [NSPoint], straightCorner: Set, alpha: CGFloat, beta rawBeta: CGFloat) -> CGPath? { guard vertex.count >= 4 else { return nil } @@ -339,7 +337,7 @@ private extension SquirrelView { var control1: NSPoint var control2: NSPoint var target = previousPoint - var diff = NSMakePoint(point.x - previousPoint.x, point.y - previousPoint.y) + var diff = NSPoint(x: point.x - previousPoint.x, y: point.y - previousPoint.y) if straightCorner.isEmpty || !straightCorner.contains(vertex.count-1) { target.x += sign(diff.x/beta)*beta target.y += sign(diff.y/beta)*beta @@ -354,41 +352,41 @@ private extension SquirrelView { path.addLine(to: target) } else { control1 = point - diff = NSMakePoint(point.x - previousPoint.x, point.y - previousPoint.y) - + diff = NSPoint(x: point.x - previousPoint.x, y: point.y - previousPoint.y) + target.x -= sign(diff.x/beta)*beta control1.x -= sign(diff.x/beta)*alpha target.y -= sign(diff.y/beta)*beta control1.y -= sign(diff.y/beta)*alpha - + path.addLine(to: target) target = point control2 = point - diff = NSMakePoint(nextPoint.x - point.x, nextPoint.y - point.y) - + diff = NSPoint(x: nextPoint.x - point.x, y: nextPoint.y - point.y) + control2.x += sign(diff.x/beta)*alpha target.x += sign(diff.x/beta)*beta control2.y += sign(diff.y/beta)*alpha target.y += sign(diff.y/beta)*beta - + path.addCurve(to: target, control1: control1, control2: control2) } } path.closeSubpath() return path } - - func rectVertex(of rect: NSRect) -> Array { + + func rectVertex(of rect: NSRect) -> [NSPoint] { [rect.origin, - NSMakePoint(rect.origin.x, rect.origin.y+rect.size.height), - NSMakePoint(rect.origin.x+rect.size.width, rect.origin.y+rect.size.height), - NSMakePoint(rect.origin.x+rect.size.width, rect.origin.y)] + NSPoint(x: rect.origin.x, y: rect.origin.y+rect.size.height), + NSPoint(x: rect.origin.x+rect.size.width, y: rect.origin.y+rect.size.height), + NSPoint(x: rect.origin.x+rect.size.width, y: rect.origin.y)] } - + func nearEmpty(_ rect: NSRect) -> Bool { return rect.size.height * rect.size.width < 1 } - + // Calculate 3 boxes containing the text in range. leadingRect and trailingRect are incomplete line rectangle // bodyRect is complete lines in the middle func multilineRects(forRange range: NSTextRange, extraSurounding: Double, bounds: NSRect) -> (NSRect, NSRect, NSRect) { @@ -403,10 +401,10 @@ private extension SquirrelView { lineRects.append(newRect) return true } - - var leadingRect = NSZeroRect - var bodyRect = NSZeroRect - var trailingRect = NSZeroRect + + var leadingRect = NSRect.zero + var bodyRect = NSRect.zero + var trailingRect = NSRect.zero if lineRects.count == 1 { bodyRect = lineRects[0] } else if lineRects.count == 2 { @@ -418,17 +416,17 @@ private extension SquirrelView { var x0 = CGFloat.infinity, x1 = -CGFloat.infinity, y0 = CGFloat.infinity, y1 = -CGFloat.infinity for i in 1..<(lineRects.count-1) { let rect = lineRects[i] - x0 = min(NSMinX(rect), x0) - x1 = max(NSMaxX(rect), x1) - y0 = min(NSMinY(rect), y0) - y1 = max(NSMaxY(rect), y1) + x0 = min(rect.minX, x0) + x1 = max(rect.maxX, x1) + y0 = min(rect.minY, y0) + y1 = max(rect.maxY, y1) } - y0 = min(NSMaxY(leadingRect), y0) - y1 = max(NSMinY(trailingRect), y1) - bodyRect = NSMakeRect(x0, y0, x1-x0, y1-y0) + y0 = min(leadingRect.maxY, y0) + y1 = max(trailingRect.minY, y1) + bodyRect = NSRect(x: x0, y: y0, width: x1-x0, height: y1-y0) } - - if (extraSurounding > 0) { + + if extraSurounding > 0 { if nearEmpty(leadingRect) && nearEmpty(trailingRect) { bodyRect = expandHighlightWidth(rect: bodyRect, extraSurrounding: extraSurounding) } else { @@ -440,27 +438,27 @@ private extension SquirrelView { } } } - + if !nearEmpty(leadingRect) && !nearEmpty(trailingRect) { - leadingRect.size.width = NSMaxX(bounds) - leadingRect.origin.x - trailingRect.size.width = NSMaxX(trailingRect) - NSMinX(bounds) - trailingRect.origin.x = NSMinX(bounds) + leadingRect.size.width = bounds.maxX - leadingRect.origin.x + trailingRect.size.width = trailingRect.maxX - bounds.minX + trailingRect.origin.x = bounds.minX if !nearEmpty(bodyRect) { bodyRect.size.width = bounds.size.width bodyRect.origin.x = bounds.origin.x } else { - let diff = NSMinY(trailingRect) - NSMaxY(leadingRect) + let diff = trailingRect.minY - leadingRect.maxY leadingRect.size.height += diff / 2 trailingRect.size.height += diff / 2 trailingRect.origin.y -= diff / 2 } } - + return (leadingRect, bodyRect, trailingRect) } - + // Based on the 3 boxes from multilineRectForRange, calculate the vertex of the polygon containing the text in range - func multilineVertex(leadingRect: NSRect, bodyRect: NSRect, trailingRect: NSRect) -> Array { + func multilineVertex(leadingRect: NSRect, bodyRect: NSRect, trailingRect: NSRect) -> [NSPoint] { if nearEmpty(bodyRect) && !nearEmpty(leadingRect) && nearEmpty(trailingRect) { return rectVertex(of: leadingRect) } else if nearEmpty(bodyRect) && nearEmpty(leadingRect) && !nearEmpty(trailingRect) { @@ -475,7 +473,7 @@ private extension SquirrelView { let trailingVertex = rectVertex(of: trailingRect) let bodyVertex = rectVertex(of: bodyRect) return [trailingVertex[1], trailingVertex[2], trailingVertex[3], bodyVertex[2], bodyVertex[3], bodyVertex[0]] - } else if !nearEmpty(leadingRect) && !nearEmpty(trailingRect) && nearEmpty(bodyRect) && (NSMaxX(leadingRect)>NSMinX(trailingRect)) { + } else if !nearEmpty(leadingRect) && !nearEmpty(trailingRect) && nearEmpty(bodyRect) && (leadingRect.maxX>trailingRect.minX) { let leadingVertex = rectVertex(of: leadingRect) let trailingVertex = rectVertex(of: trailingRect) return [trailingVertex[0], trailingVertex[1], trailingVertex[2], trailingVertex[3], leadingVertex[2], leadingVertex[3], leadingVertex[0], leadingVertex[1]] @@ -485,13 +483,13 @@ private extension SquirrelView { let trailingVertex = rectVertex(of: trailingRect) return [trailingVertex[1], trailingVertex[2], trailingVertex[3], bodyVertex[2], leadingVertex[3], leadingVertex[0], leadingVertex[1], bodyVertex[0]] } else { - return Array() + return [NSPoint]() } } - + // If the point is outside the innerBox, will extend to reach the outerBox - func expand(vertex: Array, innerBorder: NSRect, outerBorder: NSRect) -> Array { - var newVertex = Array() + func expand(vertex: [NSPoint], innerBorder: NSRect, outerBorder: NSRect) -> [NSPoint] { + var newVertex = [NSPoint]() for i in 0.. CGPoint { if diff.y == 0 && diff.x > 0 { - return NSMakePoint(0, 1) + return NSPoint(x: 0, y: 1) } else if diff.y == 0 && diff.x < 0 { - return NSMakePoint(0, -1) + return NSPoint(x: 0, y: -1) } else if diff.x == 0 && diff.y > 0 { - return NSMakePoint(-1, 0) + return NSPoint(x: -1, y: 0) } else if diff.x == 0 && diff.y < 0 { - return NSMakePoint(1, 0) + return NSPoint(x: 1, y: 0) } else { - return NSMakePoint(0, 0) + return NSPoint(x: 0, y: 0) } } - + func shapeFromPath(path: CGPath?) -> CAShapeLayer { let layer = CAShapeLayer() layer.path = path layer.fillRule = .evenOdd return layer } - + // Assumes clockwise iteration - func enlarge(vertex: Array, by: Double) -> Array { + func enlarge(vertex: [NSPoint], by: Double) -> [NSPoint] { if by != 0 { var previousPoint: NSPoint var point: NSPoint @@ -544,10 +542,10 @@ private extension SquirrelView { point = vertex[i] nextPoint = vertex[(i+1) % vertex.count] newPoint = point - displacement = direction(diff: NSMakePoint(point.x - previousPoint.x, point.y - previousPoint.y)) + displacement = direction(diff: NSPoint(x: point.x - previousPoint.x, y: point.y - previousPoint.y)) newPoint.x += by * displacement.x newPoint.y += by * displacement.y - displacement = direction(diff: NSMakePoint(nextPoint.x - point.x, nextPoint.y - point.y)) + displacement = direction(diff: NSPoint(x: nextPoint.x - point.x, y: nextPoint.y - point.y)) newPoint.x += by * displacement.x newPoint.y += by * displacement.y results[i] = newPoint @@ -557,7 +555,7 @@ private extension SquirrelView { return vertex } } - + // Add gap between horizontal candidates func expandHighlightWidth(rect: NSRect, extraSurrounding: CGFloat) -> NSRect { var newRect = rect @@ -567,13 +565,13 @@ private extension SquirrelView { } return newRect } - - func removeCorner(highlightedPoints: Array, rightCorners: Set, containingRect: NSRect) -> Set { + + func removeCorner(highlightedPoints: [CGPoint], rightCorners: Set, containingRect: NSRect) -> Set { if !highlightedPoints.isEmpty && !rightCorners.isEmpty { var result = rightCorners for cornerIndex in rightCorners { let corner = highlightedPoints[cornerIndex] - let dist = min(NSMaxY(containingRect) - corner.y, corner.y - NSMinY(containingRect)) + let dist = min(containingRect.maxY - corner.y, corner.y - containingRect.minY) if dist < 1e-2 { result.remove(cornerIndex) } @@ -583,12 +581,12 @@ private extension SquirrelView { return rightCorners } } - + func linearMultilineFor(body: NSRect, leading: NSRect, trailing: NSRect) -> (Array, Array, Set, Set) { - let highlightedPoints, highlightedPoints2: Array + let highlightedPoints, highlightedPoints2: [NSPoint] let rightCorners, rightCorners2: Set // Handles the special case where containing boxes are separated - if (nearEmpty(body) && !nearEmpty(leading) && !nearEmpty(trailing) && NSMaxX(trailing) < NSMinX(leading)) { + if nearEmpty(body) && !nearEmpty(leading) && !nearEmpty(trailing) && trailing.maxX < leading.minX { highlightedPoints = rectVertex(of: leading) highlightedPoints2 = rectVertex(of: trailing) rightCorners = [2, 3] @@ -601,17 +599,17 @@ private extension SquirrelView { } return (highlightedPoints, highlightedPoints2, rightCorners, rightCorners2) } - + func drawPath(highlightedRange: NSRange, backgroundRect: NSRect, preeditRect: NSRect, containingRect: NSRect, extraExpansion: Double) -> CGPath? { let theme = currentTheme let resultingPath: CGMutablePath? - + var currentContainingRect = containingRect currentContainingRect.size.width += extraExpansion * 2 currentContainingRect.size.height += extraExpansion * 2 currentContainingRect.origin.x -= extraExpansion currentContainingRect.origin.y -= extraExpansion - + let halfLinespace = theme.linespace / 2 var innerBox = backgroundRect innerBox.size.width -= (theme.edgeInset.width + 1) * 2 - 2 * extraExpansion @@ -627,26 +625,26 @@ private extension SquirrelView { } innerBox.size.height -= theme.linespace innerBox.origin.y += halfLinespace - + var outerBox = backgroundRect outerBox.size.height -= preeditRect.size.height + max(0, theme.hilitedCornerRadius + theme.borderLineWidth) - 2 * extraExpansion outerBox.size.width -= max(0, theme.hilitedCornerRadius + theme.borderLineWidth) - 2 * extraExpansion outerBox.origin.x += max(0.0, theme.hilitedCornerRadius + theme.borderLineWidth) / 2.0 - extraExpansion outerBox.origin.y += preeditRect.size.height + max(0, theme.hilitedCornerRadius + theme.borderLineWidth) / 2 - extraExpansion - + let effectiveRadius = max(0, theme.hilitedCornerRadius + 2 * extraExpansion / theme.hilitedCornerRadius * max(0, theme.cornerRadius - theme.hilitedCornerRadius)) - + if theme.linear, let highlightedTextRange = convert(range: highlightedRange) { let (leadingRect, bodyRect, trailingRect) = multilineRects(forRange: highlightedTextRange, extraSurounding: separatorWidth, bounds: outerBox) var (highlightedPoints, highlightedPoints2, rightCorners, rightCorners2) = linearMultilineFor(body: bodyRect, leading: leadingRect, trailing: trailingRect) - + // Expand the boxes to reach proper border highlightedPoints = enlarge(vertex: highlightedPoints, by: extraExpansion) highlightedPoints = expand(vertex: highlightedPoints, innerBorder: innerBox, outerBorder: outerBox) rightCorners = removeCorner(highlightedPoints: highlightedPoints, rightCorners: rightCorners, containingRect: currentContainingRect) resultingPath = drawSmoothLines(highlightedPoints, straightCorner: rightCorners, alpha: 0.3*effectiveRadius, beta: 1.4*effectiveRadius)?.mutableCopy() - - if (highlightedPoints2.count > 0) { + + if highlightedPoints2.count > 0 { highlightedPoints2 = enlarge(vertex: highlightedPoints2, by: extraExpansion) highlightedPoints2 = expand(vertex: highlightedPoints2, innerBorder: innerBox, outerBorder: outerBox) rightCorners2 = removeCorner(highlightedPoints: highlightedPoints2, rightCorners: rightCorners2, containingRect: currentContainingRect) @@ -660,7 +658,7 @@ private extension SquirrelView { if !nearEmpty(highlightedRect) { highlightedRect.size.width = backgroundRect.size.width highlightedRect.size.height += theme.linespace - highlightedRect.origin = NSMakePoint(backgroundRect.origin.x, highlightedRect.origin.y + theme.edgeInset.height - halfLinespace) + highlightedRect.origin = NSPoint(x: backgroundRect.origin.x, y: highlightedRect.origin.y + theme.edgeInset.height - halfLinespace) if highlightedRange.upperBound == (textView.string as NSString).length { highlightedRect.size.height += theme.edgeInset.height - halfLinespace } @@ -673,7 +671,7 @@ private extension SquirrelView { highlightedRect.origin.y -= theme.hilitedCornerRadius / 2 } } - + var highlightedPoints = rectVertex(of: highlightedRect) highlightedPoints = enlarge(vertex: highlightedPoints, by: extraExpansion) highlightedPoints = expand(vertex: highlightedPoints, innerBorder: innerBox, outerBorder: outerBox) @@ -686,7 +684,7 @@ private extension SquirrelView { } return resultingPath } - + func carveInset(rect: NSRect) -> NSRect { var newRect = rect newRect.size.height -= (currentTheme.hilitedCornerRadius + currentTheme.borderWidth) * 2