Browse Source

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
pull/1739/head
Emil Ernerfeldt 2 years ago
committed by GitHub
parent
commit
317436c057
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      eframe/CHANGELOG.md
  2. 4
      eframe/Cargo.toml
  3. 102
      eframe/src/epi.rs
  4. 11
      eframe/src/lib.rs
  5. 33
      eframe/src/native/epi_integration.rs
  6. 8
      eframe/src/native/run.rs
  7. 30
      eframe/src/web/backend.rs
  8. 15
      eframe/src/web/mod.rs
  9. 7
      egui_demo_app/src/lib.rs
  10. 6
      egui_demo_app/src/wrap_app.rs

3
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)).

4
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.

102
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 <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 {
@ -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(),
}
}
}
@ -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.

11
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(())
}

33
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<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>,
@ -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<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
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());

30
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<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,
@ -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<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)
}

15
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<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> {

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

6
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
}

Loading…
Cancel
Save