Skip to content

Commit

Permalink
Use dark-light on Mac and Windows (#1726)
Browse files Browse the repository at this point in the history
* Use dark-light on Mac and Windows

dark-light has a nasty problem on Linux: frewsxcv/rust-dark-light#17

So we made dark-light opt-in in #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
  • Loading branch information
emilk authored Jun 9, 2022
1 parent 29973e5 commit 317436c
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 59 deletions.
3 changes: 3 additions & 0 deletions eframe/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
4 changes: 3 additions & 1 deletion eframe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
102 changes: 100 additions & 2 deletions eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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 <https://github.com/frewsxcv/rust-dark-light/issues/17>.
///
/// 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 {
Expand All @@ -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<Theme> {
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<Theme> {
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(),
}
}
}
Expand Down Expand Up @@ -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<WebInfo>,

/// 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<bool>,
pub system_theme: Option<Theme>,

/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
/// `None` if this is the first frame.
Expand Down
11 changes: 8 additions & 3 deletions eframe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down
33 changes: 4 additions & 29 deletions eframe/src/native/epi_integration.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -187,6 +182,7 @@ impl EpiIntegration {
event_loop: &EventLoopWindowTarget<E>,
max_texture_side: usize,
window: &winit::window::Window,
system_theme: Option<Theme>,
storage: Option<Box<dyn epi::Storage>>,
#[cfg(feature = "glow")] gl: Option<std::sync::Arc<glow::Context>>,
#[cfg(feature = "wgpu")] render_state: Option<egui_wgpu::RenderState>,
Expand All @@ -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()),
Expand All @@ -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;
Expand Down Expand Up @@ -376,16 +364,3 @@ pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Mem
#[cfg(not(feature = "persistence"))]
None
}

#[cfg(feature = "dark-light")]
fn prefer_dark_mode() -> Option<bool> {
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<bool> {
None
}
8 changes: 8 additions & 0 deletions eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down
30 changes: 20 additions & 10 deletions eframe/src/web/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,24 @@ pub struct AppRunner {
}

impl AppRunner {
pub fn new(canvas_id: &str, app_creator: epi::AppCreator) -> Result<Self, JsValue> {
pub fn new(
canvas_id: &str,
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<Self, JsValue> {
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,
Expand All @@ -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(),
Expand Down Expand Up @@ -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<AppRunnerRef, JsValue> {
let mut runner = AppRunner::new(canvas_id, app_creator)?;
pub fn start(
canvas_id: &str,
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<AppRunnerRef, JsValue> {
let mut runner = AppRunner::new(canvas_id, web_options, app_creator)?;
runner.warm_up()?;
start_runner(runner)
}
Expand Down
15 changes: 8 additions & 7 deletions eframe/src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ use web_sys::EventTarget;

use input::*;

use crate::Theme;

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

/// Current time in seconds (since undefined point in time)
Expand Down Expand Up @@ -55,13 +57,12 @@ pub fn native_pixels_per_point() -> f32 {
}
}

pub fn prefer_dark_mode() -> Option<bool> {
Some(
web_sys::window()?
.match_media("(prefers-color-scheme: dark)")
.ok()??
.matches(),
)
pub fn system_theme() -> Option<Theme> {
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<web_sys::HtmlCanvasElement> {
Expand Down
7 changes: 6 additions & 1 deletion egui_demo_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))),
)
}
Loading

0 comments on commit 317436c

Please sign in to comment.