Skip to content

Commit 5c89d3e

Browse files
committed
Create colorScheme modifier to allow users to override system color scheme
Tells backends to update the default styles of various controls (e.g. buttons, text fields) and the default foreground color (when no foreground color is explicitly specified).
1 parent 1da01c3 commit 5c89d3e

File tree

9 files changed

+108
-18
lines changed

9 files changed

+108
-18
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,12 @@ public final class AppKitBackend: AppBackend {
105105

106106
public func computeRootEnvironment(defaultEnvironment: Environment) -> Environment {
107107
let isDark = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark"
108-
let textColor: Color = isDark ? .white : .black
109108
let font = Font.system(
110109
size: Int(NSFont.systemFont(ofSize: 0.0).pointSize.rounded(.awayFromZero))
111110
)
112111
return
113112
defaultEnvironment
114-
.with(\.foregroundColor, textColor)
113+
.with(\.colorScheme, isDark ? .dark : .light)
115114
.with(\.font, font)
116115
}
117116

@@ -320,7 +319,12 @@ public final class AppKitBackend: AppBackend {
320319
}
321320

322321
public func createTextView() -> Widget {
323-
return NSTextField(wrappingLabelWithString: "")
322+
let field = NSTextField(wrappingLabelWithString: "")
323+
// Somewhat unintuitively, this changes the behaviour of the text field even
324+
// though it's not editable. It prevents the text from resetting to default
325+
// styles when clicked (yeah that happens...)
326+
field.allowsEditingTextAttributes = true
327+
return field
324328
}
325329

326330
public func updateTextView(_ textView: Widget, content: String, environment: Environment) {
@@ -341,6 +345,7 @@ public final class AppKitBackend: AppBackend {
341345
let button = button as! NSButton
342346
button.attributedTitle = Self.attributedString(for: label, in: environment)
343347
button.bezelStyle = .regularSquare
348+
button.appearance = environment.colorScheme.nsAppearance
344349
button.onAction = { _ in
345350
action()
346351
}
@@ -455,6 +460,7 @@ public final class AppKitBackend: AppBackend {
455460
) {
456461
let textField = textField as! NSObservableTextField
457462
textField.placeholderString = placeholder
463+
textField.appearance = environment.colorScheme.nsAppearance
458464
textField.onEdit = { textField in
459465
onChange(textField.stringValue)
460466
}
@@ -678,7 +684,7 @@ public final class AppKitBackend: AppBackend {
678684
.right
679685
}
680686
return [
681-
.foregroundColor: environment.foregroundColor.nsColor,
687+
.foregroundColor: environment.suggestedForegroundColor.nsColor,
682688
.font: font(for: environment),
683689
.paragraphStyle: paragraphStyle,
684690
]
@@ -777,6 +783,17 @@ class NSCustomTableViewDelegate: NSObject, NSTableViewDelegate, NSTableViewDataS
777783
}
778784
}
779785

786+
extension ColorScheme {
787+
var nsAppearance: NSAppearance? {
788+
switch self {
789+
case .light:
790+
return NSAppearance(named: .aqua)
791+
case .dark:
792+
return NSAppearance(named: .darkAqua)
793+
}
794+
}
795+
}
796+
780797
extension Color {
781798
init(_ nsColor: NSColor) {
782799
guard let resolvedNSColor = nsColor.usingColorSpace(.deviceRGB) else {

Sources/Gtk/Utility/CSS/CSSProperty.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public struct CSSProperty: Equatable {
1919
}
2020

2121
public static func backgroundColor(_ color: Color) -> CSSProperty {
22-
CSSProperty(key: "background-color", value: rgba(color))
22+
CSSProperty(key: "background", value: rgba(color))
2323
}
2424

2525
public static func lineLimit(_ limit: Int) -> CSSProperty {
@@ -70,7 +70,7 @@ public struct CSSProperty: Equatable {
7070
let red = color.red * 255
7171
let green = color.green * 255
7272
let blue = color.blue * 255
73-
let alpha = color.alpha * 255
73+
let alpha = color.alpha
7474
return "rgba(\(red),\(green),\(blue),\(alpha))"
7575
}
7676
}

Sources/Gtk3/Datatypes/Color.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ public struct Color: Equatable {
1818
self.alpha = alpha
1919
}
2020

21+
public static func eightBit(
22+
_ red: UInt8,
23+
_ green: UInt8,
24+
_ blue: UInt8,
25+
_ alpha: UInt8 = 255
26+
) -> Color {
27+
Color(
28+
Double(red) / 255,
29+
Double(green) / 255,
30+
Double(blue) / 255,
31+
Double(alpha) / 255
32+
)
33+
}
34+
2135
public var gdkColor: GdkRGBA {
2236
return GdkRGBA(red: red, green: green, blue: blue, alpha: alpha)
2337
}

Sources/Gtk3/Utility/CSS/CSSProperty.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public struct CSSProperty: Equatable {
1919
}
2020

2121
public static func backgroundColor(_ color: Color) -> CSSProperty {
22-
CSSProperty(key: "background-color", value: rgba(color))
22+
CSSProperty(key: "background", value: rgba(color))
2323
}
2424

2525
public static func lineLimit(_ limit: Int) -> CSSProperty {
@@ -70,7 +70,7 @@ public struct CSSProperty: Equatable {
7070
let red = color.red * 255
7171
let green = color.green * 255
7272
let blue = color.blue * 255
73-
let alpha = color.alpha * 255
73+
let alpha = color.alpha
7474
return "rgba(\(red),\(green),\(blue),\(alpha))"
7575
}
7676
}

Sources/Gtk3Backend/Gtk3Backend.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,9 @@ public final class Gtk3Backend: AppBackend {
424424
button.label = label
425425
button.clicked = { _ in action() }
426426
button.css.clear()
427-
button.css.set(properties: Self.cssProperties(for: environment))
427+
button.css.set(
428+
properties: Self.cssProperties(for: environment, isControl: true)
429+
)
428430
}
429431

430432
public func createToggle() -> Widget {
@@ -499,7 +501,7 @@ public final class Gtk3Backend: AppBackend {
499501
}
500502

501503
textField.css.clear()
502-
textField.css.set(properties: Self.cssProperties(for: environment))
504+
textField.css.set(properties: Self.cssProperties(for: environment, isControl: true))
503505
}
504506

505507
public func setContent(ofTextField textField: Widget, to content: String) {
@@ -582,9 +584,12 @@ public final class Gtk3Backend: AppBackend {
582584

583585
// MARK: Helpers
584586

585-
private static func cssProperties(for environment: Environment) -> [CSSProperty] {
587+
private static func cssProperties(
588+
for environment: Environment,
589+
isControl: Bool = false
590+
) -> [CSSProperty] {
586591
var properties: [CSSProperty] = []
587-
properties.append(.foregroundColor(environment.foregroundColor.gtkColor))
592+
properties.append(.foregroundColor(environment.suggestedForegroundColor.gtkColor))
588593
switch environment.font {
589594
case .system(let size, let weight, let design):
590595
properties.append(.fontSize(size))
@@ -617,6 +622,19 @@ public final class Gtk3Backend: AppBackend {
617622
break
618623
}
619624
}
625+
626+
if isControl {
627+
switch environment.colorScheme {
628+
case .light:
629+
properties.append(.border(color: Color.eightBit(209, 209, 209), width: 1))
630+
properties.append(.backgroundColor(Color(1, 1, 1, 1)))
631+
case .dark:
632+
properties.append(.border(color: Color.eightBit(32, 32, 32), width: 1))
633+
properties.append(.backgroundColor(Color(1, 1, 1, 0.1)))
634+
}
635+
properties.append(.init(key: "box-shadow", value: "none"))
636+
}
637+
620638
return properties
621639
}
622640
}

Sources/GtkBackend/GtkBackend.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ public final class GtkBackend: AppBackend {
439439
button.label = label
440440
button.clicked = { _ in action() }
441441
button.css.clear()
442-
button.css.set(properties: Self.cssProperties(for: environment))
442+
button.css.set(properties: Self.cssProperties(for: environment, isControl: true))
443443
}
444444

445445
public func createToggle() -> Widget {
@@ -514,7 +514,7 @@ public final class GtkBackend: AppBackend {
514514
}
515515

516516
textField.css.clear()
517-
textField.css.set(properties: Self.cssProperties(for: environment))
517+
textField.css.set(properties: Self.cssProperties(for: environment, isControl: true))
518518
}
519519

520520
public func setContent(ofTextField textField: Widget, to content: String) {
@@ -603,9 +603,12 @@ public final class GtkBackend: AppBackend {
603603
return container
604604
}
605605

606-
private static func cssProperties(for environment: Environment) -> [CSSProperty] {
606+
private static func cssProperties(
607+
for environment: Environment,
608+
isControl: Bool = false
609+
) -> [CSSProperty] {
607610
var properties: [CSSProperty] = []
608-
properties.append(.foregroundColor(environment.foregroundColor.gtkColor))
611+
properties.append(.foregroundColor(environment.suggestedForegroundColor.gtkColor))
609612
switch environment.font {
610613
case .system(let size, let weight, let design):
611614
properties.append(.fontSize(size))
@@ -638,6 +641,13 @@ public final class GtkBackend: AppBackend {
638641
break
639642
}
640643
}
644+
645+
if isControl {
646+
properties.append(.backgroundColor(Color(1, 1, 1, 0.1)))
647+
properties.append(CSSProperty(key: "border", value: "none"))
648+
properties.append(CSSProperty(key: "box-shadow", value: "none"))
649+
}
650+
641651
return properties
642652
}
643653
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
extension View {
2+
public func colorScheme(_ colorScheme: ColorScheme) -> some View {
3+
EnvironmentModifier(self) { environment in
4+
environment.with(\.colorScheme, colorScheme)
5+
}
6+
}
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public enum ColorScheme {
2+
case light
3+
case dark
4+
5+
public var defaultForegroundColor: Color {
6+
switch self {
7+
case .light: .black
8+
case .dark: .white
9+
}
10+
}
11+
}

Sources/SwiftCrossUI/ViewGraph/Environment.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,31 @@ public struct Environment {
33
public var layoutOrientation: Orientation
44
public var layoutAlignment: StackAlignment
55
public var layoutSpacing: Int
6-
public var foregroundColor: Color
76
public var font: Font
87
public var multilineTextAlignment: HorizontalAlignment
98

9+
/// The current color scheme of the current view scope.
10+
public var colorScheme: ColorScheme
11+
/// The foreground color. `nil` means that the default foreground color of
12+
/// the current color scheme should be used.
13+
public var foregroundColor: Color?
14+
15+
/// The suggested foreground color for backends to use. Backends don't
16+
/// neccessarily have to obey this when ``Environment/foregroundColor``
17+
/// is `nil`.
18+
public var suggestedForegroundColor: Color {
19+
foregroundColor ?? colorScheme.defaultForegroundColor
20+
}
21+
1022
init() {
1123
onResize = { _ in }
1224
layoutOrientation = .vertical
1325
layoutAlignment = .center
1426
layoutSpacing = 10
15-
foregroundColor = .black
27+
foregroundColor = nil
1628
font = .system(size: 12)
1729
multilineTextAlignment = .leading
30+
colorScheme = .light
1831
}
1932

2033
public func with<T>(_ keyPath: WritableKeyPath<Self, T>, _ newValue: T) -> Self {

0 commit comments

Comments
 (0)