Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(
_ transform: (Element) -> T
Expand All @@ -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<count, Element>,
Expand All @@ -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 {
Expand All @@ -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<let N: Int, Element>: RandomAccessCollection {
public typealias Index = Int
public let base: InlineArray<N, Element>
Expand Down
20 changes: 14 additions & 6 deletions Sources/QizhKit/Extensions/Color+/Color+values.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 24 additions & 1 deletion Sources/QizhKit/UI/Debug/LabeledValueView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -525,16 +534,24 @@ 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 {
shape.inset(by: LinePosition.inner.inset(for: pixelLength))
.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 {
Expand All @@ -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) {
Expand Down
60 changes: 30 additions & 30 deletions Sources/QizhKit/UI/Debug/ValueView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -147,7 +147,7 @@ public enum ValueView: View, Sendable {
}
}

public var attributedString: AttributedString {
public nonisolated var attributedString: AttributedString {
switch self {
case .undefined,
.string,
Expand All @@ -160,88 +160,88 @@ 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))
.baselineOffset(1)
}
}

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)
}
}
Expand Down
Loading