Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eframe: Automatically change theme when system dark/light mode changes #2750

Merged
merged 17 commits into from
Mar 29, 2023
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
1 change: 1 addition & 0 deletions crates/eframe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ web-sys = { version = "0.3.58", features = [
"KeyboardEvent",
"Location",
"MediaQueryList",
"MediaQueryListEvent",
"MouseEvent",
"Navigator",
"Performance",
Expand Down
6 changes: 4 additions & 2 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,15 @@ pub struct NativeOptions {
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub renderer: Renderer,

/// Only used 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 <https://github.com/frewsxcv/rust-dark-light/issues/17>.
///
/// On Mac and Windows the theme will automatically change when the dark vs light mode preference is changed.
///
/// This only works on Linux if the `dark-light` feature is enabled.
///
/// See also [`Self::default_theme`].
pub follow_system_theme: bool,

Expand Down
18 changes: 17 additions & 1 deletion crates/eframe/src/native/epi_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ pub fn window_builder<E>(
// Restore pos/size from previous session
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
#[cfg(windows)]
window_settings.clamp_window_to_sane_position(&event_loop);
window_settings.clamp_window_to_sane_position(event_loop);
window_builder = window_settings.initialize_window(window_builder);
window_settings.inner_size_points()
} else {
Expand Down Expand Up @@ -308,14 +308,17 @@ pub struct EpiIntegration {
close: bool,
can_drag_window: bool,
window_state: WindowState,
follow_system_theme: bool,
}

impl EpiIntegration {
#[allow(clippy::too_many_arguments)]
pub fn new<E>(
event_loop: &EventLoopWindowTarget<E>,
max_texture_side: usize,
window: &winit::window::Window,
system_theme: Option<Theme>,
follow_system_theme: bool,
storage: Option<Box<dyn epi::Storage>>,
#[cfg(feature = "glow")] gl: Option<std::sync::Arc<glow::Context>>,
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
Expand Down Expand Up @@ -363,6 +366,7 @@ impl EpiIntegration {
close: false,
can_drag_window: false,
window_state,
follow_system_theme,
}
}

Expand Down Expand Up @@ -426,6 +430,11 @@ impl EpiIntegration {
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
self.frame.info.native_pixels_per_point = Some(*scale_factor as _);
}
WindowEvent::ThemeChanged(winit_theme) if self.follow_system_theme => {
let theme = theme_from_winit_theme(*winit_theme);
self.frame.info.system_theme = Some(theme);
self.egui_ctx.set_visuals(theme.egui_visuals());
}
_ => {}
}

Expand Down Expand Up @@ -565,3 +574,10 @@ pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Mem
#[cfg(not(feature = "persistence"))]
None
}

pub(crate) fn theme_from_winit_theme(theme: winit::window::Theme) -> Theme {
match theme {
winit::window::Theme::Dark => Theme::Dark,
winit::window::Theme::Light => Theme::Light,
}
}
25 changes: 23 additions & 2 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,12 +675,13 @@ mod glow_integration {
egui_glow::Painter::new(gl.clone(), "", self.native_options.shader_version)
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));

let system_theme = self.native_options.system_theme();
let system_theme = system_theme(gl_window.window(), &self.native_options);
let mut integration = epi_integration::EpiIntegration::new(
event_loop,
painter.max_texture_side(),
gl_window.window(),
system_theme,
self.native_options.follow_system_theme,
storage,
Some(gl.clone()),
#[cfg(feature = "wgpu")]
Expand Down Expand Up @@ -1117,12 +1118,13 @@ mod wgpu_integration {

let wgpu_render_state = painter.render_state();

let system_theme = self.native_options.system_theme();
let system_theme = system_theme(&window, &self.native_options);
let mut integration = epi_integration::EpiIntegration::new(
event_loop,
painter.max_texture_side().unwrap_or(2048),
&window,
system_theme,
self.native_options.follow_system_theme,
storage,
#[cfg(feature = "glow")]
None,
Expand Down Expand Up @@ -1421,3 +1423,22 @@ mod wgpu_integration {

#[cfg(feature = "wgpu")]
pub use wgpu_integration::run_wgpu;

#[cfg(any(target_os = "windows", target_os = "macos"))]
fn system_theme(window: &winit::window::Window, options: &NativeOptions) -> Option<crate::Theme> {
if options.follow_system_theme {
window
.theme()
.map(super::epi_integration::theme_from_winit_theme)
} else {
None
}
}

// Winit only reads the system theme on macOS and Windows.
// On Linux we have to fall back on dark-light (if enabled).
// See: https://github.com/rust-windowing/winit/issues/1549
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
fn system_theme(_window: &winit::window::Window, options: &NativeOptions) -> Option<crate::Theme> {
options.system_theme()
}
9 changes: 7 additions & 2 deletions crates/eframe/src/web/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,16 @@ pub async fn start(
tracing::warn!(
"eframe compiled without RUSTFLAGS='--cfg=web_sys_unstable_apis'. Copying text won't work."
);
let follow_system_theme = web_options.follow_system_theme;

let mut runner = AppRunner::new(canvas_id, web_options, app_creator).await?;
runner.warm_up()?;
start_runner(runner)
start_runner(runner, follow_system_theme)
}

/// Install event listeners to register different input events
/// and starts running the given [`AppRunner`].
fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
fn start_runner(app_runner: AppRunner, follow_system_theme: bool) -> Result<AppRunnerRef, JsValue> {
let mut runner_container = AppRunnerContainer {
runner: Arc::new(Mutex::new(app_runner)),
panicked: Arc::new(AtomicBool::new(false)),
Expand All @@ -549,6 +550,10 @@ fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
super::events::install_document_events(&mut runner_container)?;
text_agent::install_text_agent(&mut runner_container)?;

if follow_system_theme {
super::events::install_color_scheme_change_event(&mut runner_container)?;
}

super::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;

// Disable all event handlers on panic
Expand Down
21 changes: 21 additions & 0 deletions crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,27 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
Ok(())
}

pub fn install_color_scheme_change_event(
runner_container: &mut AppRunnerContainer,
) -> Result<(), JsValue> {
let window = web_sys::window().unwrap();

if let Some(media_query_list) = prefers_color_scheme_dark(&window)? {
runner_container.add_event_listener::<web_sys::MediaQueryListEvent>(
&media_query_list,
"change",
|event, mut runner_lock| {
let theme = theme_from_dark_mode(event.matches());
runner_lock.frame.info.system_theme = Some(theme);
runner_lock.egui_ctx().set_visuals(theme.egui_visuals());
runner_lock.needs_repaint.repaint_asap();
},
)?;
}

Ok(())
}

pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();

Expand Down
19 changes: 15 additions & 4 deletions crates/eframe/src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use std::sync::{

use egui::Vec2;
use wasm_bindgen::prelude::*;
use web_sys::EventTarget;
use web_sys::{EventTarget, MediaQueryList};

use input::*;

Expand Down Expand Up @@ -75,11 +75,22 @@ pub fn native_pixels_per_point() -> f32 {
}

pub fn system_theme() -> Option<Theme> {
let dark_mode = web_sys::window()?
.match_media("(prefers-color-scheme: dark)")
let dark_mode = prefers_color_scheme_dark(&web_sys::window()?)
.ok()??
.matches();
Some(if dark_mode { Theme::Dark } else { Theme::Light })
Some(theme_from_dark_mode(dark_mode))
}

fn prefers_color_scheme_dark(window: &web_sys::Window) -> Result<Option<MediaQueryList>, JsValue> {
window.match_media("(prefers-color-scheme: dark)")
}

fn theme_from_dark_mode(dark_mode: bool) -> Theme {
if dark_mode {
Theme::Dark
} else {
Theme::Light
}
}

pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
Expand Down