From d1073dcecb4590a9764435f5e5bf01e3e7c5fd91 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 17 Feb 2020 20:25:27 +0100 Subject: [PATCH] Implement ThemeChanged for web target. (#1462) * Implement ThemeChanged for web target. * Add TODO upstream to stdweb. Co-authored-by: Ryan G --- CHANGELOG.md | 1 + Cargo.toml | 2 ++ FEATURES.md | 3 ++ src/platform/web.rs | 6 ++++ .../web/event_loop/window_target.rs | 15 +++++++++- src/platform_impl/web/stdweb/canvas.rs | 17 +++++++++++ src/platform_impl/web/stdweb/mod.rs | 10 +++++++ src/platform_impl/web/web_sys/canvas.rs | 29 ++++++++++++++++++- src/platform_impl/web/web_sys/mod.rs | 11 +++++++ 9 files changed, 92 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9baa58..92924ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Wayland, fix color from `close_button_icon_color` not applying. - Ignore locale if unsupported by X11 backend - On Wayland, Add HiDPI cursor support +- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change. - Fix `Event::to_static` returning `None` for user events. # 0.21.0 (2020-02-04) diff --git a/Cargo.toml b/Cargo.toml index b537dca4..f0ac5fdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,8 @@ features = [ 'HtmlCanvasElement', 'HtmlElement', 'KeyboardEvent', + 'MediaQueryList', + 'MediaQueryListEvent', 'MouseEvent', 'Node', 'PointerEvent', diff --git a/FEATURES.md b/FEATURES.md index 3d439588..0918d358 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -149,6 +149,9 @@ If your PR makes notable changes to Winit's features, please update this section * Getting the device idiom * Getting the preferred video mode +### Web +* Get if systems preferred color scheme is "dark" + ## Usability * `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial) diff --git a/src/platform/web.rs b/src/platform/web.rs index 6083f5cb..c5ceca90 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -14,6 +14,9 @@ use stdweb::web::html_element::CanvasElement; #[cfg(feature = "stdweb")] pub trait WindowExtStdweb { fn canvas(&self) -> CanvasElement; + + /// Whether the browser reports the preferred color scheme to be "dark". + fn is_dark_mode(&self) -> bool; } #[cfg(feature = "web-sys")] @@ -22,6 +25,9 @@ use web_sys::HtmlCanvasElement; #[cfg(feature = "web-sys")] pub trait WindowExtWebSys { fn canvas(&self) -> HtmlCanvasElement; + + /// Whether the browser reports the preferred color scheme to be "dark". + fn is_dark_mode(&self) -> bool; } #[cfg(feature = "stdweb")] diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 2395f346..db485a16 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -2,7 +2,7 @@ use super::{backend, device, proxy::Proxy, runner, window}; use crate::dpi::{PhysicalSize, Size}; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; -use crate::window::WindowId; +use crate::window::{Theme, WindowId}; use std::clone::Clone; pub struct WindowTarget { @@ -199,5 +199,18 @@ impl WindowTarget { }); runner.request_redraw(WindowId(id)); }); + + let runner = self.runner.clone(); + canvas.on_dark_mode(move |is_dark_mode| { + let theme = if is_dark_mode { + Theme::Dark + } else { + Theme::Light + }; + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::ThemeChanged(theme), + }); + }); } } diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index a3581917..f6ed4866 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -6,6 +6,7 @@ use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use std::cell::RefCell; use std::rc::Rc; +use stdweb::js; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ @@ -240,6 +241,22 @@ impl Canvas { self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler())); } + pub fn on_dark_mode(&mut self, handler: F) + where + F: 'static + FnMut(bool), + { + // TODO: upstream to stdweb + js! { + var handler = @{handler}; + + if (window.matchMedia) { + window.matchMedia("(prefers-color-scheme: dark)").addListener(function(e) { + handler(event.matches) + }); + } + } + } + fn add_event(&self, mut handler: F) -> EventListenerHandle where E: ConcreteEvent, diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 9274ab99..a95dfc7d 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -10,6 +10,7 @@ use crate::platform::web::WindowExtStdweb; use crate::window::Window; use stdweb::js; +use stdweb::unstable::TryInto; use stdweb::web::event::BeforeUnloadEvent; use stdweb::web::window; use stdweb::web::IEventTarget; @@ -31,6 +32,15 @@ impl WindowExtStdweb for Window { fn canvas(&self) -> CanvasElement { self.window.canvas().raw().clone() } + + fn is_dark_mode(&self) -> bool { + // TODO: upstream to stdweb + let is_dark_mode = js! { + return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) + }; + + is_dark_mode.try_into().expect("should return a bool") + } } pub fn window_size() -> LogicalSize { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 970f87da..a2755b95 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -8,7 +8,10 @@ use std::cell::RefCell; use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; +use web_sys::{ + Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, PointerEvent, + WheelEvent, +}; pub struct Canvas { /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. @@ -26,6 +29,7 @@ pub struct Canvas { on_mouse_wheel: Option>, on_fullscreen_change: Option>, wants_fullscreen: Rc>, + on_dark_mode: Option>, } impl Drop for Canvas { @@ -77,6 +81,7 @@ impl Canvas { on_mouse_wheel: None, on_fullscreen_change: None, wants_fullscreen: Rc::new(RefCell::new(false)), + on_dark_mode: None, }) } @@ -251,6 +256,28 @@ impl Canvas { Some(self.add_event("fullscreenchange", move |_: Event| handler())); } + pub fn on_dark_mode(&mut self, mut handler: F) + where + F: 'static + FnMut(bool), + { + let window = web_sys::window().expect("Failed to obtain window"); + + self.on_dark_mode = window + .match_media("(prefers-color-scheme: dark)") + .ok() + .flatten() + .and_then(|media| { + let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| { + handler(event.matches()) + }) as Box); + + media + .add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref())) + .map(|_| closure) + .ok() + }); + } + fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index cafbae1f..c41cd069 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -38,6 +38,17 @@ impl WindowExtWebSys for Window { fn canvas(&self) -> HtmlCanvasElement { self.window.canvas().raw().clone() } + + fn is_dark_mode(&self) -> bool { + let window = web_sys::window().expect("Failed to obtain window"); + + window + .match_media("(prefers-color-scheme: dark)") + .ok() + .flatten() + .map(|media| media.matches()) + .unwrap_or(false) + } } pub fn window_size() -> LogicalSize {