From cf553c1ec271c987f2563ea7e40e534f21224750 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 8 Jul 2022 00:03:39 +0200 Subject: [PATCH] Minor tweaks --- .swiftlint.yml | 17 +- Color Picker.xcodeproj/project.pbxproj | 40 ++-- .../xcschemes/Color Picker.xcscheme | 2 +- Color Picker/App.swift | 19 +- Color Picker/AppState.swift | 23 ++- Color Picker/ColorPickerScreen.swift | 12 +- Color Picker/Constants.swift | 38 ---- Color Picker/Info.plist | 2 +- Color Picker/Intents.intentdefinition | 8 +- Color Picker/SettingsScreen.swift | 192 +++++++++--------- Color Picker/Utilities.swift | 108 ++++------ IntentsExtension/Info.plist | 4 +- 12 files changed, 224 insertions(+), 241 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 676b6fe..fe3fbf0 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,5 +1,5 @@ only_rules: - - anyobject_protocol + - accessibility_trait_for_button - array_init - block_based_kvo - class_delegate_protocol @@ -10,6 +10,7 @@ only_rules: - collection_alignment - colon - comma + - comma_inheritance - compiler_protocol_init - computed_accessors_order - conditional_returns_on_newline @@ -77,12 +78,11 @@ only_rules: - no_fallthrough_only - no_space_in_method_call - notification_center_detachment + - ns_number_init_as_function_reference - nsobject_prefer_isequal - - number_separator - opening_brace - operator_usage_whitespace - operator_whitespace - - orphaned_doc_comment - overridden_super_call - prefer_self_in_static_references - prefer_self_type_over_type_of_self @@ -106,8 +106,10 @@ only_rules: - required_enum_case - return_arrow_whitespace - return_value_from_void_function + - self_binding - self_in_property_initialization - shorthand_operator + - shorthand_optional_binding - sorted_first_last - statement_position - static_operator @@ -137,9 +139,9 @@ only_rules: - unused_setter_value - valid_ibinspectable - vertical_parameter_alignment - - vertical_parameter_alignment_on_call - vertical_whitespace_closing_braces - vertical_whitespace_opening_braces + - void_function_in_ternary - void_return - xct_specific_matcher - xctfail_message @@ -149,8 +151,8 @@ analyzer_rules: - unused_declaration - unused_import - typesafe_array_init -number_separator: - minimum_length: 5 +for_where: + allow_for_as_filter: true identifier_name: max_length: warning: 100 @@ -203,3 +205,6 @@ custom_rules: final_class: regex: '^class [a-zA-Z\d]+[^{]+\{' message: 'Classes should be marked as final whenever possible. If you actually need it to be subclassable, just add `// swiftlint:disable:next final_class`.' + no_alignment_center: + regex: '\b\(alignment: .center\b' + message: 'This alignment is the default.' diff --git a/Color Picker.xcodeproj/project.pbxproj b/Color Picker.xcodeproj/project.pbxproj index f7fcb63..781a70f 100644 --- a/Color Picker.xcodeproj/project.pbxproj +++ b/Color Picker.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -24,7 +24,7 @@ E3DFA8C92662514800D2623E /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DFA8C82662514800D2623E /* Events.swift */; }; E3E7D79B27218903009D71F4 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E3E7D79A27218903009D71F4 /* Intents.framework */; platformFilter = maccatalyst; }; E3E7D79E27218903009D71F4 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E7D79D27218903009D71F4 /* IntentHandler.swift */; }; - E3E7D7A227218903009D71F4 /* IntentsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E3E7D79927218903009D71F4 /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + E3E7D7A227218903009D71F4 /* IntentsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E3E7D79927218903009D71F4 /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E3E7D7A927218959009D71F4 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = E3E7D7A827218959009D71F4 /* Intents.intentdefinition */; }; E3E7D7AA27218959009D71F4 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = E3E7D7A827218959009D71F4 /* Intents.intentdefinition */; }; E3E7D7AC27218C03009D71F4 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38D4332263AB24E00701B82 /* Utilities.swift */; }; @@ -44,15 +44,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - E3E7D7A327218903009D71F4 /* Embed App Extensions */ = { + E3E7D7A327218903009D71F4 /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - E3E7D7A227218903009D71F4 /* IntentsExtension.appex in Embed App Extensions */, + E3E7D7A227218903009D71F4 /* IntentsExtension.appex in Embed Foundation Extensions */, ); - name = "Embed App Extensions"; + name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -186,7 +186,7 @@ E3A3B11625904E7B001B4D0C /* Frameworks */, E3A3B11725904E7B001B4D0C /* Resources */, E394DAAF263E95E200F5B042 /* Copy “Launch at Login Helper” */, - E3E7D7A327218903009D71F4 /* Embed App Extensions */, + E3E7D7A327218903009D71F4 /* Embed Foundation Extensions */, ); buildRules = ( ); @@ -233,7 +233,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1310; - LastUpgradeCheck = 1250; + LastUpgradeCheck = 1400; TargetAttributes = { E3A3B11825904E7B001B4D0C = { CreatedOnToolsVersion = 12.3; @@ -244,7 +244,7 @@ }; }; buildConfigurationList = E3A3B11425904E7B001B4D0C /* Build configuration list for PBXProject "Color Picker" */; - compatibilityVersion = "Xcode 13.0"; + compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -291,6 +291,7 @@ /* Begin PBXShellScriptBuildPhase section */ E394DAAF263E95E200F5B042 /* Copy “Launch at Login Helper” */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -310,6 +311,7 @@ }; E3E9F9AE2642B8F800AE6450 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -403,6 +405,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -420,7 +423,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -466,6 +469,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -477,7 +481,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -497,11 +501,13 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = YG56YK5RN5; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "Color Picker/Info.plist"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -522,11 +528,13 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = YG56YK5RN5; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "Color Picker/Info.plist"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -544,6 +552,7 @@ CODE_SIGN_ENTITLEMENTS = IntentsExtension/IntentsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = YG56YK5RN5; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -555,7 +564,6 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; PRODUCT_BUNDLE_IDENTIFIER = "com.sindresorhus.Color-Picker.IntentsExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -571,6 +579,7 @@ CODE_SIGN_ENTITLEMENTS = IntentsExtension/IntentsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = YG56YK5RN5; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -582,7 +591,6 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; PRODUCT_BUNDLE_IDENTIFIER = "com.sindresorhus.Color-Picker.IntentsExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -637,7 +645,7 @@ repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.2.0; + minimumVersion = 5.0.0; }; }; E394DAB0263E965500F5B042 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { @@ -645,7 +653,7 @@ repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.6.0; + minimumVersion = 1.9.0; }; }; E3E14060259A0D97004FC89F /* XCRemoteSwiftPackageReference "Defaults" */ = { @@ -653,7 +661,7 @@ repositoryURL = "https://github.com/sindresorhus/Defaults"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 6.2.1; + minimumVersion = 6.3.0; }; }; E3F4BC852788A5780075DC52 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { @@ -661,7 +669,7 @@ repositoryURL = "https://github.com/getsentry/sentry-cocoa"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 7.17.0; + minimumVersion = 7.31.3; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Color Picker.xcodeproj/xcshareddata/xcschemes/Color Picker.xcscheme b/Color Picker.xcodeproj/xcshareddata/xcschemes/Color Picker.xcscheme index 5bf4923..4e68840 100644 --- a/Color Picker.xcodeproj/xcshareddata/xcschemes/Color Picker.xcscheme +++ b/Color Picker.xcodeproj/xcshareddata/xcschemes/Color Picker.xcscheme @@ -1,6 +1,6 @@ Bool { true } + func applicationDidFinishLaunching(_ notification: Notification) { + if #available(macOS 13, *) { + SSApp.swiftUIMainWindow?.close() + } + } // 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. diff --git a/Color Picker/AppState.swift b/Color Picker/AppState.swift index 1017ec1..12d0a4e 100644 --- a/Color Picker/AppState.swift +++ b/Color Picker/AppState.swift @@ -13,6 +13,7 @@ final class AppState: ObservableObject { let colorPanel = ColorPanel() colorPanel.titleVisibility = .hidden colorPanel.hidesOnDeactivate = false + colorPanel.becomesKeyOnlyIfNeeded = false colorPanel.isFloatingPanel = false colorPanel.isRestorable = false colorPanel.styleMask.remove(.utilityWindow) @@ -20,8 +21,10 @@ final class AppState: ObservableObject { colorPanel.standardWindowButton(.zoomButton)?.isHidden = true colorPanel.tabbingMode = .disallowed colorPanel.collectionBehavior = [ - .moveToActiveSpace, + .canJoinAllSpaces, .fullScreenAuxiliary + // We cannot enable tiling as then it doesn't show up in fullscreen spaces. (macOS 12.5) +// .fullScreenAllowsTiling ] colorPanel.makeMain() @@ -145,6 +148,8 @@ final class AppState: ObservableObject { if Defaults[.showInMenuBar] { colorPanel.close() + } else { + colorPanel.makeKeyAndOrderFront(nil) } #if DEBUG @@ -162,12 +167,16 @@ final class AppState: ObservableObject { } private func fixStuff() { - // Make the invisible native SwitUI window not block access to the desktop. (macOS 12.0) - // https://github.com/feedback-assistant/reports/issues/253 - SSApp.swiftUIMainWindow?.ignoresMouseEvents = true - - // Make the invisible native SwiftUI window not show up in mission control when in menu bar mode. (macOS 11.6) - SSApp.swiftUIMainWindow?.collectionBehavior = .stationary + if #available(macOS 13, *) { + SSApp.swiftUIMainWindow?.close() + } else { + // Make the invisible native SwitUI window not block access to the desktop. (macOS 12.0) + // https://github.com/feedback-assistant/reports/issues/253 + SSApp.swiftUIMainWindow?.ignoresMouseEvents = true + + // Make the invisible native SwiftUI window not show up in mission control when in menu bar mode. (macOS 11.6) + SSApp.swiftUIMainWindow?.collectionBehavior = .stationary + } } private func requestReview() { diff --git a/Color Picker/ColorPickerScreen.swift b/Color Picker/ColorPickerScreen.swift index ece1d0e..0640278 100644 --- a/Color Picker/ColorPickerScreen.swift +++ b/Color Picker/ColorPickerScreen.swift @@ -33,6 +33,7 @@ private struct RecentlyPickedColorsButton: View { recentlyPickedColors = [] } } + // TODO: Remove when targeting macOS 13 where it's fixed. // Without, it becomes disabled. (macOS 12.4) .buttonStyle(.automatic) } label: { @@ -80,13 +81,13 @@ private struct BarView: View { .keyboardShortcut("v", modifiers: [.shift, .command]) .disabled(NSColor.fromPasteboardGraceful(.general) == nil) RecentlyPickedColorsButton() - moreButton + 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) - .background2 { + .background { RoundedRectangle(cornerRadius: 6, style: .continuous) .fill(Color.black.opacity(colorScheme == .dark ? 0.17 : 0.05)) } @@ -101,7 +102,7 @@ private struct BarView: View { } } - private var moreButton: some View { + private var actionButton: some View { Menu { Button("Copy as HSB") { appState.colorPanel.color.hsbColorString.copyToPasteboard() @@ -114,10 +115,11 @@ private struct BarView: View { .keyboardShortcut(",") } } label: { - Label("More", systemImage: "ellipsis.circle.fill") + 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) @@ -314,7 +316,7 @@ struct ColorPickerScreen: View { .padding(9) // 244 makes `HSL` always fit in the text field. .frame(minWidth: 244, maxWidth: .infinity) - .onAppear { + .task { updateColorsFromPanel() } .onChange(of: uppercaseHexColor) { _ in diff --git a/Color Picker/Constants.swift b/Color Picker/Constants.swift index 59aedff..291cbff 100644 --- a/Color Picker/Constants.swift +++ b/Color Picker/Constants.swift @@ -134,41 +134,3 @@ enum MenuBarItemClickAction: String, CaseIterable, Defaults.Serializable { } } } - - -// Workaround for Swift bug. - -extension Defaults.Serializable where Self: Codable { - public static var bridge: Defaults.TopLevelCodableBridge { Defaults.TopLevelCodableBridge() } -} - -extension Defaults.Serializable where Self: Codable & NSSecureCoding { - public static var bridge: Defaults.CodableNSSecureCodingBridge { Defaults.CodableNSSecureCodingBridge() } -} - -extension Defaults.Serializable where Self: Codable & NSSecureCoding & Defaults.PreferNSSecureCoding { - public static var bridge: Defaults.NSSecureCodingBridge { Defaults.NSSecureCodingBridge() } -} - -extension Defaults.Serializable where Self: Codable & RawRepresentable { - public static var bridge: Defaults.RawRepresentableCodableBridge { Defaults.RawRepresentableCodableBridge() } -} - -extension Defaults.Serializable where Self: Codable & RawRepresentable & Defaults.PreferRawRepresentable { - public static var bridge: Defaults.RawRepresentableBridge { Defaults.RawRepresentableBridge() } -} - -extension Defaults.Serializable where Self: RawRepresentable { - public static var bridge: Defaults.RawRepresentableBridge { Defaults.RawRepresentableBridge() } -} -extension Defaults.Serializable where Self: NSSecureCoding { - public static var bridge: Defaults.NSSecureCodingBridge { Defaults.NSSecureCodingBridge() } -} - -extension Defaults.CollectionSerializable where Element: Defaults.Serializable { - public static var bridge: Defaults.CollectionBridge { Defaults.CollectionBridge() } -} - -extension Defaults.SetAlgebraSerializable where Element: Defaults.Serializable & Hashable { - public static var bridge: Defaults.SetAlgebraBridge { Defaults.SetAlgebraBridge() } -} diff --git a/Color Picker/Info.plist b/Color Picker/Info.plist index 229da23..e7a6482 100644 --- a/Color Picker/Info.plist +++ b/Color Picker/Info.plist @@ -21,7 +21,7 @@ ITSAppUsesNonExemptEncryption LSApplicationCategoryType - public.app-category.utilities + public.app-category.developer-tools LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement diff --git a/Color Picker/Intents.intentdefinition b/Color Picker/Intents.intentdefinition index f14f5f5..f5b81d3 100644 --- a/Color Picker/Intents.intentdefinition +++ b/Color Picker/Intents.intentdefinition @@ -9,11 +9,11 @@ INIntentDefinitionNamespace cTK6qm INIntentDefinitionSystemVersion - 21A559 + 22A400 INIntentDefinitionToolsBuildVersion - 13A1030d + 14B47b INIntentDefinitionToolsVersion - 13.1 + 14.1 INIntents @@ -154,7 +154,7 @@ INIntentTitle - Random Color + Get Random Color INIntentTitleID 3RA5pB INIntentType diff --git a/Color Picker/SettingsScreen.swift b/Color Picker/SettingsScreen.swift index a3ef756..f31cb4f 100644 --- a/Color Picker/SettingsScreen.swift +++ b/Color Picker/SettingsScreen.swift @@ -3,71 +3,20 @@ import Defaults import LaunchAtLogin import KeyboardShortcuts -private struct HideMenuBarIconSetting: View { - @State private var isAlertPresented = false - - var body: some View { - Defaults.Toggle("Hide menu bar icon", key: .hideMenuBarIcon) - .onChange { - isAlertPresented = $0 - } - .help("This can be useful if you only use this app with the global keyboard shortcuts.") - .alert2( - "If you need to access the menu bar icon, launch the app to reveal it for 5 seconds.", - isPresented: $isAlertPresented - ) - } -} - -private struct MenuBarItemClickActionSetting: View { - @Default(.menuBarItemClickAction) private var menuBarItemClickAction - - var body: some View { - VStack { - EnumPicker(enumBinding: $menuBarItemClickAction) { element, _ in - Text(element.title) - } label: { - Text("When clicking menu bar icon:") - .respectDisabled() - .fixedSize() - } - .fixedSize() - Text(menuBarItemClickAction.tip) - .offset(x: 2) - .settingSubtitleTextStyle() - .frame(maxWidth: .infinity, alignment: .trailing) - } - } -} - -private struct PreferredColorFormatSetting: View { - @Default(.preferredColorFormat) private var preferredColorFormat - - var body: some View { - EnumPicker(enumBinding: $preferredColorFormat) { element, _ in - Text(element.title) - } label: { - Text("Preferred color format:") - .fixedSize() - } - .fixedSize() - } -} - -private struct ShownColorFormatsSetting: View { +struct SettingsScreen: View { var body: some View { - HStack(alignment: .firstTextBaseline) { - Text("Shown color formats:") - // TODO: Use a dropdown when SwiftUI supports multiple selections in `Picker`. - Defaults.MultiCheckboxPicker( - key: .shownColorFormats, - data: ColorFormat.allCases - ) { - Text($0.title) - } + TabView { + GeneralSettings() + .settingsTabItem(.general) + ColorSettings() + .settingsTabItem("Color", systemImage: "drop.fill") + ShortcutsSettings() + .settingsTabItem(.shortcuts) + AdvancedSettings() + .settingsTabItem(.advanced) } - .accessibilityElement(children: .combine) - .help("Choose which color formats to show in the color picker window. Disabled formats will still show up in the “Color” menu.") + .frame(width: 420) + .windowLevel(.floating + 1) // Ensure it's always above the color picker. } } @@ -76,25 +25,28 @@ private struct GeneralSettings: View { var body: some View { VStack(alignment: .leading) { - 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("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 { - LaunchAtLogin.Toggle() - .help(showInMenuBar ? "" : "There is really no point in launching the app at login if it is not in the menu bar. You can instead just put it in the Dock and launch it when needed.") - HideMenuBarIconSetting() - MenuBarItemClickActionSetting() - } - .disabled(!showInMenuBar) - .padding(.leading, 19) - Button("Feedback & Support") { - SSApp.openSendFeedbackPage() + Section { + 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("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 { + LaunchAtLogin.Toggle() + .help(showInMenuBar ? "" : "There is really no point in launching the app at login if it is not in the menu bar. You can instead just put it in the Dock and launch it when needed.") + HideMenuBarIconSetting() + MenuBarItemClickActionSetting() + } + .disabled(!showInMenuBar) + .padding(.leading, 20) + } footer: { + Button("Feedback & Support") { + SSApp.openSendFeedbackPage() + } + .buttonStyle(.link) + .padding(.top) + .offset(y: 20) } - .buttonStyle(.link) - .padding(.top) - .offset(y: 20) } .padding() .padding() @@ -114,7 +66,7 @@ private struct ColorSettings: View { .padding() .padding(.horizontal) Divider() - VStack(alignment: .leading) { + Section { PreferredColorFormatSetting() } .padding() @@ -125,9 +77,8 @@ private struct ColorSettings: View { } .padding() .padding(.horizontal) - .offset(x: 10) Divider() - HStack { + Section { Link("What is LCH color?", destination: "https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/") .controlSize(.small) .padding(.top) @@ -181,20 +132,69 @@ private struct AdvancedSettings: View { } } -struct SettingsScreen: View { +private struct HideMenuBarIconSetting: View { + @State private var isAlertPresented = false + var body: some View { - TabView { - GeneralSettings() - .settingsTabItem(.general) - ColorSettings() - .settingsTabItem("Color", systemImage: "drop.fill") - ShortcutsSettings() - .settingsTabItem(.shortcuts) - AdvancedSettings() - .settingsTabItem(.advanced) + Defaults.Toggle("Hide menu bar icon", key: .hideMenuBarIcon) + .onChange { + isAlertPresented = $0 + } + .help("This can be useful if you only use this app with the global keyboard shortcuts.") + .alert2( + "If you need to access the menu bar icon, launch the app to reveal it for 5 seconds.", + isPresented: $isAlertPresented + ) + } +} + +private struct MenuBarItemClickActionSetting: View { + @Default(.menuBarItemClickAction) private var menuBarItemClickAction + + var body: some View { + VStack { + EnumPicker(enumBinding: $menuBarItemClickAction) { element, _ in + Text(element.title) + } label: { + Text("When clicking menu bar icon:") + .respectDisabled() + .fixedSize() + } + .fixedSize() + Text(menuBarItemClickAction.tip) + .offset(x: 2) + .settingSubtitleTextStyle() + .frame(maxWidth: .infinity, alignment: .trailing) } - .frame(width: 420) - .windowLevel(.floating + 1) // Ensure it's always above the color picker. + } +} + +private struct PreferredColorFormatSetting: View { + @Default(.preferredColorFormat) private var preferredColorFormat + + var body: some View { + EnumPicker(enumBinding: $preferredColorFormat) { element, _ in + Text(element.title) + } label: { + Text("Preferred color format:") + .fixedSize() + } + .fixedSize() + } +} + +private struct ShownColorFormatsSetting: View { + var body: some View { + Section("Shown color formats:") { + // TODO: Use a dropdown when SwiftUI supports multiple selections in `Picker`. + Defaults.MultiCheckboxPicker( + key: .shownColorFormats, + data: ColorFormat.allCases + ) { + Text($0.title) + } + } + .help("Choose which color formats to show in the color picker window. Disabled formats will still show up in the “Color” menu.") } } diff --git a/Color Picker/Utilities.swift b/Color Picker/Utilities.swift index 4276cb6..9f4a905 100644 --- a/Color Picker/Utilities.swift +++ b/Color Picker/Utilities.swift @@ -169,7 +169,7 @@ enum SSApp { "metadata": metadata ] - URL("https://sindresorhus.com/feedback/").addingDictionaryAsQuery(query).open() + URL("https://sindresorhus.com/feedback").addingDictionaryAsQuery(query).open() } static var isDockIconVisible: Bool { @@ -406,7 +406,7 @@ final class LocalEventMonitor: ObservableObject { @discardableResult func start() -> Self { monitor = NSEvent.addLocalMonitorForEvents(matching: events) { [weak self] in - guard let self = self else { + guard let self else { return $0 } @@ -418,7 +418,7 @@ final class LocalEventMonitor: ObservableObject { } func stop() { - guard let monitor = monitor else { + guard let monitor else { return } @@ -447,7 +447,7 @@ final class GlobalEventMonitor { } func stop() { - guard let monitor = monitor else { + guard let monitor else { return } @@ -469,7 +469,7 @@ extension NSView { } func constrainEdgesToSuperview() { - guard let superview = superview else { + guard let superview else { assertionFailure("There is no superview for this view") return } @@ -1043,7 +1043,7 @@ struct NativeTextField: NSViewRepresentable { // This is required so that it correctly loses focus when the user clicks in the menu bar or uses the dropper from a keyboard shortcut. globalEventMonitor = GlobalEventMonitor(events: [.leftMouseDown, .rightMouseDown]) { [weak self] _ in - guard let self = self else { + guard let self else { return } @@ -1052,7 +1052,7 @@ struct NativeTextField: NSViewRepresentable { // Cannot be `.leftMouseUp` as the color wheel swallows it. localEventMonitor = LocalEventMonitor(events: [.leftMouseDown, .rightMouseDown, .keyDown]) { [weak self] event in - guard let self = self else { + guard let self else { return nil } @@ -1144,7 +1144,7 @@ struct NativeTextField: NSViewRepresentable { nsView.stringValue = text nsView.placeholderString = placeholder - if let font = font { + if let font { nsView.font = font } @@ -1233,13 +1233,13 @@ extension NSAlert { self.messageText = title self.alertStyle = style - if let message = message { + if let message { self.informativeText = message } addButtons(withTitles: buttonTitles) - if let defaultButtonIndex = defaultButtonIndex { + if let defaultButtonIndex { self.defaultButtonIndex = defaultButtonIndex } } @@ -1249,7 +1249,7 @@ extension NSAlert { */ @discardableResult func runModal(for window: NSWindow? = nil) -> NSApplication.ModalResponse { - guard let window = window else { + guard let window else { return runModal() } @@ -1319,12 +1319,12 @@ extension ControlActionClosureProtocol { return trampoline.action } set { - guard let action = newValue else { + guard let newValue else { objc_setAssociatedObject(self, &controlActionClosureProtocolAssociatedObjectKey, nil, .OBJC_ASSOCIATION_RETAIN) return } - let trampoline = ActionTrampoline(action: action) + let trampoline = ActionTrampoline(action: newValue) target = trampoline self.action = #selector(ActionTrampoline.handleAction) objc_setAssociatedObject(self, &controlActionClosureProtocolAssociatedObjectKey, trampoline, .OBJC_ASSOCIATION_RETAIN) @@ -1374,7 +1374,7 @@ final class CallbackMenuItem: NSMenuItem { self.isEnabled = isEnabled self.isHidden = isHidden - if let keyModifiers = keyModifiers { + if let keyModifiers { self.keyEquivalentModifierMask = keyModifiers } } @@ -1574,7 +1574,7 @@ extension View { /** Bind the native backing-window of a SwiftUI window to a property. */ - func bindNativeWindow(_ window: Binding) -> some View { + func bindHostingWindow(_ window: Binding) -> some View { background(WindowAccessor(window)) } } @@ -1588,7 +1588,7 @@ private struct WindowViewModifier: ViewModifier { onWindow(window) return content - .bindNativeWindow($window) + .bindHostingWindow($window) } } @@ -1596,7 +1596,7 @@ extension View { /** Access the native backing-window of a SwiftUI window. */ - func accessNativeWindow(_ onWindow: @escaping (NSWindow?) -> Void) -> some View { + func accessHostingWindow(_ onWindow: @escaping (NSWindow?) -> Void) -> some View { modifier(WindowViewModifier(onWindow: onWindow)) } @@ -1604,7 +1604,7 @@ extension View { Set the window level of a SwiftUI window. */ func windowLevel(_ level: NSWindow.Level) -> some View { - accessNativeWindow { + accessHostingWindow { $0?.level = level } } @@ -1773,7 +1773,7 @@ extension NSPasteboard { if onlyWhileAppIsActive { SSPublishers.appIsActive .sink { [weak self] isActive in - guard let self = self else { + guard let self else { return } @@ -1946,11 +1946,11 @@ struct EnumPicker: View where Enum: CaseIterable & Equatab } extension EnumPicker where Label == Text { - init( - _ title: S, + init( + _ title: some StringProtocol, enumBinding: Binding, @ViewBuilder content: @escaping (Enum, Bool) -> Content - ) where S: StringProtocol { + ) { self.enumBinding = enumBinding self.content = content self.label = { Text(title) } @@ -2483,7 +2483,7 @@ extension Defaults { } ``` */ - struct MultiCheckboxPicker>>: View where Data.Element: Hashable & Identifiable { + struct MultiCheckboxPicker: View where Data.Element: Hashable & Identifiable & Defaults.Serializable { typealias Element = Data.Element typealias Selection = Set @@ -2493,7 +2493,7 @@ extension Defaults { private var elementLabel: (Element) -> ElementLabel init( - key: Key, + key: Defaults.Key>, data: Data, @ViewBuilder elementLabel: @escaping (Element) -> ElementLabel ) { @@ -2542,7 +2542,7 @@ extension NSImage { Self(size: size, flipped: false) { bounds in NSGraphicsContext.current?.imageInterpolation = .high - guard let cornerRadius = cornerRadius else { + guard let cornerRadius else { color.drawSwatch(in: bounds) return true } @@ -2563,7 +2563,7 @@ extension NSImage { if borderWidth > 0, - let borderColor = borderColor + let borderColor { borderColor.setStroke() bezierPath.lineWidth = borderWidth @@ -2699,24 +2699,6 @@ extension NSImage { #endif -// TODO: Remove when targeting macOS 12. -extension View { - func overlay2( - alignment: Alignment = .center, - @ViewBuilder content: () -> Overlay - ) -> some View { - overlay(ZStack(content: content), alignment: alignment) - } - - func background2( - alignment: Alignment = .center, - @ViewBuilder content: () -> V - ) -> some View { - background(ZStack(content: content), alignment: alignment) - } -} - - extension Shape where Self == Rectangle { static var rectangle: Self { .init() } } @@ -2752,12 +2734,12 @@ extension View { /** This allows multiple alerts on a single view, which `.alert()` doesn't. */ - func alert2( + func alert2( _ title: Text, isPresented: Binding, - @ViewBuilder actions: () -> A, - @ViewBuilder message: () -> M - ) -> some View where A: View, M: View { + @ViewBuilder actions: () -> some View, + @ViewBuilder message: () -> some View + ) -> some View { background( EmptyView() .alert( @@ -2772,12 +2754,12 @@ extension View { /** This allows multiple alerts on a single view, which `.alert()` doesn't. */ - func alert2( + func alert2( _ title: String, isPresented: Binding, - @ViewBuilder actions: () -> A, - @ViewBuilder message: () -> M - ) -> some View where A: View, M: View { + @ViewBuilder actions: () -> some View, + @ViewBuilder message: () -> some View + ) -> some View { alert2( Text(title), isPresented: isPresented, @@ -2789,19 +2771,19 @@ extension View { /** This allows multiple alerts on a single view, which `.alert()` doesn't. */ - func alert2( + func alert2( _ title: Text, message: String? = nil, isPresented: Binding, - @ViewBuilder actions: () -> A - ) -> some View where A: View { + @ViewBuilder actions: () -> some View + ) -> some View { // swiftlint:disable:next trailing_closure alert2( title, isPresented: isPresented, actions: actions, message: { - if let message = message { + if let message { Text(message) } } @@ -2812,19 +2794,19 @@ extension View { /** This allows multiple alerts on a single view, which `.alert()` doesn't. */ - func alert2( + func alert2( _ title: String, message: String? = nil, isPresented: Binding, - @ViewBuilder actions: () -> A - ) -> some View where A: View { + @ViewBuilder actions: () -> some View + ) -> some View { // swiftlint:disable:next trailing_closure alert2( title, isPresented: isPresented, actions: actions, message: { - if let message = message { + if let message { Text(message) } } @@ -2868,7 +2850,7 @@ extension View { } -extension Task where Success == Never, Failure == Never { +extension Task { public static func sleep(seconds: TimeInterval) async throws { try await sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) } @@ -2945,7 +2927,7 @@ extension NSError { var userInfo = userInfo userInfo[NSLocalizedDescriptionKey] = description - if let recoverySuggestion = recoverySuggestion { + if let recoverySuggestion { userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion } @@ -2958,7 +2940,7 @@ extension NSError { } -extension Button where Label == SwiftUI.Label { +extension Button> { init( _ title: String, systemImage: String, diff --git a/IntentsExtension/Info.plist b/IntentsExtension/Info.plist index da77be9..200ad9f 100644 --- a/IntentsExtension/Info.plist +++ b/IntentsExtension/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + NSExtension NSExtensionAttributes @@ -21,7 +23,5 @@ NSExtensionPrincipalClass $(PRODUCT_MODULE_NAME).IntentHandler - ITSAppUsesNonExemptEncryption -