diff --git a/Sources/QizhKit/Extensions/AttributedString+/AttributedString+String.swift b/Sources/QizhKit/Extensions/AttributedString+/AttributedString+String.swift index 1b6ebb2..6b8b9e2 100644 --- a/Sources/QizhKit/Extensions/AttributedString+/AttributedString+String.swift +++ b/Sources/QizhKit/Extensions/AttributedString+/AttributedString+String.swift @@ -6,6 +6,13 @@ // Copyright © 2025 Serhii Shevchenko. All rights reserved. // +#if os(iOS) || targetEnvironment(macCatalyst) +import UIKit +public typealias PlatformColor = UIColor +#elseif os(macOS) +import AppKit +public typealias PlatformColor = NSColor +#endif import SwiftUI extension AttributedString { @@ -16,7 +23,13 @@ extension AttributedString { } extension AttributedString { - @inlinable public func foregroundColor(_ color: UIColor) -> AttributedString { + @inlinable public func foregroundColor(_ color: PlatformColor) -> AttributedString { + transformingAttributes(\.foregroundColor) { foregroundColor in + foregroundColor.value = Color(color) + } + } + + @inlinable public func foregroundColor(_ color: Color) -> AttributedString { transformingAttributes(\.foregroundColor) { foregroundColor in foregroundColor.value = color } diff --git a/Sources/QizhKit/Extensions/Collection+/InlineArray+sugar.swift b/Sources/QizhKit/Extensions/Collection+/InlineArray+sugar.swift index ec81134..e793792 100644 --- a/Sources/QizhKit/Extensions/Collection+/InlineArray+sugar.swift +++ b/Sources/QizhKit/Extensions/Collection+/InlineArray+sugar.swift @@ -9,7 +9,7 @@ import Foundation #if swift(>=6.2) -@available(iOS 26.0, *) +@available(iOS 26.0, macOS 26.0, *) extension InlineArray { @inlinable public func map( _ transform: (Element) -> T @@ -22,7 +22,7 @@ extension InlineArray { } } -@available(iOS 26.0, *) +@available(iOS 26.0, macOS 26.0, *) extension InlineArray: @retroactive Equatable where Element: Equatable { public static func == ( lhs: InlineArray, @@ -37,7 +37,7 @@ extension InlineArray: @retroactive Equatable where Element: Equatable { } } -@available(iOS 26.0, *) +@available(iOS 26.0, macOS 26.0, *) extension InlineArray: @retroactive Hashable where Element: Hashable { public func hash(into hasher: inout Hasher) { for i in 0 ..< count { @@ -46,7 +46,7 @@ extension InlineArray: @retroactive Hashable where Element: Hashable { } } -@available(iOS 26.0, *) +@available(iOS 26.0, macOS 26.0, *) public struct InlineArrayCollection: RandomAccessCollection { public typealias Index = Int public let base: InlineArray diff --git a/Sources/QizhKit/Extensions/Color+/Color+values.swift b/Sources/QizhKit/Extensions/Color+/Color+values.swift index f061e68..9f53535 100644 --- a/Sources/QizhKit/Extensions/Color+/Color+values.swift +++ b/Sources/QizhKit/Extensions/Color+/Color+values.swift @@ -88,17 +88,25 @@ public extension Color { static let tertiaryLabel = Color(nsColor: .tertiaryLabelColor) static let quaternaryLabel = Color(nsColor: .quaternaryLabelColor) + @available(macOS 10.15, *) static let systemFill = Color(nsColor: .systemFill) + @available(macOS 10.15, *) static let secondarySystemFill = Color(nsColor: .secondarySystemFill) + @available(macOS 10.15, *) static let tertiarySystemFill = Color(nsColor: .tertiarySystemFill) + @available(macOS 10.15, *) static let quaternarySystemFill = Color(nsColor: .quaternarySystemFill) - static let link = Color(nsColor: .link) - static let placeholderText = Color(nsColor: .placeholderText) - static let separator = Color(nsColor: .separator) - static let opaqueSeparator = Color(nsColor: .opaqueSeparator) - static let lightText = Color(nsColor: .lightText) - static let darkText = Color(nsColor: .darkText) + @available(macOS 10.10, *) + static let link = Color(nsColor: .linkColor) + @available(macOS 10.10, *) + static let placeholderText = Color(nsColor: .placeholderTextColor) + @available(macOS 10.14, *) + static let separator = Color(nsColor: .separatorColor) + @available(macOS 10.14, *) + static let opaqueSeparator = Color(nsColor: .opaqueSeparatorColor) + static let lightText = Color.white + static let darkText = Color.black #endif // MARK: B&W diff --git a/Sources/QizhKit/UI/Debug/LabeledValueView.swift b/Sources/QizhKit/UI/Debug/LabeledValueView.swift index 1ee3f3b..5a5ebc1 100644 --- a/Sources/QizhKit/UI/Debug/LabeledValueView.swift +++ b/Sources/QizhKit/UI/Debug/LabeledValueView.swift @@ -6,6 +6,11 @@ // Copyright © 2020 Serhii Shevchenko. All rights reserved. // +#if os(iOS) || targetEnvironment(macCatalyst) +import UIKit +#elseif os(macOS) +import AppKit +#endif import SwiftUI // MARK: - Environment Values @@ -498,8 +503,12 @@ public struct LabeledValueView: View { .strokeBorder(.tertiary, lineWidth: pixelLength) } } + #if os(iOS) || targetEnvironment(macCatalyst) .contentShape([.contextMenuPreview, .hoverEffect, .interaction, .dragPreview], shape) .hoverEffect(.highlight) + #else + .contentShape([.interaction, .dragPreview], shape) + #endif .fixedHeight() .apply { view in ViewThatFits(in: .horizontal) { @@ -525,7 +534,11 @@ public struct LabeledValueView: View { valueView .multilineTextAlignment(.leading) .frame(minHeight: 15, alignment: .topLeading) - .background(.systemBackground, in: shape) + #if os(iOS) || targetEnvironment(macCatalyst) + .background(Color(.systemBackground), in: shape) + #elseif os(macOS) + .background(Color(NSColor.windowBackgroundColor), in: shape) + #endif .clipShape(shape) .overlay { if colorScheme.isDark { @@ -533,8 +546,12 @@ public struct LabeledValueView: View { .strokeBorder(.tertiary, lineWidth: pixelLength) } } + #if os(iOS) || targetEnvironment(macCatalyst) .contentShape([.contextMenuPreview, .hoverEffect, .interaction, .dragPreview], shape) .hoverEffect(.highlight) + #else + .contentShape([.interaction, .dragPreview], shape) + #endif .asMultilineSwitcher(isInitiallyCollapsed: not(isInitiallyMultiline)) .contextMenu { Label { @@ -543,7 +560,13 @@ public struct LabeledValueView: View { Image(systemName: "doc.on.doc") } .button { + #if os(iOS) || targetEnvironment(macCatalyst) UIPasteboard.general.string = valueView.string + #elseif os(macOS) + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(valueView.string, forType: .string) + #endif } ShareLink(item: valueView.string) { diff --git a/Sources/QizhKit/UI/Debug/ValueView.swift b/Sources/QizhKit/UI/Debug/ValueView.swift index 83ba497..22f67f3 100644 --- a/Sources/QizhKit/UI/Debug/ValueView.swift +++ b/Sources/QizhKit/UI/Debug/ValueView.swift @@ -34,7 +34,7 @@ public enum ValueView: View, Sendable { .foregroundStyle(.primary) } - public var text: Text { + public nonisolated var text: Text { switch self { case let .undefined(value): Text(value.description) @@ -91,7 +91,7 @@ public enum ValueView: View, Sendable { } } - public var string: String { + public nonisolated var string: String { switch self { case let .undefined(value): value.description @@ -147,7 +147,7 @@ public enum ValueView: View, Sendable { } } - public var attributedString: AttributedString { + public nonisolated var attributedString: AttributedString { switch self { case .undefined, .string, @@ -160,39 +160,39 @@ public enum ValueView: View, Sendable { string.asAttributedString() case let .cgPoint(value, fraction): ValueView.cgFloat(value.x, fraction: fraction).attributedString - + String.comaspace.asAttributedString().foregroundColor(.secondaryLabel) + + String.comaspace.asAttributedString().foregroundColor(.secondary) + ValueView.cgFloat(value.y, fraction: fraction).attributedString case let .cgSize(value, fraction): ValueView.cgFloat(value.width, fraction: fraction).attributedString - + multiplyString.asAttributedString().foregroundColor(.secondaryLabel) + + multiplyString.asAttributedString().foregroundColor(.secondary) + ValueView.cgFloat(value.height, fraction: fraction).attributedString case let .cgRect(value, fraction): - String.leftParenthesis.asAttributedString().foregroundColor(.secondaryLabel) + String.leftParenthesis.asAttributedString().foregroundColor(.secondary) + ValueView.cgPoint(value.origin, fraction: fraction).attributedString - + String("), (").asAttributedString().foregroundColor(.secondaryLabel) + + String("), (").asAttributedString().foregroundColor(.secondary) + ValueView.cgSize(value.size, fraction: fraction).attributedString - + String.rightParenthesis.asAttributedString().foregroundColor(.secondaryLabel) + + String.rightParenthesis.asAttributedString().foregroundColor(.secondary) case let .cgVector(value, fraction): ValueView.cgFloat(value.dx, fraction: fraction).attributedString - + String.comaspace.asAttributedString().foregroundColor(.secondaryLabel) + + String.comaspace.asAttributedString().foregroundColor(.secondary) + ValueView.cgFloat(value.dy, fraction: fraction).attributedString case let .edgeInsets(value, fraction): - String("top:").asAttributedString().foregroundColor(.secondaryLabel) + String("top:").asAttributedString().foregroundColor(.secondary) + ValueView.cgFloat(value.top, fraction: fraction).attributedString - + String("bot:").asAttributedString().foregroundColor(.secondaryLabel) + + String("bot:").asAttributedString().foregroundColor(.secondary) + ValueView.cgFloat(value.bottom, fraction: fraction).attributedString - + String("lead:").asAttributedString().foregroundColor(.secondaryLabel) + + String("lead:").asAttributedString().foregroundColor(.secondary) + ValueView.cgFloat(value.leading, fraction: fraction).attributedString - + String("trail:").asAttributedString().foregroundColor(.secondaryLabel) + + String("trail:").asAttributedString().foregroundColor(.secondary) + ValueView.cgFloat(value.trailing, fraction: fraction).attributedString } } - fileprivate var multiplyString: String { + fileprivate nonisolated var multiplyString: String { NilReplacement.x.description } - fileprivate var multiplyText: Text { + fileprivate nonisolated var multiplyText: Text { Text(Image(systemName: "multiply")) .foregroundStyle(.secondary) .font(.system(size: 6, weight: .semibold)) @@ -200,48 +200,48 @@ public enum ValueView: View, Sendable { } } -extension ValueView: @MainActor RawRepresentable { - @inlinable public init?(rawValue: String) { +extension ValueView: RawRepresentable { + @inlinable public nonisolated init?(rawValue: String) { self = .string(rawValue) } - @inlinable public var rawValue: String { + @inlinable public nonisolated var rawValue: String { string } } -extension ValueView: @MainActor LosslessStringConvertible { - public init?(_ description: String) { +extension ValueView: LosslessStringConvertible { + public nonisolated init?(_ description: String) { self = .string(description) } } -extension ValueView: @MainActor CustomStringConvertible { - public var description: String { +extension ValueView: CustomStringConvertible { + public nonisolated var description: String { string } } -extension ValueView: @MainActor ExpressibleByStringLiteral { - public init(stringLiteral value: String) { +extension ValueView: ExpressibleByStringLiteral { + public nonisolated init(stringLiteral value: String) { self = .string(value) } } -extension ValueView: @MainActor ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { +extension ValueView: ExpressibleByBooleanLiteral { + public nonisolated init(booleanLiteral value: Bool) { self = .bool(value) } } -extension ValueView: @MainActor ExpressibleByIntegerLiteral { - public init(integerLiteral value: Int) { +extension ValueView: ExpressibleByIntegerLiteral { + public nonisolated init(integerLiteral value: Int) { self = .integer(value) } } -extension ValueView: @MainActor ExpressibleByFloatLiteral { - public init(floatLiteral value: Double) { +extension ValueView: ExpressibleByFloatLiteral { + public nonisolated init(floatLiteral value: Double) { self = .floatingPoint(value) } }