diff --git a/Color Picker.xcodeproj/project.pbxproj b/Color Picker.xcodeproj/project.pbxproj index 88080e3..552a3aa 100644 --- a/Color Picker.xcodeproj/project.pbxproj +++ b/Color Picker.xcodeproj/project.pbxproj @@ -7,13 +7,13 @@ objects = { /* Begin PBXBuildFile section */ - E38D432F263AAE8500701B82 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38D432E263AAE8500701B82 /* SettingsView.swift */; }; + E38D432F263AAE8500701B82 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38D432E263AAE8500701B82 /* SettingsScreen.swift */; }; E38D4331263AAEA900701B82 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38D4330263AAEA900701B82 /* Constants.swift */; }; E38D4333263AB24E00701B82 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38D4332263AB24E00701B82 /* Utilities.swift */; }; - E38D4335263AEE3700701B82 /* ColorPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38D4334263AEE3700701B82 /* ColorPickerView.swift */; }; + E38D4335263AEE3700701B82 /* ColorPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38D4334263AEE3700701B82 /* ColorPickerScreen.swift */; }; E390D734263B3C71005FCB34 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E390D733263B3C71005FCB34 /* AppState.swift */; }; E390D736263C6ACD005FCB34 /* ColorPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E390D735263C6ACD005FCB34 /* ColorPanel.swift */; }; - E390D738263C74C2005FCB34 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E390D737263C74C2005FCB34 /* WelcomeView.swift */; }; + E390D738263C74C2005FCB34 /* WelcomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E390D737263C74C2005FCB34 /* WelcomeScreen.swift */; }; E394C2F62642BE4C006B51CF /* InternetAccessPolicy.json in Resources */ = {isa = PBXBuildFile; fileRef = E394C2F52642BE4C006B51CF /* InternetAccessPolicy.json */; }; E394DAAB263D73AD00F5B042 /* Regex in Frameworks */ = {isa = PBXBuildFile; productRef = E394DAAA263D73AD00F5B042 /* Regex */; }; E394DAAE263E95D900F5B042 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = E394DAAD263E95D900F5B042 /* LaunchAtLogin */; }; @@ -57,13 +57,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - E38D432E263AAE8500701B82 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + E38D432E263AAE8500701B82 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = ""; }; E38D4330263AAEA900701B82 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; E38D4332263AB24E00701B82 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; - E38D4334263AEE3700701B82 /* ColorPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerView.swift; sourceTree = ""; }; + E38D4334263AEE3700701B82 /* ColorPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerScreen.swift; sourceTree = ""; }; E390D733263B3C71005FCB34 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; E390D735263C6ACD005FCB34 /* ColorPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPanel.swift; sourceTree = ""; }; - E390D737263C74C2005FCB34 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; + E390D737263C74C2005FCB34 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = ""; }; E394C2F52642BE4C006B51CF /* InternetAccessPolicy.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = InternetAccessPolicy.json; sourceTree = ""; }; E3A3B11925904E7B001B4D0C /* Color Picker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Color Picker.app"; sourceTree = BUILT_PRODUCTS_DIR; }; E3A3B11C25904E7B001B4D0C /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; @@ -150,10 +150,10 @@ E38D4330263AAEA900701B82 /* Constants.swift */, E390D733263B3C71005FCB34 /* AppState.swift */, E3DFA8C82662514800D2623E /* Events.swift */, - E38D4334263AEE3700701B82 /* ColorPickerView.swift */, + E38D4334263AEE3700701B82 /* ColorPickerScreen.swift */, E390D735263C6ACD005FCB34 /* ColorPanel.swift */, - E38D432E263AAE8500701B82 /* SettingsView.swift */, - E390D737263C74C2005FCB34 /* WelcomeView.swift */, + E38D432E263AAE8500701B82 /* SettingsScreen.swift */, + E390D737263C74C2005FCB34 /* WelcomeScreen.swift */, E38D4332263AB24E00701B82 /* Utilities.swift */, E3A3B12025904E7D001B4D0C /* Assets.xcassets */, E3E7D7A827218959009D71F4 /* Intents.intentdefinition */, @@ -332,15 +332,15 @@ buildActionMask = 2147483647; files = ( E3A3B11D25904E7B001B4D0C /* App.swift in Sources */, - E38D432F263AAE8500701B82 /* SettingsView.swift in Sources */, + E38D432F263AAE8500701B82 /* SettingsScreen.swift in Sources */, E38D4333263AB24E00701B82 /* Utilities.swift in Sources */, - E390D738263C74C2005FCB34 /* WelcomeView.swift in Sources */, + E390D738263C74C2005FCB34 /* WelcomeScreen.swift in Sources */, E390D734263B3C71005FCB34 /* AppState.swift in Sources */, E38D4331263AAEA900701B82 /* Constants.swift in Sources */, E3DFA8C92662514800D2623E /* Events.swift in Sources */, E390D736263C6ACD005FCB34 /* ColorPanel.swift in Sources */, E3E7D7A927218959009D71F4 /* Intents.intentdefinition in Sources */, - E38D4335263AEE3700701B82 /* ColorPickerView.swift in Sources */, + E38D4335263AEE3700701B82 /* ColorPickerScreen.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Color Picker/App.swift b/Color Picker/App.swift index bfc4865..db29c85 100644 --- a/Color Picker/App.swift +++ b/Color Picker/App.swift @@ -73,7 +73,7 @@ struct AppMain: App { } } Settings { - SettingsView() + SettingsScreen() } } diff --git a/Color Picker/AppState.swift b/Color Picker/AppState.swift index 589ffcb..83647ea 100644 --- a/Color Picker/AppState.swift +++ b/Color Picker/AppState.swift @@ -25,7 +25,7 @@ final class AppState: ObservableObject { ] colorPanel.makeMain() - let view = ColorPickerView(colorPanel: colorPanel) + let view = ColorPickerScreen(colorPanel: colorPanel) .environmentObject(self) let accessoryView = NSHostingView(rootView: view) colorPanel.accessoryView = accessoryView @@ -93,30 +93,28 @@ final class AppState: ObservableObject { let item = $0 - $0.button!.onAction { [self] in - let event = NSApp.currentEvent! + $0.button!.onAction { [self] event in + let isAlternative = event.isAlternateClickForStatusItem let showMenu = { - item.menu = createMenu() - item.button!.performClick(nil) - item.menu = nil + item.showMenu(createMenu()) } switch Defaults[.menuBarItemClickAction] { case .showMenu: - if event.type == .rightMouseUp { + if isAlternative { pickColor() } else { showMenu() } case .showColorSampler: - if event.type == .rightMouseUp { + if isAlternative { showMenu() } else { pickColor() } case .toggleWindow: - if event.type == .rightMouseUp { + if isAlternative { showMenu() } else { colorPanel.toggle() diff --git a/Color Picker/ColorPanel.swift b/Color Picker/ColorPanel.swift index bb4c883..204f326 100644 --- a/Color Picker/ColorPanel.swift +++ b/Color Picker/ColorPanel.swift @@ -1,6 +1,7 @@ import Cocoa import Defaults +@MainActor final class ColorPanel: NSColorPanel, NSWindowDelegate { override var canBecomeMain: Bool { true } override var canBecomeKey: Bool { true } diff --git a/Color Picker/ColorPickerView.swift b/Color Picker/ColorPickerScreen.swift similarity index 98% rename from Color Picker/ColorPickerView.swift rename to Color Picker/ColorPickerScreen.swift index 502aa1e..0e32ac1 100644 --- a/Color Picker/ColorPickerView.swift +++ b/Color Picker/ColorPickerScreen.swift @@ -121,7 +121,7 @@ private struct BarView: View { } } -struct ColorPickerView: View { +struct ColorPickerScreen: View { @Default(.uppercaseHexColor) private var uppercaseHexColor @Default(.hashPrefixInHexColor) private var hashPrefixInHexColor @Default(.legacyColorSyntax) private var legacyColorSyntax @@ -369,8 +369,8 @@ struct ColorPickerView: View { } } -struct ColorPickerView_Previews: PreviewProvider { +struct ColorPickerScreen_Previews: PreviewProvider { static var previews: some View { - ColorPickerView(colorPanel: .shared) + ColorPickerScreen(colorPanel: .shared) } } diff --git a/Color Picker/SettingsView.swift b/Color Picker/SettingsScreen.swift similarity index 98% rename from Color Picker/SettingsView.swift rename to Color Picker/SettingsScreen.swift index 1947472..4a0ead3 100644 --- a/Color Picker/SettingsView.swift +++ b/Color Picker/SettingsScreen.swift @@ -34,6 +34,7 @@ private struct MenuBarItemClickActionSetting: View { } .fixedSize() Text(menuBarItemClickAction.tip) + .offset(x: 2) .settingSubtitleTextStyle() .frame(maxWidth: .infinity, alignment: .trailing) } @@ -197,7 +198,7 @@ private struct AdvancedSettings: View { } } -struct SettingsView: View { +struct SettingsScreen: View { var body: some View { TabView { GeneralSettings() @@ -216,8 +217,8 @@ struct SettingsView: View { } } -struct SettingsView_Previews: PreviewProvider { +struct SettingsScreen_Previews: PreviewProvider { static var previews: some View { - SettingsView() + SettingsScreen() } } diff --git a/Color Picker/Utilities.swift b/Color Picker/Utilities.swift index 1023b3a..3d8c001 100644 --- a/Color Picker/Utilities.swift +++ b/Color Picker/Utilities.swift @@ -473,10 +473,10 @@ extension NSColor { color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) return .init( - red: red.double, - green: green.double, - blue: blue.double, - alpha: alpha.double + red: red, + green: green, + blue: blue, + alpha: alpha ) } } @@ -1191,18 +1191,6 @@ struct NativeTextField: NSViewRepresentable { } -extension NSColorPanel { - /** - Show the color sampler. - */ - func showColorSampler() { - // "_magnify:" - let selector = String(":yfingam_".reversed()) - perform(NSSelectorFromString(selector)) - } -} - - extension NSColorPanel { /** Publishes when the color in the color panel changes. @@ -1333,15 +1321,15 @@ protocol ControlActionClosureProtocol: NSObjectProtocol { } private final class ActionTrampoline: NSObject { - private let action: () -> Void + private let action: (NSEvent) -> Void - init(action: @escaping () -> Void) { + init(action: @escaping (NSEvent) -> Void) { self.action = action } @objc fileprivate func handleAction(_ sender: AnyObject) { - action() + action(NSApp.currentEvent!) } } @@ -1352,12 +1340,12 @@ extension ControlActionClosureProtocol { ``` let button = NSButton(title: "Unicorn", target: nil, action: nil) - button.onAction { + button.onAction { _ in print("Button action") } ``` */ - func onAction(_ action: @escaping () -> Void) { + func onAction(_ action: @escaping (NSEvent) -> Void) { let trampoline = ActionTrampoline(action: action) target = trampoline self.action = #selector(ActionTrampoline.handleAction) @@ -2772,6 +2760,7 @@ extension Shape where Self == RoundedRectangle { extension View { + @available(macOS, obsoleted: 12) @ViewBuilder func menuIndicatorHidden() -> some View { if #available(macOS 12, *) { @@ -2806,3 +2795,55 @@ extension Task where Success == Never, Failure == Never { try await sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) } } + + +extension NSStatusItem { + /** + Show a one-time menu from the status item. + */ + func showMenu(_ menu: NSMenu) { + self.menu = menu + button!.performClick(nil) + self.menu = nil + } +} + + +extension NSEvent { + /** + Real modifiers. + + - Note: Prefer this over `.modifierFlags`. + + ``` + // Check if Command is one of possible more modifiers keys + event.modifiers.contains(.command) + + // Check if Command is the only modifier key + event.modifiers == .command + + // Check if Command and Shift are the only modifiers + event.modifiers == [.command, .shift] + ``` + */ + var modifiers: ModifierFlags { + modifierFlags + .intersection(.deviceIndependentFlagsMask) + // We remove `capsLock` as it shouldn't affect the modifiers. + // We remove `numericPad`/`function` as arrow keys trigger it, use `event.specialKeys` instead. + .subtracting([.capsLock, .numericPad, .function]) + } +} + + +extension NSEvent { + var isAlternateMouseUp: Bool { + type == .rightMouseUp + || (type == .leftMouseUp && modifiers == .control) + } + + var isAlternateClickForStatusItem: Bool { + isAlternateMouseUp + || (type == .leftMouseUp && modifiers == .option) + } +} diff --git a/Color Picker/WelcomeView.swift b/Color Picker/WelcomeScreen.swift similarity index 100% rename from Color Picker/WelcomeView.swift rename to Color Picker/WelcomeScreen.swift