Skip to content

Commit 76704b2

Browse files
committed
Allow backends to modify the root-level environment (e.g. to handle system dark mode etc)
1 parent c032c6e commit 76704b2

File tree

5 files changed

+50
-16
lines changed

5 files changed

+50
-16
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ public struct AppKitBackend: AppBackend {
119119
}
120120
}
121121

122+
public func computeRootEnvironment(defaultEnvironment: Environment) -> Environment {
123+
defaultEnvironment.with(\.foregroundColor, Color(NSColor.textColor))
124+
}
125+
122126
public func show(widget: Widget) {}
123127

124128
class NSContainerView: NSView {
@@ -273,13 +277,7 @@ public struct AppKitBackend: AppBackend {
273277
public func updateTextView(_ textView: Widget, content: String, environment: Environment) {
274278
let field = textView.view as! NSTextField
275279
field.stringValue = content
276-
let textColor = environment.foregroundColor
277-
field.textColor = NSColor(
278-
calibratedRed: CGFloat(textColor.red),
279-
green: CGFloat(textColor.green),
280-
blue: CGFloat(textColor.blue),
281-
alpha: 1
282-
)
280+
field.textColor = environment.foregroundColor.nsColor
283281
}
284282

285283
public func createButton() -> Widget {
@@ -526,6 +524,31 @@ public struct AppKitBackend: AppBackend {
526524
}
527525
}
528526

527+
extension Color {
528+
init(_ nsColor: NSColor) {
529+
guard let resolvedNSColor = nsColor.usingColorSpace(.deviceRGB) else {
530+
print("error: Failed to convert NSColor to RGB")
531+
self = .black
532+
return
533+
}
534+
self.init(
535+
Float(resolvedNSColor.redComponent),
536+
Float(resolvedNSColor.greenComponent),
537+
Float(resolvedNSColor.blueComponent),
538+
Float(resolvedNSColor.alphaComponent)
539+
)
540+
}
541+
542+
var nsColor: NSColor {
543+
NSColor(
544+
calibratedRed: CGFloat(red),
545+
green: CGFloat(green),
546+
blue: CGFloat(blue),
547+
alpha: 1
548+
)
549+
}
550+
}
551+
529552
// Source: https://gist.github.com/sindresorhus/3580ce9426fff8fafb1677341fca4815
530553
enum AssociationPolicy {
531554
case assign

Sources/SwiftCrossUI/Backend/AppBackend.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public protocol AppBackend {
5252
/// after starting the app, and hence this generic root window creation
5353
/// API must reflect that. This is always the first method to be called
5454
/// and is where boilerplate app setup should happen.
55-
55+
///
5656
/// Runs the backend's main run loop. The app will exit when this method
5757
/// returns. This wall always be the first method called by SwiftCrossUI.
5858
///
@@ -116,6 +116,10 @@ public protocol AppBackend {
116116
/// compatible with dispatching UI updates.
117117
func runInMainThread(action: @escaping () -> Void)
118118

119+
/// Computes the root environment for an app (e.g. by checking the system's current
120+
/// theme). May fall back on the provided defaults where reasonable.
121+
func computeRootEnvironment(defaultEnvironment: Environment) -> Environment
122+
119123
/// Shows a widget after it has been created or updated (may be unnecessary
120124
/// for some backends). Predominantly used by ``ViewGraphNode`` after
121125
/// propagating updates.

Sources/SwiftCrossUI/Modifiers/ForegroundColor.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ extension View {
22
/// Sets the color of the foreground elements displayed by this view.
33
public func foregroundColor(_ color: Color) -> some View {
44
return EnvironmentModifier(self) { environment in
5-
print("Updating fg color")
65
return environment.with(\.foregroundColor, color)
76
}
87
}

Sources/SwiftCrossUI/ViewGraph/Environment.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public struct Environment {
1313
foregroundColor = .black
1414
}
1515

16-
func with<T>(_ keyPath: WritableKeyPath<Self, T>, _ newValue: T) -> Self {
16+
public func with<T>(_ keyPath: WritableKeyPath<Self, T>, _ newValue: T) -> Self {
1717
var environment = self
1818
environment[keyPath: keyPath] = newValue
1919
return environment

Sources/SwiftCrossUI/_App.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ class _App<AppRoot: App> {
1313
/// A cancellable handle to observation of the app's state .
1414
var cancellable: Cancellable?
1515
/// The root level environment.
16-
var environment: Environment
16+
var defaultEnvironment: Environment
1717

1818
/// Wraps a user's app implementation.
1919
init(_ app: AppRoot) {
2020
backend = app.backend
2121
self.app = app
22-
self.environment = Environment()
22+
self.defaultEnvironment = Environment()
2323
}
2424

2525
func forceRefresh() {
2626
self.sceneGraphRoot?.update(
2727
self.app.body,
2828
backend: self.backend,
29-
environment: environment
29+
environment: defaultEnvironment
3030
)
3131
}
3232

@@ -37,17 +37,25 @@ class _App<AppRoot: App> {
3737
let rootNode = AppRoot.Body.Node(
3838
from: self.app.body,
3939
backend: self.backend,
40-
environment: self.environment
40+
environment: self.defaultEnvironment
4141
)
4242

43-
rootNode.update(nil, backend: self.backend, environment: self.environment)
43+
rootNode.update(
44+
nil,
45+
backend: self.backend,
46+
environment: self.backend.computeRootEnvironment(
47+
defaultEnvironment: self.defaultEnvironment
48+
)
49+
)
4450
self.sceneGraphRoot = rootNode
4551

4652
self.cancellable = self.app.state.didChange.observe {
4753
self.sceneGraphRoot?.update(
4854
self.app.body,
4955
backend: self.backend,
50-
environment: self.environment
56+
environment: self.backend.computeRootEnvironment(
57+
defaultEnvironment: self.defaultEnvironment
58+
)
5159
)
5260
}
5361
}

0 commit comments

Comments
 (0)