Skip to content

Commit 5296c14

Browse files
tronicaluAtomicBoolean
authored andcommitted
iOS: Add support for dark/light theme detection
This is here until rust-windowing/winit#4570 lands in a winit release we can use. ChangeLog: iOS: Added detection of system dark/light theme
1 parent b4e8d18 commit 5296c14

4 files changed

Lines changed: 102 additions & 1 deletion

File tree

internal/backends/winit/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ i-slint-renderer-skia = { workspace = true, features = ["default"] }
130130
[target.'cfg(target_os = "ios")'.dependencies]
131131
objc2 = { workspace = true }
132132
objc2-foundation = { version = "0.3.2", default-features = false, features = ["std", "block2", "NSString", "NSNotification", "NSOperation", "NSGeometry", "objc2-core-foundation"] }
133-
objc2-ui-kit = { version = "0.3.2", default-features = false, features = ["UIScreen", "UIWindow", "UIView", "UIViewAnimating", "UIViewPropertyAnimator", "UIResponder", "objc2-core-foundation", "objc2-quartz-core", "block2"] }
133+
objc2-ui-kit = { version = "0.3.2", default-features = false, features = ["UIScreen", "UIWindow", "UIView", "UIViewAnimating", "UIViewPropertyAnimator", "UIResponder", "UIInterface", "UITrait", "UITraitCollection", "objc2-core-foundation", "objc2-quartz-core", "block2"] }
134134
objc2-quartz-core = { version = "0.3.2", default-features = false, features = ["CADisplayLink", "CATransaction"] }
135135
# Match version in objc2
136136
block2 = "0.6.2"

internal/backends/winit/ios.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// Copyright © SixtyFPS GmbH <info@slint.dev>
22
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
33

4+
mod color_scheme;
45
mod keyboard_animator;
56
mod virtual_keyboard;
67

8+
pub(crate) use color_scheme::{
9+
ColorSchemeObserver, current_color_scheme, install_color_scheme_observer,
10+
};
711
pub(crate) use keyboard_animator::KeyboardCurveSampler;
812
pub(crate) use virtual_keyboard::{KeyboardNotifications, register_keyboard_notifications};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright © SixtyFPS GmbH <info@slint.dev>
2+
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3+
4+
// Stopgap until winit ships iOS appearance support (rust-windowing/winit#4570).
5+
// Once that lands, `winit_window.theme()` and `WindowEvent::ThemeChanged` will
6+
// work on iOS and this whole module — plus the `ios_color_scheme_observer`
7+
// wiring in winitwindowadapter.rs — can be deleted.
8+
9+
use std::ptr::NonNull;
10+
use std::rc::Weak;
11+
12+
use block2::RcBlock;
13+
use objc2::{
14+
ClassType, Message, available, msg_send,
15+
rc::Retained,
16+
runtime::{AnyClass, ProtocolObject},
17+
};
18+
use objc2_foundation::NSArray;
19+
use objc2_ui_kit::{
20+
UITraitChangeObservable, UITraitChangeRegistration, UITraitCollection, UITraitEnvironment,
21+
UITraitUserInterfaceStyle, UIUserInterfaceStyle, UIView,
22+
};
23+
24+
use i_slint_core::items::ColorScheme;
25+
26+
use crate::winitwindowadapter::WinitWindowAdapter;
27+
28+
fn style_to_color_scheme(style: UIUserInterfaceStyle) -> ColorScheme {
29+
match style {
30+
UIUserInterfaceStyle::Dark => ColorScheme::Dark,
31+
UIUserInterfaceStyle::Light => ColorScheme::Light,
32+
_ => ColorScheme::Unknown,
33+
}
34+
}
35+
36+
pub(crate) fn current_color_scheme(view: &UIView) -> ColorScheme {
37+
style_to_color_scheme(unsafe { view.traitCollection().userInterfaceStyle() })
38+
}
39+
40+
pub(crate) struct ColorSchemeObserver {
41+
view: Retained<UIView>,
42+
registration: Retained<ProtocolObject<dyn UITraitChangeRegistration>>,
43+
}
44+
45+
impl Drop for ColorSchemeObserver {
46+
fn drop(&mut self) {
47+
self.view.unregisterForTraitChanges(&self.registration);
48+
}
49+
}
50+
51+
pub(crate) fn install_color_scheme_observer(
52+
view: &UIView,
53+
adapter: Weak<WinitWindowAdapter>,
54+
) -> Option<ColorSchemeObserver> {
55+
// `registerForTraitChanges:withHandler:` is iOS 17+. Older iOS still gets the
56+
// initial scheme via `current_color_scheme`, but no live updates.
57+
if !available!(ios = 17.0) {
58+
return None;
59+
}
60+
61+
let handler = RcBlock::new(
62+
move |env: NonNull<ProtocolObject<dyn UITraitEnvironment>>,
63+
_prev: NonNull<UITraitCollection>| {
64+
let Some(adapter) = adapter.upgrade() else { return };
65+
let env = unsafe { env.as_ref() };
66+
let scheme =
67+
style_to_color_scheme(unsafe { env.traitCollection().userInterfaceStyle() });
68+
adapter.set_color_scheme(scheme);
69+
},
70+
);
71+
72+
let traits: Retained<NSArray<AnyClass>> =
73+
NSArray::from_slice(&[UITraitUserInterfaceStyle::class()]);
74+
75+
let registration: Retained<ProtocolObject<dyn UITraitChangeRegistration>> = unsafe {
76+
msg_send![
77+
view,
78+
registerForTraitChanges: &*traits,
79+
withHandler: &*handler,
80+
]
81+
};
82+
83+
Some(ColorSchemeObserver { view: view.retain(), registration })
84+
}

internal/backends/winit/winitwindowadapter.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ enum WinitWindowOrNone {
173173
context_menu_muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
174174
#[cfg(target_os = "ios")]
175175
keyboard_curve_sampler: super::ios::KeyboardCurveSampler,
176+
#[cfg(target_os = "ios")]
177+
_color_scheme_observer: Option<super::ios::ColorSchemeObserver>,
176178
},
177179
None(RefCell<WindowAttributes>),
178180
}
@@ -516,6 +518,12 @@ impl WinitWindowAdapter {
516518
(view, self.self_weak.clone())
517519
};
518520

521+
// winit doesn't surface iOS appearance, so query the view's trait
522+
// collection directly; the matching live observer is installed below as
523+
// part of the `HasWindow` variant so its lifetime is tied to the window.
524+
#[cfg(target_os = "ios")]
525+
self.set_color_scheme(crate::ios::current_color_scheme(&content_view));
526+
519527
let frame_throttle = crate::frame_throttle::create_frame_throttle(
520528
self.self_weak.clone(),
521529
&winit_window,
@@ -552,6 +560,11 @@ impl WinitWindowAdapter {
552560
}
553561
},
554562
),
563+
#[cfg(target_os = "ios")]
564+
_color_scheme_observer: crate::ios::install_color_scheme_observer(
565+
&content_view,
566+
self.self_weak.clone(),
567+
),
555568
};
556569

557570
#[cfg(muda)]

0 commit comments

Comments
 (0)