Skip to content
Merged
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
2 changes: 1 addition & 1 deletion internal/backends/winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ i-slint-renderer-skia = { workspace = true, features = ["default"] }
[target.'cfg(target_os = "ios")'.dependencies]
objc2 = { workspace = true }
objc2-foundation = { version = "0.3.2", default-features = false, features = ["std", "block2", "NSString", "NSNotification", "NSOperation", "NSGeometry", "objc2-core-foundation"] }
objc2-ui-kit = { version = "0.3.2", default-features = false, features = ["UIScreen", "UIWindow", "UIView", "UIViewAnimating", "UIViewPropertyAnimator", "UIResponder", "objc2-core-foundation", "objc2-quartz-core", "block2"] }
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"] }
objc2-quartz-core = { version = "0.3.2", default-features = false, features = ["CADisplayLink", "CATransaction"] }
# Match version in objc2
block2 = "0.6.2"
Expand Down
4 changes: 4 additions & 0 deletions internal/backends/winit/ios.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

mod color_scheme;
mod keyboard_animator;
mod virtual_keyboard;

pub(crate) use color_scheme::{
ColorSchemeObserver, current_color_scheme, install_color_scheme_observer,
};
pub(crate) use keyboard_animator::KeyboardCurveSampler;
pub(crate) use virtual_keyboard::{KeyboardNotifications, register_keyboard_notifications};
84 changes: 84 additions & 0 deletions internal/backends/winit/ios/color_scheme.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

// Stopgap until winit ships iOS appearance support (rust-windowing/winit#4570).
// Once that lands, `winit_window.theme()` and `WindowEvent::ThemeChanged` will
// work on iOS and this whole module — plus the `ios_color_scheme_observer`
// wiring in winitwindowadapter.rs — can be deleted.

use std::ptr::NonNull;
use std::rc::Weak;

use block2::RcBlock;
use objc2::{
ClassType, Message, available, msg_send,
rc::Retained,
runtime::{AnyClass, ProtocolObject},
};
use objc2_foundation::NSArray;
use objc2_ui_kit::{
UITraitChangeObservable, UITraitChangeRegistration, UITraitCollection, UITraitEnvironment,
UITraitUserInterfaceStyle, UIUserInterfaceStyle, UIView,
};

use i_slint_core::items::ColorScheme;

use crate::winitwindowadapter::WinitWindowAdapter;

fn style_to_color_scheme(style: UIUserInterfaceStyle) -> ColorScheme {
match style {
UIUserInterfaceStyle::Dark => ColorScheme::Dark,
UIUserInterfaceStyle::Light => ColorScheme::Light,
_ => ColorScheme::Unknown,
}
}

pub(crate) fn current_color_scheme(view: &UIView) -> ColorScheme {
style_to_color_scheme(unsafe { view.traitCollection().userInterfaceStyle() })
}

pub(crate) struct ColorSchemeObserver {
view: Retained<UIView>,
registration: Retained<ProtocolObject<dyn UITraitChangeRegistration>>,
}

impl Drop for ColorSchemeObserver {
fn drop(&mut self) {
self.view.unregisterForTraitChanges(&self.registration);
}
}

pub(crate) fn install_color_scheme_observer(
view: &UIView,
adapter: Weak<WinitWindowAdapter>,
) -> Option<ColorSchemeObserver> {
// `registerForTraitChanges:withHandler:` is iOS 17+. Older iOS still gets the
// initial scheme via `current_color_scheme`, but no live updates.
if !available!(ios = 17.0) {
return None;
}

let handler = RcBlock::new(
move |env: NonNull<ProtocolObject<dyn UITraitEnvironment>>,
_prev: NonNull<UITraitCollection>| {
let Some(adapter) = adapter.upgrade() else { return };
let env = unsafe { env.as_ref() };
let scheme =
style_to_color_scheme(unsafe { env.traitCollection().userInterfaceStyle() });
adapter.set_color_scheme(scheme);
},
);

let traits: Retained<NSArray<AnyClass>> =
NSArray::from_slice(&[UITraitUserInterfaceStyle::class()]);

let registration: Retained<ProtocolObject<dyn UITraitChangeRegistration>> = unsafe {
msg_send![
view,
registerForTraitChanges: &*traits,
withHandler: &*handler,
]
};

Some(ColorSchemeObserver { view: view.retain(), registration })
}
13 changes: 13 additions & 0 deletions internal/backends/winit/winitwindowadapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ enum WinitWindowOrNone {
context_menu_muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
#[cfg(target_os = "ios")]
keyboard_curve_sampler: super::ios::KeyboardCurveSampler,
#[cfg(target_os = "ios")]
_color_scheme_observer: Option<super::ios::ColorSchemeObserver>,
},
None(RefCell<WindowAttributes>),
}
Expand Down Expand Up @@ -516,6 +518,12 @@ impl WinitWindowAdapter {
(view, self.self_weak.clone())
};

// winit doesn't surface iOS appearance, so query the view's trait
// collection directly; the matching live observer is installed below as
// part of the `HasWindow` variant so its lifetime is tied to the window.
#[cfg(target_os = "ios")]
self.set_color_scheme(crate::ios::current_color_scheme(&content_view));

let frame_throttle = crate::frame_throttle::create_frame_throttle(
self.self_weak.clone(),
&winit_window,
Expand Down Expand Up @@ -552,6 +560,11 @@ impl WinitWindowAdapter {
}
},
),
#[cfg(target_os = "ios")]
_color_scheme_observer: crate::ios::install_color_scheme_observer(
&content_view,
self.self_weak.clone(),
),
};

#[cfg(muda)]
Expand Down
Loading