Iced apps can respond to dynamic OS theme changes using dark-light cross-platform crate and Iced Subscription, for example:
impl App {
fn update(&mut self, message: Message) {
match message {
AppMsg::ThemeSelected(theme) => {
self.theme = theme;
}
}
}
fn subscription(&self) -> Subscription<Message> {
Subscription::run(|| {
let stream = futures::executor::block_on(dark_light::subscribe())
.map(|s| s.boxed())
.unwrap_or(futures::stream::empty().boxed());
stream
.map(|color_mode| match color_mode {
dark_light::Mode::Default | dark_light::Mode::Light => Theme::Light,
dark_light::Mode::Dark => Theme::Dark,
})
.map(|theme| Message::ThemeChanged(theme))
})
}
fn theme(&self) -> Theme {
self.theme.clone()
}
}
However, unlike the iced-drawn UI, the winit-drawn UI (titlebar, border, etc.) do not respond to dynamic OS color theme changes. Notably, the capability is present in winit, because it gets drawn with correct light/dark color at app launch, but stays stuck afterwards. This makes sense, I guess, as our setup does not hook into winit at all.
I noticed that winit provides Window::set_theme API, which doesn’t seem to be exposed by iced. If it could be exposed, then thanks to dark-light crate, we could trigger the winit CSD (client-side decoration) theme change from iced Application.