From 317436c0570dde617fb408d6c9a973c5dd0a2fc4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Jun 2022 17:41:59 +0200 Subject: [PATCH] Use dark-light on Mac and Windows (#1726) * Use dark-light on Mac and Windows dark-light has a nasty problem on Linux: https://github.com/frewsxcv/rust-dark-light/issues/17 So we made dark-light opt-in in https://github.com/emilk/egui/pull/1437 This PR makes dark-light a default dependency again, but only use it on Max and Windows. This is controlled with the new NativeOptions::follow_system_theme. If this isn't enabled, then NativeOptions::default_theme is used. * Add eframe::WebOptions --- eframe/CHANGELOG.md | 3 + eframe/Cargo.toml | 4 +- eframe/src/epi.rs | 102 ++++++++++++++++++++++++++- eframe/src/lib.rs | 11 ++- eframe/src/native/epi_integration.rs | 33 ++------- eframe/src/native/run.rs | 8 +++ eframe/src/web/backend.rs | 30 +++++--- eframe/src/web/mod.rs | 15 ++-- egui_demo_app/src/lib.rs | 7 +- egui_demo_app/src/wrap_app.rs | 6 -- 10 files changed, 160 insertions(+), 59 deletions(-) diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index eef287dfc..4f40df08b 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -11,6 +11,9 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C * Add `NativeOptions::renderer` to switch between the rendering backends * Fix clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)). * Allow running on native without hardware accelerated rendering. Change with `NativeOptions::hardware_acceleration` ([#1681]([#1693](https://github.com/emilk/egui/pull/1693)). +* `dark-light` (dark mode detection) is now enabled by default on Mac and Windows ([#1726](https://github.com/emilk/egui/pull/1726)). +* Add `NativeOptions::follow_system_theme` and `NativeOptions::default_theme` ([#1726](https://github.com/emilk/egui/pull/1726)). + ## 0.18.0 - 2022-04-30 * MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index 6cf848786..f79a6b982 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -20,9 +20,11 @@ all-features = true [features] -default = ["default_fonts", "glow"] +default = ["dark-light", "default_fonts", "glow"] ## Detect dark mode system preference using [`dark-light`](https://docs.rs/dark-light). +## +## See also [`NativeOptions::follow_system_theme`] and [`NativeOptions::default_theme`]. dark-light = ["dep:dark-light"] ## If set, egui will use `include_bytes!` to bundle some fonts. diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index aecb78912..2e36c7057 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -149,6 +149,7 @@ pub trait App { } /// Selects the level of hardware graphics acceleration. +#[cfg(not(target_arch = "wasm32"))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum HardwareAcceleration { /// Require graphics acceleration. @@ -166,6 +167,7 @@ pub enum HardwareAcceleration { /// Options controlling the behavior of a native window. /// /// Only a single native window is currently supported. +#[cfg(not(target_arch = "wasm32"))] #[derive(Clone)] pub struct NativeOptions { /// Sets whether or not the window will always be on top of other windows. @@ -243,8 +245,25 @@ pub struct NativeOptions { /// What rendering backend to use. pub renderer: Renderer, + + /// If the `dark-light` feature is enabled: + /// + /// Try to detect and follow the system preferred setting for dark vs light mode. + /// + /// By default, this is `true` on Mac and Windows, but `false` on Linux + /// due to . + /// + /// See also [`Self::default_theme`]. + pub follow_system_theme: bool, + + /// Which theme to use in case [`Self::follow_system_theme`] is `false` + /// or the `dark-light` feature is disabled. + /// + /// Default: `Theme::Dark`. + pub default_theme: Theme, } +#[cfg(not(target_arch = "wasm32"))] impl Default for NativeOptions { fn default() -> Self { Self { @@ -265,6 +284,84 @@ impl Default for NativeOptions { stencil_buffer: 0, hardware_acceleration: HardwareAcceleration::Preferred, renderer: Renderer::default(), + follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"), + default_theme: Theme::Dark, + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl NativeOptions { + /// The theme used by the system. + #[cfg(feature = "dark-light")] + pub fn system_theme(&self) -> Option { + if self.follow_system_theme { + crate::profile_scope!("dark_light::detect"); + match dark_light::detect() { + dark_light::Mode::Dark => Some(Theme::Dark), + dark_light::Mode::Light => Some(Theme::Light), + } + } else { + None + } + } + + /// The theme used by the system. + #[cfg(not(feature = "dark-light"))] + pub fn system_theme(&self) -> Option { + None + } +} + +// ---------------------------------------------------------------------------- + +/// Options when using `eframe` in a web page. +#[cfg(target_arch = "wasm32")] +pub struct WebOptions { + /// Try to detect and follow the system preferred setting for dark vs light mode. + /// + /// See also [`Self::default_theme`]. + /// + /// Default: `true`. + pub follow_system_theme: bool, + + /// Which theme to use in case [`Self::follow_system_theme`] is `false` + /// or system theme detection fails. + /// + /// Default: `Theme::Dark`. + pub default_theme: Theme, +} + +#[cfg(target_arch = "wasm32")] +impl Default for WebOptions { + fn default() -> Self { + Self { + follow_system_theme: true, + default_theme: Theme::Dark, + } + } +} + +// ---------------------------------------------------------------------------- + +/// Dark or Light theme. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum Theme { + /// Dark mode: light text on a dark background. + Dark, + /// Light mode: dark text on a light background. + Light, +} + +impl Theme { + /// Get the egui visuals corresponding to this theme. + /// + /// Use with [`egui::Context::set_visuals`]. + pub fn egui_visuals(self) -> egui::Visuals { + match self { + Self::Dark => egui::Visuals::dark(), + Self::Light => egui::Visuals::light(), } } } @@ -531,9 +628,10 @@ pub struct IntegrationInfo { /// If the app is running in a Web context, this returns information about the environment. pub web_info: Option, - /// Does the system prefer dark mode (over light mode)? + /// Does the OS use dark or light mode? + /// /// `None` means "don't know". - pub prefer_dark_mode: Option, + pub system_theme: Option, /// Seconds of cpu usage (in seconds) of UI code on the previous frame. /// `None` if this is the first frame. diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index da9036e91..e96de64b3 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -99,12 +99,17 @@ pub use web_sys; /// #[cfg(target_arch = "wasm32")] /// #[wasm_bindgen] /// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { -/// eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyEguiApp::new(cc)))) +/// let web_options = eframe::WebOptions::default(); +/// eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))) /// } /// ``` #[cfg(target_arch = "wasm32")] -pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bindgen::JsValue> { - web::start(canvas_id, app_creator)?; +pub fn start_web( + canvas_id: &str, + web_options: WebOptions, + app_creator: AppCreator, +) -> Result<(), wasm_bindgen::JsValue> { + web::start(canvas_id, web_options, app_creator)?; Ok(()) } diff --git a/eframe/src/native/epi_integration.rs b/eframe/src/native/epi_integration.rs index 0365d1683..597e0c855 100644 --- a/eframe/src/native/epi_integration.rs +++ b/eframe/src/native/epi_integration.rs @@ -1,4 +1,4 @@ -use crate::{epi, WindowInfo}; +use crate::{epi, Theme, WindowInfo}; use egui_winit::{native_pixels_per_point, WindowSettings}; use winit::event_loop::EventLoopWindowTarget; @@ -47,12 +47,7 @@ pub fn window_builder( max_window_size, resizable, transparent, - vsync: _, // used in `fn create_display` - multisampling: _, // used in `fn create_display` - depth_buffer: _, // used in `fn create_display` - stencil_buffer: _, // used in `fn create_display` - hardware_acceleration: _, // used in `fn create_display` - renderer: _, // used in `fn run_native` + .. } = native_options; let window_icon = icon_data.clone().and_then(load_icon); @@ -187,6 +182,7 @@ impl EpiIntegration { event_loop: &EventLoopWindowTarget, max_texture_side: usize, window: &winit::window::Window, + system_theme: Option, storage: Option>, #[cfg(feature = "glow")] gl: Option>, #[cfg(feature = "wgpu")] render_state: Option, @@ -195,12 +191,10 @@ impl EpiIntegration { *egui_ctx.memory() = load_egui_memory(storage.as_deref()).unwrap_or_default(); - let prefer_dark_mode = prefer_dark_mode(); - let frame = epi::Frame { info: epi::IntegrationInfo { web_info: None, - prefer_dark_mode, + system_theme, cpu_usage: None, native_pixels_per_point: Some(native_pixels_per_point(window)), window_info: read_window_info(window, egui_ctx.pixels_per_point()), @@ -213,12 +207,6 @@ impl EpiIntegration { render_state, }; - if prefer_dark_mode == Some(true) { - egui_ctx.set_visuals(egui::Visuals::dark()); - } else { - egui_ctx.set_visuals(egui::Visuals::light()); - } - let mut egui_winit = egui_winit::State::new(event_loop); egui_winit.set_max_texture_side(max_texture_side); let pixels_per_point = window.scale_factor() as f32; @@ -376,16 +364,3 @@ pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option Option { - match dark_light::detect() { - dark_light::Mode::Dark => Some(true), - dark_light::Mode::Light => Some(false), - } -} - -#[cfg(not(feature = "dark-light"))] -fn prefer_dark_mode() -> Option { - None -} diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index 5fb69425b..dcde4de4a 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -66,15 +66,19 @@ pub fn run_glow( let mut painter = egui_glow::Painter::new(gl.clone(), None, "") .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); + let system_theme = native_options.system_theme(); let mut integration = epi_integration::EpiIntegration::new( &event_loop, painter.max_texture_side(), gl_window.window(), + system_theme, storage, Some(gl.clone()), #[cfg(feature = "wgpu")] None, ); + let theme = system_theme.unwrap_or(native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); { let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); @@ -248,15 +252,19 @@ pub fn run_wgpu( let render_state = painter.get_render_state().expect("Uninitialized"); + let system_theme = native_options.system_theme(); let mut integration = epi_integration::EpiIntegration::new( &event_loop, painter.max_texture_side().unwrap_or(2048), &window, + system_theme, storage, #[cfg(feature = "glow")] None, Some(render_state.clone()), ); + let theme = system_theme.unwrap_or(native_options.default_theme); + integration.egui_ctx.set_visuals(theme.egui_visuals()); { let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); diff --git a/eframe/src/web/backend.rs b/eframe/src/web/backend.rs index a636f4055..602f95af5 100644 --- a/eframe/src/web/backend.rs +++ b/eframe/src/web/backend.rs @@ -140,16 +140,24 @@ pub struct AppRunner { } impl AppRunner { - pub fn new(canvas_id: &str, app_creator: epi::AppCreator) -> Result { + pub fn new( + canvas_id: &str, + web_options: crate::WebOptions, + app_creator: epi::AppCreator, + ) -> Result { let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?; // fail early - let prefer_dark_mode = super::prefer_dark_mode(); + let system_theme = if web_options.follow_system_theme { + super::system_theme() + } else { + None + }; let info = epi::IntegrationInfo { web_info: Some(epi::WebInfo { location: web_location(), }), - prefer_dark_mode, + system_theme, cpu_usage: None, native_pixels_per_point: Some(native_pixels_per_point()), window_info: None, @@ -158,11 +166,9 @@ impl AppRunner { let egui_ctx = egui::Context::default(); load_memory(&egui_ctx); - if prefer_dark_mode == Some(true) { - egui_ctx.set_visuals(egui::Visuals::dark()); - } else { - egui_ctx.set_visuals(egui::Visuals::light()); - } + + let theme = system_theme.unwrap_or(web_options.default_theme); + egui_ctx.set_visuals(theme.egui_visuals()); let app = app_creator(&epi::CreationContext { egui_ctx: egui_ctx.clone(), @@ -393,8 +399,12 @@ impl AppRunnerContainer { /// Install event listeners to register different input events /// and start running the given app. -pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result { - let mut runner = AppRunner::new(canvas_id, app_creator)?; +pub fn start( + canvas_id: &str, + web_options: crate::WebOptions, + app_creator: epi::AppCreator, +) -> Result { + let mut runner = AppRunner::new(canvas_id, web_options, app_creator)?; runner.warm_up()?; start_runner(runner) } diff --git a/eframe/src/web/mod.rs b/eframe/src/web/mod.rs index 693408da5..13b666e33 100644 --- a/eframe/src/web/mod.rs +++ b/eframe/src/web/mod.rs @@ -26,6 +26,8 @@ use web_sys::EventTarget; use input::*; +use crate::Theme; + // ---------------------------------------------------------------------------- /// Current time in seconds (since undefined point in time) @@ -55,13 +57,12 @@ pub fn native_pixels_per_point() -> f32 { } } -pub fn prefer_dark_mode() -> Option { - Some( - web_sys::window()? - .match_media("(prefers-color-scheme: dark)") - .ok()?? - .matches(), - ) +pub fn system_theme() -> Option { + let dark_mode = web_sys::window()? + .match_media("(prefers-color-scheme: dark)") + .ok()?? + .matches(); + Some(if dark_mode { Theme::Dark } else { Theme::Light }) } pub fn canvas_element(canvas_id: &str) -> Option { diff --git a/egui_demo_app/src/lib.rs b/egui_demo_app/src/lib.rs index cc7bf65d4..a7d7ed949 100644 --- a/egui_demo_app/src/lib.rs +++ b/egui_demo_app/src/lib.rs @@ -34,5 +34,10 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { // Redirect tracing to console.log and friends: tracing_wasm::set_as_global_default(); - eframe::start_web(canvas_id, Box::new(|cc| Box::new(WrapApp::new(cc)))) + let web_options = eframe::WebOptions::default(); + eframe::start_web( + canvas_id, + web_options, + Box::new(|cc| Box::new(WrapApp::new(cc))), + ) } diff --git a/egui_demo_app/src/wrap_app.rs b/egui_demo_app/src/wrap_app.rs index ff43549b5..fd32bb3ed 100644 --- a/egui_demo_app/src/wrap_app.rs +++ b/egui_demo_app/src/wrap_app.rs @@ -112,12 +112,6 @@ impl WrapApp { } } - if cc.integration_info.prefer_dark_mode == Some(false) { - cc.egui_ctx.set_visuals(egui::Visuals::light()); // use light mode if explicitly asked for - } else { - cc.egui_ctx.set_visuals(egui::Visuals::dark()); // use dark mode if there is no preference, or the preference is dark mode - } - slf }