Skip to content

Commit

Permalink
Follow the System Theme in egui (#4860)
Browse files Browse the repository at this point in the history
* Some initial progress towards #4490

This PR just moves `Theme` and the "follow system theme" settings to
egui and adds `RawInput.system_theme`.
A follow-up PR can then introduce the two separate `dark_mode_style` and
`light_mode_style` fields on `Options`.


<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->


* [x] I have followed the instructions in the PR template


### Breaking changes

The options `follow_system_theme` and `default_theme` has been moved
from `eframe` into `egui::Options`, settable with `ctx.options_mut`

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
  • Loading branch information
bash and emilk authored Aug 6, 2024
1 parent ed02542 commit 2dac4a4
Show file tree
Hide file tree
Showing 16 changed files with 129 additions and 137 deletions.
62 changes: 0 additions & 62 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,21 +297,6 @@ pub struct NativeOptions {
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub renderer: Renderer,

/// Try to detect and follow the system preferred setting for dark vs light mode.
///
/// The theme will automatically change when the dark vs light mode preference is changed.
///
/// Does not work on Linux (see <https://github.com/rust-windowing/winit/issues/1549>).
///
/// See also [`Self::default_theme`].
pub follow_system_theme: bool,

/// Which theme to use in case [`Self::follow_system_theme`] is `false`
/// or eframe fails to detect the system theme.
///
/// Default: [`Theme::Dark`].
pub default_theme: Theme,

/// This controls what happens when you close the main eframe window.
///
/// If `true`, execution will continue after the eframe window is closed.
Expand Down Expand Up @@ -417,8 +402,6 @@ impl Default for NativeOptions {
#[cfg(any(feature = "glow", feature = "wgpu"))]
renderer: Renderer::default(),

follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
default_theme: Theme::Dark,
run_and_return: true,

#[cfg(any(feature = "glow", feature = "wgpu"))]
Expand Down Expand Up @@ -449,19 +432,6 @@ impl Default for NativeOptions {
/// 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,

/// Sets the number of bits in the depth buffer.
///
/// `egui` doesn't need the depth buffer, so the default value is 0.
Expand Down Expand Up @@ -492,8 +462,6 @@ pub struct WebOptions {
impl Default for WebOptions {
fn default() -> Self {
Self {
follow_system_theme: true,
default_theme: Theme::Dark,
depth_buffer: 0,

#[cfg(feature = "glow")]
Expand All @@ -509,31 +477,6 @@ impl Default for WebOptions {

// ----------------------------------------------------------------------------

/// 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(),
}
}
}

// ----------------------------------------------------------------------------

/// WebGL Context options
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand Down Expand Up @@ -814,11 +757,6 @@ pub struct IntegrationInfo {
#[cfg(target_arch = "wasm32")]
pub web_info: WebInfo,

/// Does the OS use dark or light mode?
///
/// `None` means "don't know".
pub system_theme: Option<Theme>,

/// Seconds of cpu usage (in seconds) on the previous frame.
///
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
Expand Down
22 changes: 2 additions & 20 deletions crates/eframe/src/native/epi_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId};
use egui_winit::{EventResponse, WindowSettings};

use crate::{epi, Theme};
use crate::epi;

pub fn viewport_builder(
egui_zoom_factor: f32,
Expand Down Expand Up @@ -158,7 +158,6 @@ pub struct EpiIntegration {
close: bool,

can_drag_window: bool,
follow_system_theme: bool,
#[cfg(feature = "persistence")]
persist_window: bool,
app_icon_setter: super::app_icon::AppTitleIconSetter,
Expand All @@ -169,7 +168,6 @@ impl EpiIntegration {
pub fn new(
egui_ctx: egui::Context,
window: &winit::window::Window,
system_theme: Option<Theme>,
app_name: &str,
native_options: &crate::NativeOptions,
storage: Option<Box<dyn epi::Storage>>,
Expand All @@ -180,10 +178,7 @@ impl EpiIntegration {
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
) -> Self {
let frame = epi::Frame {
info: epi::IntegrationInfo {
system_theme,
cpu_usage: None,
},
info: epi::IntegrationInfo { cpu_usage: None },
storage,
#[cfg(feature = "glow")]
gl,
Expand Down Expand Up @@ -217,7 +212,6 @@ impl EpiIntegration {
pending_full_output: Default::default(),
close: false,
can_drag_window: false,
follow_system_theme: native_options.follow_system_theme,
#[cfg(feature = "persistence")]
persist_window: native_options.persist_window,
app_icon_setter,
Expand Down Expand Up @@ -251,11 +245,6 @@ impl EpiIntegration {
state: ElementState::Pressed,
..
} => self.can_drag_window = true,
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 @@ -398,10 +387,3 @@ 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,
}
}
7 changes: 1 addition & 6 deletions crates/eframe/src/native/glow_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,11 @@ impl GlowWinitApp {
}
}

let system_theme =
winit_integration::system_theme(&glutin.window(ViewportId::ROOT), &self.native_options);
let painter = Rc::new(RefCell::new(painter));

let integration = EpiIntegration::new(
egui_ctx,
&glutin.window(ViewportId::ROOT),
system_theme,
&self.app_name,
&self.native_options,
storage,
Expand Down Expand Up @@ -281,9 +278,6 @@ impl GlowWinitApp {
}
}

let theme = system_theme.unwrap_or(self.native_options.default_theme);
integration.egui_ctx.set_visuals(theme.egui_visuals());

if self
.native_options
.viewport
Expand Down Expand Up @@ -1120,6 +1114,7 @@ impl GlutinWindowContext {
viewport_id,
event_loop,
Some(window.scale_factor() as f32),
window.theme(),
self.max_texture_side,
)
});
Expand Down
6 changes: 2 additions & 4 deletions crates/eframe/src/native/wgpu_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,9 @@ impl WgpuWinitApp {

let wgpu_render_state = painter.render_state();

let system_theme = winit_integration::system_theme(&window, &self.native_options);
let integration = EpiIntegration::new(
egui_ctx.clone(),
&window,
system_theme,
&self.app_name,
&self.native_options,
storage,
Expand Down Expand Up @@ -243,6 +241,7 @@ impl WgpuWinitApp {
ViewportId::ROOT,
event_loop,
Some(window.scale_factor() as f32),
window.theme(),
painter.max_texture_side(),
);

Expand All @@ -251,8 +250,6 @@ impl WgpuWinitApp {
let event_loop_proxy = self.repaint_proxy.lock().clone();
egui_winit.init_accesskit(&window, event_loop_proxy);
}
let theme = system_theme.unwrap_or(self.native_options.default_theme);
egui_ctx.set_visuals(theme.egui_visuals());

let app_creator = std::mem::take(&mut self.app_creator)
.expect("Single-use AppCreator has unexpectedly already been taken");
Expand Down Expand Up @@ -872,6 +869,7 @@ impl Viewport {
viewport_id,
event_loop,
Some(window.scale_factor() as f32),
window.theme(),
painter.max_texture_side(),
));

Expand Down
10 changes: 0 additions & 10 deletions crates/eframe/src/native/winit_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,6 @@ pub enum EventResult {
Exit,
}

pub fn system_theme(window: &Window, options: &crate::NativeOptions) -> Option<crate::Theme> {
if options.follow_system_theme {
window
.theme()
.map(super::epi_integration::theme_from_winit_theme)
} else {
None
}
}

#[cfg(feature = "accesskit")]
pub(crate) fn on_accesskit_window_event(
egui_winit: &mut egui_winit::State,
Expand Down
11 changes: 1 addition & 10 deletions crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,11 @@ impl AppRunner {
) -> Result<Self, String> {
let painter = super::ActiveWebPainter::new(canvas, &web_options).await?;

let system_theme = if web_options.follow_system_theme {
super::system_theme()
} else {
None
};

let info = epi::IntegrationInfo {
web_info: epi::WebInfo {
user_agent: super::user_agent().unwrap_or_default(),
location: super::web_location(),
},
system_theme,
cpu_usage: None,
};
let storage = LocalStorage::default();
Expand All @@ -68,9 +61,6 @@ impl AppRunner {
o.zoom_factor = 1.0;
});

let theme = system_theme.unwrap_or(web_options.default_theme);
egui_ctx.set_visuals(theme.egui_visuals());

let cc = epi::CreationContext {
egui_ctx: egui_ctx.clone(),
integration_info: info.clone(),
Expand Down Expand Up @@ -132,6 +122,7 @@ impl AppRunner {
.entry(egui::ViewportId::ROOT)
.or_default()
.native_pixels_per_point = Some(super::native_pixels_per_point());
runner.input.raw.system_theme = super::system_theme();

Ok(runner)
}
Expand Down
13 changes: 7 additions & 6 deletions crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub(crate) fn install_event_handlers(runner_ref: &WebRunner) -> Result<(), JsVal
install_wheel(runner_ref, &canvas)?;
install_drag_and_drop(runner_ref, &canvas)?;
install_window_events(runner_ref, &window)?;
install_color_scheme_change_event(runner_ref, &window)?;
Ok(())
}

Expand Down Expand Up @@ -353,17 +354,17 @@ fn install_window_events(runner_ref: &WebRunner, window: &EventTarget) -> Result
Ok(())
}

pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Result<(), JsValue> {
let window = web_sys::window().unwrap();

if let Some(media_query_list) = prefers_color_scheme_dark(&window)? {
fn install_color_scheme_change_event(
runner_ref: &WebRunner,
window: &web_sys::Window,
) -> Result<(), JsValue> {
if let Some(media_query_list) = prefers_color_scheme_dark(window)? {
runner_ref.add_event_listener::<web_sys::MediaQueryListEvent>(
&media_query_list,
"change",
|event, runner| {
let theme = theme_from_dark_mode(event.matches());
runner.frame.info.system_theme = Some(theme);
runner.egui_ctx().set_visuals(theme.egui_visuals());
runner.input.raw.system_theme = Some(theme);
runner.needs_repaint.repaint_asap();
},
)?;
Expand Down
10 changes: 4 additions & 6 deletions crates/eframe/src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ use web_sys::MediaQueryList;

use input::*;

use crate::Theme;

// ----------------------------------------------------------------------------

pub(crate) fn string_from_js_value(value: &JsValue) -> String {
Expand Down Expand Up @@ -103,7 +101,7 @@ pub fn native_pixels_per_point() -> f32 {
/// Ask the browser about the preferred system theme.
///
/// `None` means unknown.
pub fn system_theme() -> Option<Theme> {
pub fn system_theme() -> Option<egui::Theme> {
let dark_mode = prefers_color_scheme_dark(&web_sys::window()?)
.ok()??
.matches();
Expand All @@ -114,11 +112,11 @@ fn prefers_color_scheme_dark(window: &web_sys::Window) -> Result<Option<MediaQue
window.match_media("(prefers-color-scheme: dark)")
}

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

Expand Down
6 changes: 0 additions & 6 deletions crates/eframe/src/web/web_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ impl WebRunner {
) -> Result<(), JsValue> {
self.destroy();

let follow_system_theme = web_options.follow_system_theme;

let text_agent = TextAgent::attach(self)?;

let runner = AppRunner::new(canvas, web_options, app_creator, text_agent).await?;
Expand All @@ -83,10 +81,6 @@ impl WebRunner {
{
events::install_event_handlers(self)?;

if follow_system_theme {
events::install_color_scheme_change_event(self)?;
}

// The resize observer handles calling `request_animation_frame` to start the render loop.
events::install_resize_observer(self)?;
}
Expand Down
Loading

0 comments on commit 2dac4a4

Please sign in to comment.