Skip to content

Commit f8e295b

Browse files
committed
WinUIBackend: Impl system theme handling and control theming (e.g. btns)
Apps now react to user's changing their Windows system theme from light to dark and vice versa.
1 parent d75dc4e commit f8e295b

File tree

1 file changed

+84
-5
lines changed

1 file changed

+84
-5
lines changed

Sources/WinUIBackend/WinUIBackend.swift

+84-5
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public final class WinUIBackend: AppBackend {
5050
var textFieldChangeActions: [ObjectIdentifier: (String) -> Void] = [:]
5151
var textFieldSubmitActions: [ObjectIdentifier: () -> Void] = [:]
5252
var dispatcherQueue: WinAppSDK.DispatcherQueue?
53+
var themeChangeAction: (() -> Void)?
5354
}
5455

5556
private var internalState: InternalState
@@ -71,6 +72,11 @@ public final class WinUIBackend: AppBackend {
7172
_ = application.resources.insert("ToggleSwitchPreContentMargin", 0.0 as Double)
7273
_ = application.resources.insert("ToggleSwitchPostContentMargin", 0.0 as Double)
7374

75+
// Handle theme changes
76+
UWP.UISettings().colorValuesChanged.addHandler { _, _ in
77+
self.internalState.themeChangeAction?()
78+
}
79+
7480
// TODO: Read in previously hardcoded values from the application's
7581
// resources dictionary for future-proofing. Example code for getting
7682
// property values;
@@ -185,6 +191,9 @@ public final class WinUIBackend: AppBackend {
185191
public func setChild(ofWindow window: Window, to widget: Widget) {
186192
window.content = widget
187193
try! widget.updateLayout()
194+
widget.actualThemeChanged.addHandler { _, _ in
195+
self.internalState.themeChangeAction?()
196+
}
188197
}
189198

190199
public func show(window: Window) {
@@ -196,7 +205,7 @@ public final class WinUIBackend: AppBackend {
196205
}
197206

198207
public func openExternalURL(_ url: URL) throws {
199-
UWP.Launcher.launchUriAsync(WindowsFoundation.Uri(url.absoluteString))
208+
_ = UWP.Launcher.launchUriAsync(WindowsFoundation.Uri(url.absoluteString))
200209
}
201210

202211
public func runInMainThread(action: @escaping () -> Void) {
@@ -218,14 +227,22 @@ public final class WinUIBackend: AppBackend {
218227
public func computeRootEnvironment(
219228
defaultEnvironment: EnvironmentValues
220229
) -> EnvironmentValues {
230+
// Source: https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/ui/apply-windows-themes#know-when-dark-mode-is-enabled
231+
let backgroundColor = try! UWP.UISettings().getColorValue(.background)
232+
233+
let green = Int(backgroundColor.g)
234+
let red = Int(backgroundColor.r)
235+
let blue = Int(backgroundColor.b)
236+
let isLight = 5 * green + 2 * red + blue > 8 * 128
237+
221238
return
222239
defaultEnvironment
223240
.with(\.font, .system(size: 14))
241+
.with(\.colorScheme, isLight ? .light : .dark)
224242
}
225243

226244
public func setRootEnvironmentChangeHandler(to action: @escaping () -> Void) {
227-
print("Implement set root environment change handler")
228-
// TODO
245+
internalState.themeChangeAction = action
229246
}
230247

231248
public func setIncomingURLHandler(to action: @escaping (URL) -> Void) {
@@ -494,6 +511,13 @@ public final class WinUIBackend: AppBackend {
494511
button.content = block
495512
environment.apply(to: block)
496513
internalState.buttonClickActions[ObjectIdentifier(button)] = action
514+
515+
switch environment.colorScheme {
516+
case .light:
517+
button.requestedTheme = .light
518+
case .dark:
519+
button.requestedTheme = .dark
520+
}
497521
}
498522

499523
public func createScrollContainer(for child: Widget) -> Widget {
@@ -544,6 +568,14 @@ public final class WinUIBackend: AppBackend {
544568
slider.minimum = minimum
545569
slider.maximum = maximum
546570
internalState.sliderChangeActions[ObjectIdentifier(slider)] = onChange
571+
572+
// TODO: Add environment to updateSlider API
573+
// switch environment.colorScheme {
574+
// case .light:
575+
// slider.requestedTheme = .light
576+
// case .dark:
577+
// slider.requestedTheme = .dark
578+
// }
547579
}
548580

549581
public func setValue(ofSlider slider: Widget, to value: Double) {
@@ -585,6 +617,13 @@ public final class WinUIBackend: AppBackend {
585617
environment.apply(to: picker)
586618
picker.actualForegroundColor = environment.suggestedForegroundColor.uwpColor
587619

620+
switch environment.colorScheme {
621+
case .light:
622+
picker.requestedTheme = .light
623+
case .dark:
624+
picker.requestedTheme = .dark
625+
}
626+
588627
// Only update options past this point, otherwise the early return
589628
// will cause issues.
590629
guard options.count > 0 else {
@@ -622,7 +661,7 @@ public final class WinUIBackend: AppBackend {
622661
}
623662

624663
missing("proper picker updating logic")
625-
missing("picker environment handling")
664+
missing("picker font handling")
626665

627666
picker.options = options
628667
}
@@ -664,7 +703,14 @@ public final class WinUIBackend: AppBackend {
664703
internalState.textFieldChangeActions[ObjectIdentifier(textField)] = onChange
665704
internalState.textFieldSubmitActions[ObjectIdentifier(textField)] = onSubmit
666705

667-
missing("text field environment handling")
706+
switch environment.colorScheme {
707+
case .light:
708+
textField.requestedTheme = .light
709+
case .dark:
710+
textField.requestedTheme = .dark
711+
}
712+
713+
missing("text field font handling")
668714
}
669715

670716
public func setContent(ofTextField textField: Widget, to content: String) {
@@ -760,6 +806,15 @@ public final class WinUIBackend: AppBackend {
760806
block.text = label
761807
toggle.content = block
762808
internalState.toggleClickActions[ObjectIdentifier(toggle)] = onChange
809+
810+
// TODO: Add environment to updateToggle API. Rename updateToggle etc to
811+
// updateToggleButton etc
812+
// switch environment.colorScheme {
813+
// case .light:
814+
// toggle.requestedTheme = .light
815+
// case .dark:
816+
// toggle.requestedTheme = .dark
817+
// }
763818
}
764819

765820
public func setState(ofToggle toggle: Widget, to state: Bool) {
@@ -782,6 +837,14 @@ public final class WinUIBackend: AppBackend {
782837

783838
public func updateSwitch(_ toggleSwitch: Widget, onChange: @escaping (Bool) -> Void) {
784839
internalState.switchClickActions[ObjectIdentifier(toggleSwitch)] = onChange
840+
841+
// TODO: Add environment to updateSwitch API
842+
// switch environment.colorScheme {
843+
// case .light:
844+
// toggleSwitch.requestedTheme = .light
845+
// case .dark:
846+
// toggleSwitch.requestedTheme = .dark
847+
// }
785848
}
786849

787850
public func setState(ofSwitch switchWidget: Widget, to state: Bool) {
@@ -811,6 +874,13 @@ public final class WinUIBackend: AppBackend {
811874
if actionLabels.count >= 3 {
812875
alert.closeButtonText = actionLabels[2]
813876
}
877+
878+
switch environment.colorScheme {
879+
case .light:
880+
alert.requestedTheme = .light
881+
case .dark:
882+
alert.requestedTheme = .dark
883+
}
814884
}
815885

816886
public func showAlert(
@@ -935,6 +1005,15 @@ extension SwiftCrossUI.Color {
9351005
b: UInt8((blue * Float(UInt8.max)).rounded())
9361006
)
9371007
}
1008+
1009+
init(uwpColor: UWP.Color) {
1010+
self.init(
1011+
Float(uwpColor.r) / Float(UInt8.max),
1012+
Float(uwpColor.g) / Float(UInt8.max),
1013+
Float(uwpColor.b) / Float(UInt8.max),
1014+
Float(uwpColor.a) / Float(UInt8.max)
1015+
)
1016+
}
9381017
}
9391018

9401019
extension EnvironmentValues {

0 commit comments

Comments
 (0)