diff --git a/.swiftlint.yml b/.swiftlint.yml index 9b0fb55..b631fd3 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -46,6 +46,7 @@ only_rules: - explicit_init - fallthrough - fatal_error_message + - final_test_case - first_where - flatmap_over_map_reduce - for_where @@ -72,6 +73,8 @@ only_rules: - lower_acl_than_parent - mark - modifier_order + - multiline_arguments + - multiline_arguments_brackets - multiline_function_chains - multiline_literal_brackets - multiline_parameters @@ -80,12 +83,12 @@ only_rules: - no_extension_access_modifier - no_fallthrough_only - no_space_in_method_call + - non_optional_string_data_conversion - non_overridable_class_declaration - notification_center_detachment - ns_number_init_as_function_reference - nsobject_prefer_isequal - number_separator - - opening_brace - operator_usage_whitespace - operator_whitespace - overridden_super_call @@ -119,6 +122,7 @@ only_rules: - sorted_first_last - statement_position - static_operator + - static_over_final_class - strong_iboutlet - superfluous_disable_command - superfluous_else @@ -146,6 +150,7 @@ 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 @@ -155,9 +160,10 @@ only_rules: - yoda_condition analyzer_rules: - capture_variable + - typesafe_array_init + - unneeded_synthesized_initializer - unused_declaration - unused_import - - typesafe_array_init for_where: allow_for_as_filter: true number_separator: @@ -182,8 +188,12 @@ identifier_name: - 'y1' - 'y2' - 'z2' +redundant_type_annotation: + consider_default_literal_types_redundant: true +unneeded_override: + affect_initializers: true deployment_target: - macOS_deployment_target: '13' + macOS_deployment_target: '14' custom_rules: no_nsrect: regex: '\bNSRect\b' @@ -206,7 +216,7 @@ custom_rules: message: 'Use Double instead of CGFloat' swiftui_state_private: regex: '@(ObservedObject|EnvironmentObject)\s+var' - message: 'SwiftUI @ObservedObject/@EnvironmentObject properties should be private' + message: 'SwiftUI @ObservedObject and @EnvironmentObject properties should be private' swiftui_environment_private: regex: '@Environment\(\\\.\w+\)\s+var' message: 'SwiftUI @Environment properties should be private' diff --git a/Color Picker/Utilities.swift b/Color Picker/Utilities.swift index 9aa62cf..c621ddd 100644 --- a/Color Picker/Utilities.swift +++ b/Color Picker/Utilities.swift @@ -327,6 +327,10 @@ enum Device { sysctlbyname("hw.model", &model, &size, nil, 0) return String(cString: model) }() + + static var uptimeIncludingSleep: Duration { + .nanoseconds(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX)) + } } @@ -359,6 +363,38 @@ extension Dictionary where Key: ExpressibleByStringLiteral, Value: ExpressibleBy } +extension Duration { + enum ConversionUnit: Double { + case days = 86_400_000_000_000 + case hours = 3_600_000_000_000 + case minutes = 60_000_000_000 + case seconds = 1_000_000_000 + case milliseconds = 1_000_000 + case microseconds = 1000 + } + + /** + Nanoseconds representation. + */ + var nanoseconds: Int64 { + let (seconds, attoseconds) = components + let secondsNanos = seconds * 1_000_000_000 + let attosecondsNanons = attoseconds / 1_000_000_000 + let (totalNanos, isOverflow) = secondsNanos.addingReportingOverflow(attosecondsNanons) + return isOverflow ? .max : totalNanos + } + + func `in`(_ unit: ConversionUnit) -> Double { + Double(nanoseconds) / unit.rawValue + } +} + + +extension Duration { + var toTimeInterval: TimeInterval { self.in(.seconds) } +} + + extension URLComponents { mutating func addDictionaryAsQuery(_ dict: [String: String]) { percentEncodedQuery = dict.asQueryString @@ -1393,6 +1429,26 @@ extension View { } +extension NSEvent { + /** + Creates a noop mouse event that can be used as a fallback when you cannot get a real mouse event. + */ + static func noopMouseEvent(_ type: EventType) -> NSEvent { + mouseEvent( + with: type, + location: .zero, + modifierFlags: modifierFlags, + timestamp: Device.uptimeIncludingSleep.toTimeInterval, + windowNumber: 0, + context: nil, + eventNumber: 0, + clickCount: 1, + pressure: 1 + )! + } +} + + private var controlActionClosureProtocolAssociatedObjectKey: UInt8 = 0 protocol ControlActionClosureProtocol: NSObjectProtocol { @@ -1409,7 +1465,7 @@ private final class ActionTrampoline: NSObject { @objc fileprivate func handleAction(_ sender: AnyObject) { - action(NSApp.currentEvent!) + action(NSApp?.currentEvent ?? .noopMouseEvent(.leftMouseDown)) } } @@ -2306,7 +2362,7 @@ struct EnumPicker: View where Enum: CaseIterable & Equatab @ViewBuilder let label: () -> Label var body: some View { - Picker(selection: selection.caseIndex) { // swiftlint:disable:this multiline_arguments + Picker(selection: selection.caseIndex) { ForEach(Array(Enum.allCases).indexed(), id: \.0) { index, element in content(element) .tag(index) @@ -2864,6 +2920,7 @@ Store a value persistently in a `View` like with `@State`, but without updating You can use it for storing both value and reference types. */ +@MainActor @propertyWrapper struct ViewStorage: DynamicProperty { private final class ValueBox: ObservableObject { @@ -3228,12 +3285,11 @@ extension View { isPresented: Binding, @ViewBuilder actions: () -> some View ) -> some View { - // swiftlint:disable:next trailing_closure alert2( title, isPresented: isPresented, actions: actions, - message: { + message: { // swiftlint:disable:this trailing_closure if let message { Text(message) } @@ -3251,12 +3307,11 @@ extension View { isPresented: Binding, @ViewBuilder actions: () -> some View ) -> some View { - // swiftlint:disable:next trailing_closure alert2( title, isPresented: isPresented, actions: actions, - message: { + message: { // swiftlint:disable:this trailing_closure if let message { Text(message) } @@ -3272,12 +3327,11 @@ extension View { message: String? = nil, isPresented: Binding ) -> some View { - // swiftlint:disable:next trailing_closure alert2( title, message: message, isPresented: isPresented, - actions: {} + actions: {} // swiftlint:disable:this trailing_closure ) } @@ -3290,12 +3344,11 @@ extension View { message: String? = nil, isPresented: Binding ) -> some View { - // swiftlint:disable:next trailing_closure alert2( title, message: message, isPresented: isPresented, - actions: {} + actions: {} // swiftlint:disable:this trailing_closure ) } } @@ -3470,8 +3523,8 @@ extension NSColorList { allKeys.compactMap { color(withKey: $0)?.toResolvedColor } } - var keysAndColors: [NSColorList.Name: Color.Resolved] { - .init(zip(allKeys, colors)) { first, _ in first } + var keysAndColors: [(key: NSColorList.Name, color: Color.Resolved)] { + Array(zip(allKeys, colors)) } } diff --git a/readme.md b/readme.md index f62e759..3593c79 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ Requires macOS 14 or later. A special version for users that cannot access the App Store. It won't receive automatic updates. I will update it here once a year. -[Download](https://www.dropbox.com/scl/fi/v5g3wgxwipx7x05dkc4oh/Color-Picker-2.0.0-1706349638.zip?rlkey=zk983dubrc2t0gh9pc2bimoaa&raw=1) *(2.0.0 · macOS 14+)* +[Download](https://www.dropbox.com/scl/fi/reztjse2ei8xsegdb3iic/Color-Picker-2.0.2-1718996226.zip?rlkey=1bi5zf6r1jb2wcrheve8tddya&raw=1) *(2.0.2 · macOS 14+)* ## Features