From 00928733862a177cfa791a705c44a4adf0328b3f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 2 Dec 2022 01:03:23 +0700 Subject: [PATCH] Minor tweaks --- Color Picker.xcodeproj/project.pbxproj | 4 +- Color Picker/App.swift | 20 +- Color Picker/AppState.swift | 2 - Color Picker/ColorPanel.swift | 1 - Color Picker/ColorPickerScreen.swift | 265 +++++++++++++------------ Color Picker/Constants.swift | 2 +- Color Picker/Events.swift | 17 +- Color Picker/SettingsScreen.swift | 5 +- Color Picker/Utilities.swift | 4 + Color Picker/WelcomeScreen.swift | 4 + 10 files changed, 172 insertions(+), 152 deletions(-) diff --git a/Color Picker.xcodeproj/project.pbxproj b/Color Picker.xcodeproj/project.pbxproj index 781a70f..6abdea4 100644 --- a/Color Picker.xcodeproj/project.pbxproj +++ b/Color Picker.xcodeproj/project.pbxproj @@ -326,7 +326,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "swiftlint\n"; + shellScript = "PATH=\"/opt/homebrew/bin/:${PATH}\"\nswiftlint\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -661,7 +661,7 @@ repositoryURL = "https://github.com/sindresorhus/Defaults"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 6.3.0; + minimumVersion = 7.1.0; }; }; E3F4BC852788A5780075DC52 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { diff --git a/Color Picker/App.swift b/Color Picker/App.swift index e438a2b..6deeccd 100644 --- a/Color Picker/App.swift +++ b/Color Picker/App.swift @@ -1,7 +1,8 @@ import SwiftUI -import Defaults import LaunchAtLogin +// TODO: Remove the view menu + /** NOTES: - The "com.apple.security.files.user-selected.read-only" entitlement is required by the "Open" menu in the "Color Palettes" pane. @@ -62,6 +63,10 @@ struct AppMain: App { .keyboardShortcut("v", modifiers: [.shift, .command]) .disabled(NSColor.fromPasteboardGraceful(.general) == nil) } + CommandGroup(after: .windowSize) { + Defaults.Toggle("Stay on Top", key: .stayOnTop) + .keyboardShortcut("t", modifiers: [.control, .command]) + } CommandGroup(replacing: .help) { Link("What is LCH color?", destination: "https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/") Link("FAQ", destination: "https://github.com/sindresorhus/System-Color-Picker#faq") @@ -136,10 +141,11 @@ private final class AppDelegate: NSObject, NSApplicationDelegate { } } - // Does not work on macOS 12.0.1 because of `WindowGroup`: https://github.com/feedback-assistant/reports/issues/246 - // This is only run when the app is started when it's already running. -// func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { -// AppState.shared.handleAppReopen() -// return true -// } + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + if #available(macOS 13, *) { + AppState.shared.handleAppReopen() + } + + return false + } } diff --git a/Color Picker/AppState.swift b/Color Picker/AppState.swift index 12d0a4e..b83cba1 100644 --- a/Color Picker/AppState.swift +++ b/Color Picker/AppState.swift @@ -1,6 +1,4 @@ import SwiftUI -import Combine -import Defaults import Sentry @MainActor diff --git a/Color Picker/ColorPanel.swift b/Color Picker/ColorPanel.swift index b816b6a..b8b7edd 100644 --- a/Color Picker/ColorPanel.swift +++ b/Color Picker/ColorPanel.swift @@ -1,5 +1,4 @@ import Cocoa -import Defaults @MainActor final class ColorPanel: NSColorPanel, NSWindowDelegate { diff --git a/Color Picker/ColorPickerScreen.swift b/Color Picker/ColorPickerScreen.swift index 0640278..fcd9fed 100644 --- a/Color Picker/ColorPickerScreen.swift +++ b/Color Picker/ColorPickerScreen.swift @@ -1,133 +1,4 @@ import SwiftUI -import Defaults - -private struct RecentlyPickedColorsButton: View { - @EnvironmentObject private var appState: AppState - @Default(.recentlyPickedColors) private var recentlyPickedColors - - // TODO: Find a better way to handle this than having to subscribe to each key. - @Default(.preferredColorFormat) private var preferredColorFormat // Only to get updates - @Default(.uppercaseHexColor) private var uppercaseHexColor // Only to get updates - @Default(.hashPrefixInHexColor) private var hashPrefixInHexColor // Only to get updates - @Default(.legacyColorSyntax) private var legacyColorSyntax // Only to get updates - - var body: some View { - Menu { - Group { - ForEach(recentlyPickedColors.reversed()) { color in - Button { - appState.colorPanel.color = color - } label: { - Label { - Text(color.stringRepresentation) - } icon: { - // We don't use SwiftUI here as it only supports showing an actual image. (macOS 12.0) - // https://github.com/feedback-assistant/reports/issues/247 - Image(nsImage: color.swatchImage) - } - .labelStyle(.titleAndIcon) - } - } - Divider() - Button("Clear") { - recentlyPickedColors = [] - } - } - // TODO: Remove when targeting macOS 13 where it's fixed. - // Without, it becomes disabled. (macOS 12.4) - .buttonStyle(.automatic) - } label: { - Image(systemName: "clock.fill") - .controlSize(.large) -// .padding(8) // Has no effect. (macOS 12.0.1) - .contentShape(.rectangle) - } - .menuIndicator(.hidden) - .padding(8) - .fixedSize() - .opacity(0.6) // Try to match the other buttons. - .disabled(recentlyPickedColors.isEmpty) - .help(recentlyPickedColors.isEmpty ? "No recently picked colors" : "Recently picked colors") - } -} - -private struct BarView: View { - @Environment(\.colorScheme) private var colorScheme - @EnvironmentObject private var appState: AppState - @StateObject private var pasteboardObserver = NSPasteboard.SimpleObservable(.general).stop() - @Default(.showInMenuBar) private var showInMenuBar - - var body: some View { - HStack(spacing: 12) { - Button { - appState.pickColor() - } label: { - Image(systemName: "eyedropper") - .font(.system(size: 14).bold()) - .padding(8) - } - .contentShape(.rectangle) - .help("Pick color") - .keyboardShortcut("p") - .padding(.leading, 4) - Button { - appState.pasteColor() - } label: { - Image(systemName: "paintbrush.fill") - .padding(8) - } - .contentShape(.rectangle) - .help("Paste color in the format Hex, HSL, RGB, or LCH") - .keyboardShortcut("v", modifiers: [.shift, .command]) - .disabled(NSColor.fromPasteboardGraceful(.general) == nil) - RecentlyPickedColorsButton() - actionButton - Spacer() - } - // Cannot do this as the `Menu` buttons don't respect it. (macOS 12.0.1) - // https://github.com/feedback-assistant/reports/issues/249 -// .font(.title3) - .background { - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color.black.opacity(colorScheme == .dark ? 0.17 : 0.05)) - } - .padding(.vertical, 4) - .buttonStyle(.borderless) - .menuStyle(.borderlessButton) - .onAppearOnScreen { - pasteboardObserver.start() - } - .onDisappearFromScreen { - pasteboardObserver.stop() - } - } - - private var actionButton: some View { - Menu { - Button("Copy as HSB") { - appState.colorPanel.color.hsbColorString.copyToPasteboard() - } - if showInMenuBar { - Divider() - Button(OS.isMacOS13OrLater ? "Settings…" : "Preferences…") { - SSApp.showSettingsWindow() - } - .keyboardShortcut(",") - } - } label: { - Label("Action", systemImage: "ellipsis.circle.fill") - .labelStyle(.iconOnly) -// .padding(8) // Has no effect. (macOS 12.0.1) - } - // TODO: Remove when targeting macOS 13 where it's fixed. - .buttonStyle(.automatic) // Without, it becomes disabled: https://github.com/feedback-assistant/reports/issues/250 (macOS 12.0.1) - .padding(8) - .contentShape(.rectangle) - .fixedSize() - .opacity(0.6) // Try to match the other buttons. - .menuIndicator(.hidden) - } -} struct ColorPickerScreen: View { @EnvironmentObject private var appState: AppState @@ -136,6 +7,7 @@ struct ColorPickerScreen: View { @Default(.legacyColorSyntax) private var legacyColorSyntax @Default(.shownColorFormats) private var shownColorFormats @Default(.largerText) private var largerText + @Default(.showAccessibilityColorName) private var showAccessibilityColorName @State private var hexColor = "" @State private var hslColor = "" @State private var rgbColor = "" @@ -312,6 +184,11 @@ struct ColorPickerScreen: View { if shownColorFormats.contains(.lch) { lchColorView } + if showAccessibilityColorName { + Text(colorPanel.color.accessibilityName) + .font(.system(largerText ? .title3 : .body)) + .textSelection(.enabled) + } } .padding(9) // 244 makes `HSL` always fit in the text field. @@ -380,3 +257,133 @@ struct ColorPickerScreen_Previews: PreviewProvider { ColorPickerScreen(colorPanel: .shared) } } + +private struct BarView: View { + @Environment(\.colorScheme) private var colorScheme + @EnvironmentObject private var appState: AppState + @StateObject private var pasteboardObserver = NSPasteboard.SimpleObservable(.general).stop() + @Default(.showInMenuBar) private var showInMenuBar + + var body: some View { + HStack(spacing: 12) { + Button { + appState.pickColor() + } label: { + Image(systemName: "eyedropper") + .font(.system(size: 14).bold()) + .padding(8) + } + .contentShape(.rectangle) + .help("Pick color") + .keyboardShortcut("p") + .padding(.leading, 4) + Button { + appState.pasteColor() + } label: { + Image(systemName: "paintbrush.fill") + .padding(8) + } + .contentShape(.rectangle) + .help("Paste color in the format Hex, HSL, RGB, or LCH") + .keyboardShortcut("v", modifiers: [.shift, .command]) + .disabled(NSColor.fromPasteboardGraceful(.general) == nil) + RecentlyPickedColorsButton() + actionButton + Spacer() + } + // Cannot do this as the `Menu` buttons don't respect it. (macOS 12.0.1) + // https://github.com/feedback-assistant/reports/issues/249 +// .font(.title3) + .background { + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.black.opacity(colorScheme == .dark ? 0.17 : 0.05)) + } + .padding(.vertical, 4) + .buttonStyle(.borderless) + .menuStyle(.borderlessButton) + .onAppearOnScreen { + pasteboardObserver.start() + } + .onDisappearFromScreen { + pasteboardObserver.stop() + } + } + + private var actionButton: some View { + Menu { + Button("Copy as HSB") { + appState.colorPanel.color.hsbColorString.copyToPasteboard() + } + if showInMenuBar { + Divider() + Defaults.Toggle("Stay on Top", key: .stayOnTop) + Divider() + Button(OS.isMacOS13OrLater ? "Settings…" : "Preferences…") { + SSApp.showSettingsWindow() + } + .keyboardShortcut(",") + } + } label: { + Label("Action", systemImage: "ellipsis.circle.fill") + .labelStyle(.iconOnly) +// .padding(8) // Has no effect. (macOS 12.0.1) + } + // TODO: Remove when targeting macOS 13 where it's fixed. + .buttonStyle(.automatic) // Without, it becomes disabled: https://github.com/feedback-assistant/reports/issues/250 (macOS 12.0.1) + .padding(8) + .contentShape(.rectangle) + .fixedSize() + .opacity(0.6) // Try to match the other buttons. + .menuIndicator(.hidden) + } +} + +private struct RecentlyPickedColorsButton: View { + @EnvironmentObject private var appState: AppState + @Default(.recentlyPickedColors) private var recentlyPickedColors + + // TODO: Find a better way to handle this than having to subscribe to each key. + @Default(.preferredColorFormat) private var preferredColorFormat // Only to get updates + @Default(.uppercaseHexColor) private var uppercaseHexColor // Only to get updates + @Default(.hashPrefixInHexColor) private var hashPrefixInHexColor // Only to get updates + @Default(.legacyColorSyntax) private var legacyColorSyntax // Only to get updates + + var body: some View { + Menu { + Group { + ForEach(recentlyPickedColors.reversed()) { color in + Button { + appState.colorPanel.color = color + } label: { + Label { + Text(color.stringRepresentation) + } icon: { + // We don't use SwiftUI here as it only supports showing an actual image. (macOS 12.0) + // https://github.com/feedback-assistant/reports/issues/247 + Image(nsImage: color.swatchImage) + } + .labelStyle(.titleAndIcon) + } + } + Divider() + Button("Clear") { + recentlyPickedColors = [] + } + } + // TODO: Remove when targeting macOS 13 where it's fixed. + // Without, it becomes disabled. (macOS 12.4) + .buttonStyle(.automatic) + } label: { + Image(systemName: "clock.fill") + .controlSize(.large) +// .padding(8) // Has no effect. (macOS 12.0.1) + .contentShape(.rectangle) + } + .menuIndicator(.hidden) + .padding(8) + .fixedSize() + .opacity(0.6) // Try to match the other buttons. + .disabled(recentlyPickedColors.isEmpty) + .help(recentlyPickedColors.isEmpty ? "No recently picked colors" : "Recently picked colors") + } +} diff --git a/Color Picker/Constants.swift b/Color Picker/Constants.swift index 291cbff..5e2dac3 100644 --- a/Color Picker/Constants.swift +++ b/Color Picker/Constants.swift @@ -1,5 +1,4 @@ import Cocoa -import Defaults import KeyboardShortcuts extension Defaults.Keys { @@ -18,6 +17,7 @@ extension Defaults.Keys { static let shownColorFormats = Key>("shownColorFormats", default: [.hex, .hsl, .rgb, .lch]) static let largerText = Key("largerText", default: false) static let copyColorAfterPicking = Key("copyColorAfterPicking", default: false) + static let showAccessibilityColorName = Key("showAccessibilityColorName", default: false) // Deprecated static let colorFormatToCopyAfterPicking = Key("colorFormatToCopyAfterPicking", default: .none) diff --git a/Color Picker/Events.swift b/Color Picker/Events.swift index b5d2240..6b8d0f5 100644 --- a/Color Picker/Events.swift +++ b/Color Picker/Events.swift @@ -1,5 +1,4 @@ import SwiftUI -import Defaults import KeyboardShortcuts import LaunchAtLogin @@ -45,13 +44,15 @@ extension AppState { colorPanel.toggle() } - // We use this instead of `applicationShouldHandleReopen` because of the macOS bug. - // https://github.com/feedback-assistant/reports/issues/246 - NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification) - .sink { [self] _ in - handleAppReopen() - } - .store(in: &cancellables) + if #unavailable(macOS 13) { + // We use this instead of `applicationShouldHandleReopen` because of the macOS bug. + // https://github.com/feedback-assistant/reports/issues/246 + NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification) + .sink { [self] _ in + handleAppReopen() + } + .store(in: &cancellables) + } // Workaround for the color picker window not becoming active after the settings window closes. (macOS 11.3) NotificationCenter.default.publisher(for: NSWindow.willCloseNotification) diff --git a/Color Picker/SettingsScreen.swift b/Color Picker/SettingsScreen.swift index f31cb4f..9ac38b7 100644 --- a/Color Picker/SettingsScreen.swift +++ b/Color Picker/SettingsScreen.swift @@ -1,5 +1,4 @@ import SwiftUI -import Defaults import LaunchAtLogin import KeyboardShortcuts @@ -29,6 +28,8 @@ private struct GeneralSettings: View { Defaults.Toggle("Stay on top", key: .stayOnTop) .help("Make the color picker window stay on top of all other windows.") .padding(.bottom, 8) + Defaults.Toggle("Copy color in preferred format after picking", key: .copyColorAfterPicking) + .padding(.bottom, 8) Defaults.Toggle("Show in menu bar instead of Dock", key: .showInMenuBar) .help("If you have “Keep in Dock” enabled when activating this setting, you should disable that since the Dock icon will no longer be functional.") Group { @@ -123,8 +124,8 @@ private struct AdvancedSettings: View { Form { Defaults.Toggle("Show color sampler when opening window", key: .showColorSamplerOnOpen) .help("Show the color picker loupe when the color picker window is shown.") - Defaults.Toggle("Copy color in preferred format after picking", key: .copyColorAfterPicking) Defaults.Toggle("Use larger text in text fields", key: .largerText) + Defaults.Toggle("Show accessibility color name", key: .showAccessibilityColorName) } .padding() .padding(.vertical) diff --git a/Color Picker/Utilities.swift b/Color Picker/Utilities.swift index 9f4a905..e22048b 100644 --- a/Color Picker/Utilities.swift +++ b/Color Picker/Utilities.swift @@ -9,6 +9,10 @@ import Regex import Sentry #endif +typealias Defaults = _Defaults +typealias Default = _Default +typealias AnyCancellable = Combine.AnyCancellable + /* Non-reusable utilities */ diff --git a/Color Picker/WelcomeScreen.swift b/Color Picker/WelcomeScreen.swift index 5fd9391..e54ee05 100644 --- a/Color Picker/WelcomeScreen.swift +++ b/Color Picker/WelcomeScreen.swift @@ -6,6 +6,10 @@ extension AppState { return } + if NSWorkspace.shared.accessibilityDisplayShouldDifferentiateWithoutColor { + Defaults[.showAccessibilityColorName] = true + } + NSApp.activate(ignoringOtherApps: true) NSAlert.showModal(