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
This commit is contained in:
parent
29973e5d02
commit
317436c057
10 changed files with 160 additions and 59 deletions
|
@ -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)).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()?
|
||||
pub fn system_theme() -> Option<Theme> {
|
||||
let dark_mode = web_sys::window()?
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
.ok()??
|
||||
.matches(),
|
||||
)
|
||||
.matches();
|
||||
Some(if dark_mode { Theme::Dark } else { Theme::Light })
|
||||
}
|
||||
|
||||
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
|
||||
|
|
|
@ -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))),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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…
Reference in a new issue