Follow the System Theme in egui (#4860)
* 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>
This commit is contained in:
parent
ed0254288a
commit
2dac4a4fc6
|
|
@ -297,21 +297,6 @@ pub struct NativeOptions {
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||||
pub renderer: Renderer,
|
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.
|
/// This controls what happens when you close the main eframe window.
|
||||||
///
|
///
|
||||||
/// If `true`, execution will continue after the eframe window is closed.
|
/// If `true`, execution will continue after the eframe window is closed.
|
||||||
|
|
@ -417,8 +402,6 @@ impl Default for NativeOptions {
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||||
renderer: Renderer::default(),
|
renderer: Renderer::default(),
|
||||||
|
|
||||||
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
|
||||||
default_theme: Theme::Dark,
|
|
||||||
run_and_return: true,
|
run_and_return: true,
|
||||||
|
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||||
|
|
@ -449,19 +432,6 @@ impl Default for NativeOptions {
|
||||||
/// Options when using `eframe` in a web page.
|
/// Options when using `eframe` in a web page.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub struct WebOptions {
|
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.
|
/// Sets the number of bits in the depth buffer.
|
||||||
///
|
///
|
||||||
/// `egui` doesn't need the depth buffer, so the default value is 0.
|
/// `egui` doesn't need the depth buffer, so the default value is 0.
|
||||||
|
|
@ -492,8 +462,6 @@ pub struct WebOptions {
|
||||||
impl Default for WebOptions {
|
impl Default for WebOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
follow_system_theme: true,
|
|
||||||
default_theme: Theme::Dark,
|
|
||||||
depth_buffer: 0,
|
depth_buffer: 0,
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
|
|
@ -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
|
/// WebGL Context options
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
@ -814,11 +757,6 @@ pub struct IntegrationInfo {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub web_info: WebInfo,
|
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.
|
/// Seconds of cpu usage (in seconds) on the previous frame.
|
||||||
///
|
///
|
||||||
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
|
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
|
||||||
use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId};
|
use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId};
|
||||||
use egui_winit::{EventResponse, WindowSettings};
|
use egui_winit::{EventResponse, WindowSettings};
|
||||||
|
|
||||||
use crate::{epi, Theme};
|
use crate::epi;
|
||||||
|
|
||||||
pub fn viewport_builder(
|
pub fn viewport_builder(
|
||||||
egui_zoom_factor: f32,
|
egui_zoom_factor: f32,
|
||||||
|
|
@ -158,7 +158,6 @@ pub struct EpiIntegration {
|
||||||
close: bool,
|
close: bool,
|
||||||
|
|
||||||
can_drag_window: bool,
|
can_drag_window: bool,
|
||||||
follow_system_theme: bool,
|
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
persist_window: bool,
|
persist_window: bool,
|
||||||
app_icon_setter: super::app_icon::AppTitleIconSetter,
|
app_icon_setter: super::app_icon::AppTitleIconSetter,
|
||||||
|
|
@ -169,7 +168,6 @@ impl EpiIntegration {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
egui_ctx: egui::Context,
|
egui_ctx: egui::Context,
|
||||||
window: &winit::window::Window,
|
window: &winit::window::Window,
|
||||||
system_theme: Option<Theme>,
|
|
||||||
app_name: &str,
|
app_name: &str,
|
||||||
native_options: &crate::NativeOptions,
|
native_options: &crate::NativeOptions,
|
||||||
storage: Option<Box<dyn epi::Storage>>,
|
storage: Option<Box<dyn epi::Storage>>,
|
||||||
|
|
@ -180,10 +178,7 @@ impl EpiIntegration {
|
||||||
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
|
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let frame = epi::Frame {
|
let frame = epi::Frame {
|
||||||
info: epi::IntegrationInfo {
|
info: epi::IntegrationInfo { cpu_usage: None },
|
||||||
system_theme,
|
|
||||||
cpu_usage: None,
|
|
||||||
},
|
|
||||||
storage,
|
storage,
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
gl,
|
gl,
|
||||||
|
|
@ -217,7 +212,6 @@ impl EpiIntegration {
|
||||||
pending_full_output: Default::default(),
|
pending_full_output: Default::default(),
|
||||||
close: false,
|
close: false,
|
||||||
can_drag_window: false,
|
can_drag_window: false,
|
||||||
follow_system_theme: native_options.follow_system_theme,
|
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
persist_window: native_options.persist_window,
|
persist_window: native_options.persist_window,
|
||||||
app_icon_setter,
|
app_icon_setter,
|
||||||
|
|
@ -251,11 +245,6 @@ impl EpiIntegration {
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
..
|
..
|
||||||
} => self.can_drag_window = true,
|
} => 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());
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,10 +387,3 @@ pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Mem
|
||||||
#[cfg(not(feature = "persistence"))]
|
#[cfg(not(feature = "persistence"))]
|
||||||
None
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 painter = Rc::new(RefCell::new(painter));
|
||||||
|
|
||||||
let integration = EpiIntegration::new(
|
let integration = EpiIntegration::new(
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
&glutin.window(ViewportId::ROOT),
|
&glutin.window(ViewportId::ROOT),
|
||||||
system_theme,
|
|
||||||
&self.app_name,
|
&self.app_name,
|
||||||
&self.native_options,
|
&self.native_options,
|
||||||
storage,
|
storage,
|
||||||
|
|
@ -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
|
if self
|
||||||
.native_options
|
.native_options
|
||||||
.viewport
|
.viewport
|
||||||
|
|
@ -1120,6 +1114,7 @@ impl GlutinWindowContext {
|
||||||
viewport_id,
|
viewport_id,
|
||||||
event_loop,
|
event_loop,
|
||||||
Some(window.scale_factor() as f32),
|
Some(window.scale_factor() as f32),
|
||||||
|
window.theme(),
|
||||||
self.max_texture_side,
|
self.max_texture_side,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -203,11 +203,9 @@ impl WgpuWinitApp {
|
||||||
|
|
||||||
let wgpu_render_state = painter.render_state();
|
let wgpu_render_state = painter.render_state();
|
||||||
|
|
||||||
let system_theme = winit_integration::system_theme(&window, &self.native_options);
|
|
||||||
let integration = EpiIntegration::new(
|
let integration = EpiIntegration::new(
|
||||||
egui_ctx.clone(),
|
egui_ctx.clone(),
|
||||||
&window,
|
&window,
|
||||||
system_theme,
|
|
||||||
&self.app_name,
|
&self.app_name,
|
||||||
&self.native_options,
|
&self.native_options,
|
||||||
storage,
|
storage,
|
||||||
|
|
@ -243,6 +241,7 @@ impl WgpuWinitApp {
|
||||||
ViewportId::ROOT,
|
ViewportId::ROOT,
|
||||||
event_loop,
|
event_loop,
|
||||||
Some(window.scale_factor() as f32),
|
Some(window.scale_factor() as f32),
|
||||||
|
window.theme(),
|
||||||
painter.max_texture_side(),
|
painter.max_texture_side(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -251,8 +250,6 @@ impl WgpuWinitApp {
|
||||||
let event_loop_proxy = self.repaint_proxy.lock().clone();
|
let event_loop_proxy = self.repaint_proxy.lock().clone();
|
||||||
egui_winit.init_accesskit(&window, event_loop_proxy);
|
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)
|
let app_creator = std::mem::take(&mut self.app_creator)
|
||||||
.expect("Single-use AppCreator has unexpectedly already been taken");
|
.expect("Single-use AppCreator has unexpectedly already been taken");
|
||||||
|
|
@ -872,6 +869,7 @@ impl Viewport {
|
||||||
viewport_id,
|
viewport_id,
|
||||||
event_loop,
|
event_loop,
|
||||||
Some(window.scale_factor() as f32),
|
Some(window.scale_factor() as f32),
|
||||||
|
window.theme(),
|
||||||
painter.max_texture_side(),
|
painter.max_texture_side(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,16 +118,6 @@ pub enum EventResult {
|
||||||
Exit,
|
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")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub(crate) fn on_accesskit_window_event(
|
pub(crate) fn on_accesskit_window_event(
|
||||||
egui_winit: &mut egui_winit::State,
|
egui_winit: &mut egui_winit::State,
|
||||||
|
|
|
||||||
|
|
@ -38,18 +38,11 @@ impl AppRunner {
|
||||||
) -> Result<Self, String> {
|
) -> Result<Self, String> {
|
||||||
let painter = super::ActiveWebPainter::new(canvas, &web_options).await?;
|
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 {
|
let info = epi::IntegrationInfo {
|
||||||
web_info: epi::WebInfo {
|
web_info: epi::WebInfo {
|
||||||
user_agent: super::user_agent().unwrap_or_default(),
|
user_agent: super::user_agent().unwrap_or_default(),
|
||||||
location: super::web_location(),
|
location: super::web_location(),
|
||||||
},
|
},
|
||||||
system_theme,
|
|
||||||
cpu_usage: None,
|
cpu_usage: None,
|
||||||
};
|
};
|
||||||
let storage = LocalStorage::default();
|
let storage = LocalStorage::default();
|
||||||
|
|
@ -68,9 +61,6 @@ impl AppRunner {
|
||||||
o.zoom_factor = 1.0;
|
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 {
|
let cc = epi::CreationContext {
|
||||||
egui_ctx: egui_ctx.clone(),
|
egui_ctx: egui_ctx.clone(),
|
||||||
integration_info: info.clone(),
|
integration_info: info.clone(),
|
||||||
|
|
@ -132,6 +122,7 @@ impl AppRunner {
|
||||||
.entry(egui::ViewportId::ROOT)
|
.entry(egui::ViewportId::ROOT)
|
||||||
.or_default()
|
.or_default()
|
||||||
.native_pixels_per_point = Some(super::native_pixels_per_point());
|
.native_pixels_per_point = Some(super::native_pixels_per_point());
|
||||||
|
runner.input.raw.system_theme = super::system_theme();
|
||||||
|
|
||||||
Ok(runner)
|
Ok(runner)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ pub(crate) fn install_event_handlers(runner_ref: &WebRunner) -> Result<(), JsVal
|
||||||
install_wheel(runner_ref, &canvas)?;
|
install_wheel(runner_ref, &canvas)?;
|
||||||
install_drag_and_drop(runner_ref, &canvas)?;
|
install_drag_and_drop(runner_ref, &canvas)?;
|
||||||
install_window_events(runner_ref, &window)?;
|
install_window_events(runner_ref, &window)?;
|
||||||
|
install_color_scheme_change_event(runner_ref, &window)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,17 +354,17 @@ fn install_window_events(runner_ref: &WebRunner, window: &EventTarget) -> Result
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Result<(), JsValue> {
|
fn install_color_scheme_change_event(
|
||||||
let window = web_sys::window().unwrap();
|
runner_ref: &WebRunner,
|
||||||
|
window: &web_sys::Window,
|
||||||
if let Some(media_query_list) = prefers_color_scheme_dark(&window)? {
|
) -> Result<(), JsValue> {
|
||||||
|
if let Some(media_query_list) = prefers_color_scheme_dark(window)? {
|
||||||
runner_ref.add_event_listener::<web_sys::MediaQueryListEvent>(
|
runner_ref.add_event_listener::<web_sys::MediaQueryListEvent>(
|
||||||
&media_query_list,
|
&media_query_list,
|
||||||
"change",
|
"change",
|
||||||
|event, runner| {
|
|event, runner| {
|
||||||
let theme = theme_from_dark_mode(event.matches());
|
let theme = theme_from_dark_mode(event.matches());
|
||||||
runner.frame.info.system_theme = Some(theme);
|
runner.input.raw.system_theme = Some(theme);
|
||||||
runner.egui_ctx().set_visuals(theme.egui_visuals());
|
|
||||||
runner.needs_repaint.repaint_asap();
|
runner.needs_repaint.repaint_asap();
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,6 @@ use web_sys::MediaQueryList;
|
||||||
|
|
||||||
use input::*;
|
use input::*;
|
||||||
|
|
||||||
use crate::Theme;
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub(crate) fn string_from_js_value(value: &JsValue) -> String {
|
pub(crate) fn string_from_js_value(value: &JsValue) -> String {
|
||||||
|
|
@ -103,7 +101,7 @@ pub fn native_pixels_per_point() -> f32 {
|
||||||
/// Ask the browser about the preferred system theme.
|
/// Ask the browser about the preferred system theme.
|
||||||
///
|
///
|
||||||
/// `None` means unknown.
|
/// `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()?)
|
let dark_mode = prefers_color_scheme_dark(&web_sys::window()?)
|
||||||
.ok()??
|
.ok()??
|
||||||
.matches();
|
.matches();
|
||||||
|
|
@ -114,11 +112,11 @@ fn prefers_color_scheme_dark(window: &web_sys::Window) -> Result<Option<MediaQue
|
||||||
window.match_media("(prefers-color-scheme: dark)")
|
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 {
|
if dark_mode {
|
||||||
Theme::Dark
|
egui::Theme::Dark
|
||||||
} else {
|
} else {
|
||||||
Theme::Light
|
egui::Theme::Light
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,6 @@ impl WebRunner {
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
self.destroy();
|
self.destroy();
|
||||||
|
|
||||||
let follow_system_theme = web_options.follow_system_theme;
|
|
||||||
|
|
||||||
let text_agent = TextAgent::attach(self)?;
|
let text_agent = TextAgent::attach(self)?;
|
||||||
|
|
||||||
let runner = AppRunner::new(canvas, web_options, app_creator, text_agent).await?;
|
let runner = AppRunner::new(canvas, web_options, app_creator, text_agent).await?;
|
||||||
|
|
@ -83,10 +81,6 @@ impl WebRunner {
|
||||||
{
|
{
|
||||||
events::install_event_handlers(self)?;
|
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.
|
// The resize observer handles calling `request_animation_frame` to start the render loop.
|
||||||
events::install_resize_observer(self)?;
|
events::install_resize_observer(self)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub use accesskit_winit;
|
||||||
pub use egui;
|
pub use egui;
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
use egui::accesskit;
|
use egui::accesskit;
|
||||||
use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo};
|
use egui::{Pos2, Rect, Theme, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo};
|
||||||
pub use winit;
|
pub use winit;
|
||||||
|
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
|
|
@ -111,6 +111,7 @@ impl State {
|
||||||
viewport_id: ViewportId,
|
viewport_id: ViewportId,
|
||||||
display_target: &dyn HasDisplayHandle,
|
display_target: &dyn HasDisplayHandle,
|
||||||
native_pixels_per_point: Option<f32>,
|
native_pixels_per_point: Option<f32>,
|
||||||
|
theme: Option<winit::window::Theme>,
|
||||||
max_texture_side: Option<usize>,
|
max_texture_side: Option<usize>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::profile_function!();
|
crate::profile_function!();
|
||||||
|
|
@ -150,6 +151,7 @@ impl State {
|
||||||
.entry(ViewportId::ROOT)
|
.entry(ViewportId::ROOT)
|
||||||
.or_default()
|
.or_default()
|
||||||
.native_pixels_per_point = native_pixels_per_point;
|
.native_pixels_per_point = native_pixels_per_point;
|
||||||
|
slf.egui_input.system_theme = theme.map(to_egui_theme);
|
||||||
|
|
||||||
if let Some(max_texture_side) = max_texture_side {
|
if let Some(max_texture_side) = max_texture_side {
|
||||||
slf.set_max_texture_side(max_texture_side);
|
slf.set_max_texture_side(max_texture_side);
|
||||||
|
|
@ -403,6 +405,13 @@ impl State {
|
||||||
consumed: false,
|
consumed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
WindowEvent::ThemeChanged(winit_theme) => {
|
||||||
|
self.egui_input.system_theme = Some(to_egui_theme(*winit_theme));
|
||||||
|
EventResponse {
|
||||||
|
repaint: true,
|
||||||
|
consumed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
WindowEvent::HoveredFile(path) => {
|
WindowEvent::HoveredFile(path) => {
|
||||||
self.egui_input.hovered_files.push(egui::HoveredFile {
|
self.egui_input.hovered_files.push(egui::HoveredFile {
|
||||||
path: Some(path.clone()),
|
path: Some(path.clone()),
|
||||||
|
|
@ -462,7 +471,6 @@ impl State {
|
||||||
| WindowEvent::Occluded(_)
|
| WindowEvent::Occluded(_)
|
||||||
| WindowEvent::Resized(_)
|
| WindowEvent::Resized(_)
|
||||||
| WindowEvent::Moved(_)
|
| WindowEvent::Moved(_)
|
||||||
| WindowEvent::ThemeChanged(_)
|
|
||||||
| WindowEvent::TouchpadPressure { .. }
|
| WindowEvent::TouchpadPressure { .. }
|
||||||
| WindowEvent::CloseRequested => EventResponse {
|
| WindowEvent::CloseRequested => EventResponse {
|
||||||
repaint: true,
|
repaint: true,
|
||||||
|
|
@ -890,6 +898,13 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_egui_theme(theme: winit::window::Theme) -> Theme {
|
||||||
|
match theme {
|
||||||
|
winit::window::Theme::Dark => Theme::Dark,
|
||||||
|
winit::window::Theme::Light => Theme::Light,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
|
pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
|
||||||
let inner_pos_px = window.inner_position().ok()?;
|
let inner_pos_px = window.inner_position().ok()?;
|
||||||
let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);
|
let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use epaint::ColorImage;
|
use epaint::ColorImage;
|
||||||
|
|
||||||
use crate::{emath::*, Key, ViewportId, ViewportIdMap};
|
use crate::{emath::*, Key, Theme, ViewportId, ViewportIdMap};
|
||||||
|
|
||||||
/// What the integrations provides to egui at the start of each frame.
|
/// What the integrations provides to egui at the start of each frame.
|
||||||
///
|
///
|
||||||
|
|
@ -73,6 +73,11 @@ pub struct RawInput {
|
||||||
///
|
///
|
||||||
/// False when the user alt-tab away from the application, for instance.
|
/// False when the user alt-tab away from the application, for instance.
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
|
|
||||||
|
/// Does the OS use dark or light mode?
|
||||||
|
///
|
||||||
|
/// `None` means "don't know".
|
||||||
|
pub system_theme: Option<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RawInput {
|
impl Default for RawInput {
|
||||||
|
|
@ -89,6 +94,7 @@ impl Default for RawInput {
|
||||||
hovered_files: Default::default(),
|
hovered_files: Default::default(),
|
||||||
dropped_files: Default::default(),
|
dropped_files: Default::default(),
|
||||||
focused: true, // integrations opt into global focus tracking
|
focused: true, // integrations opt into global focus tracking
|
||||||
|
system_theme: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +123,7 @@ impl RawInput {
|
||||||
hovered_files: self.hovered_files.clone(),
|
hovered_files: self.hovered_files.clone(),
|
||||||
dropped_files: std::mem::take(&mut self.dropped_files),
|
dropped_files: std::mem::take(&mut self.dropped_files),
|
||||||
focused: self.focused,
|
focused: self.focused,
|
||||||
|
system_theme: self.system_theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,6 +141,7 @@ impl RawInput {
|
||||||
mut hovered_files,
|
mut hovered_files,
|
||||||
mut dropped_files,
|
mut dropped_files,
|
||||||
focused,
|
focused,
|
||||||
|
system_theme,
|
||||||
} = newer;
|
} = newer;
|
||||||
|
|
||||||
self.viewport_id = viewport_ids;
|
self.viewport_id = viewport_ids;
|
||||||
|
|
@ -147,6 +155,7 @@ impl RawInput {
|
||||||
self.hovered_files.append(&mut hovered_files);
|
self.hovered_files.append(&mut hovered_files);
|
||||||
self.dropped_files.append(&mut dropped_files);
|
self.dropped_files.append(&mut dropped_files);
|
||||||
self.focused = focused;
|
self.focused = focused;
|
||||||
|
self.system_theme = system_theme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,7 +198,7 @@ pub struct ViewportInfo {
|
||||||
/// This should always be set, if known.
|
/// This should always be set, if known.
|
||||||
///
|
///
|
||||||
/// On web this takes browser scaling into account,
|
/// On web this takes browser scaling into account,
|
||||||
/// and orresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript.
|
/// and corresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript.
|
||||||
pub native_pixels_per_point: Option<f32>,
|
pub native_pixels_per_point: Option<f32>,
|
||||||
|
|
||||||
/// Current monitor size in egui points.
|
/// Current monitor size in egui points.
|
||||||
|
|
@ -1044,6 +1053,7 @@ impl RawInput {
|
||||||
hovered_files,
|
hovered_files,
|
||||||
dropped_files,
|
dropped_files,
|
||||||
focused,
|
focused,
|
||||||
|
system_theme,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.label(format!("Active viwport: {viewport_id:?}"));
|
ui.label(format!("Active viwport: {viewport_id:?}"));
|
||||||
|
|
@ -1068,6 +1078,7 @@ impl RawInput {
|
||||||
ui.label(format!("hovered_files: {}", hovered_files.len()));
|
ui.label(format!("hovered_files: {}", hovered_files.len()));
|
||||||
ui.label(format!("dropped_files: {}", dropped_files.len()));
|
ui.label(format!("dropped_files: {}", dropped_files.len()));
|
||||||
ui.label(format!("focused: {focused}"));
|
ui.label(format!("focused: {focused}"));
|
||||||
|
ui.label(format!("system_theme: {system_theme:?}"));
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
ui.set_min_height(150.0);
|
ui.set_min_height(150.0);
|
||||||
ui.label(format!("events: {events:#?}"))
|
ui.label(format!("events: {events:#?}"))
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ pub use {
|
||||||
layers::{LayerId, Order},
|
layers::{LayerId, Order},
|
||||||
layout::*,
|
layout::*,
|
||||||
load::SizeHint,
|
load::SizeHint,
|
||||||
memory::{Memory, Options},
|
memory::{Memory, Options, Theme},
|
||||||
painter::Painter,
|
painter::Painter,
|
||||||
response::{InnerResponse, Response},
|
response::{InnerResponse, Response},
|
||||||
sense::Sense,
|
sense::Sense,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ use crate::{
|
||||||
ViewportId, ViewportIdMap, ViewportIdSet,
|
ViewportId, ViewportIdMap, ViewportIdSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod theme;
|
||||||
|
pub use theme::Theme;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// The data that egui persists between frames.
|
/// The data that egui persists between frames.
|
||||||
|
|
@ -169,6 +172,21 @@ pub struct Options {
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub(crate) style: std::sync::Arc<Style>,
|
pub(crate) style: std::sync::Arc<Style>,
|
||||||
|
|
||||||
|
/// Whether to update the visuals according to the system theme or not.
|
||||||
|
///
|
||||||
|
/// Default: `true`.
|
||||||
|
pub follow_system_theme: bool,
|
||||||
|
|
||||||
|
/// Which theme to use in case [`Self::follow_system_theme`] is set
|
||||||
|
/// and egui fails to detect the system theme.
|
||||||
|
///
|
||||||
|
/// Default: [`crate::Theme::Dark`].
|
||||||
|
pub fallback_theme: Theme,
|
||||||
|
|
||||||
|
/// Used to detect changes in system theme
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
|
system_theme: Option<Theme>,
|
||||||
|
|
||||||
/// Global zoom factor of the UI.
|
/// Global zoom factor of the UI.
|
||||||
///
|
///
|
||||||
/// This is used to calculate the `pixels_per_point`
|
/// This is used to calculate the `pixels_per_point`
|
||||||
|
|
@ -262,6 +280,9 @@ impl Default for Options {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
style: Default::default(),
|
style: Default::default(),
|
||||||
|
follow_system_theme: true,
|
||||||
|
fallback_theme: Theme::Dark,
|
||||||
|
system_theme: None,
|
||||||
zoom_factor: 1.0,
|
zoom_factor: 1.0,
|
||||||
zoom_with_keyboard: true,
|
zoom_with_keyboard: true,
|
||||||
tessellation_options: Default::default(),
|
tessellation_options: Default::default(),
|
||||||
|
|
@ -278,11 +299,35 @@ impl Default for Options {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Options {
|
||||||
|
pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput) {
|
||||||
|
if self.follow_system_theme {
|
||||||
|
let theme_from_visuals = Theme::from_dark_mode(self.style.visuals.dark_mode);
|
||||||
|
let current_system_theme = self.system_theme.unwrap_or(theme_from_visuals);
|
||||||
|
let new_system_theme = new_raw_input.system_theme.unwrap_or(self.fallback_theme);
|
||||||
|
|
||||||
|
// Only update the visuals if the system theme has changed.
|
||||||
|
// This allows users to change the visuals without them
|
||||||
|
// getting reset on the next frame.
|
||||||
|
if current_system_theme != new_system_theme || self.system_theme.is_none() {
|
||||||
|
self.system_theme = Some(new_system_theme);
|
||||||
|
if theme_from_visuals != new_system_theme {
|
||||||
|
let visuals = new_system_theme.default_visuals();
|
||||||
|
std::sync::Arc::make_mut(&mut self.style).visuals = visuals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
/// Show the options in the ui.
|
/// Show the options in the ui.
|
||||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||||
let Self {
|
let Self {
|
||||||
style, // covered above
|
style, // covered above
|
||||||
|
follow_system_theme: _,
|
||||||
|
fallback_theme: _,
|
||||||
|
system_theme: _,
|
||||||
zoom_factor: _, // TODO(emilk)
|
zoom_factor: _, // TODO(emilk)
|
||||||
zoom_with_keyboard,
|
zoom_with_keyboard,
|
||||||
tessellation_options,
|
tessellation_options,
|
||||||
|
|
@ -665,6 +710,8 @@ impl Memory {
|
||||||
|
|
||||||
// self.interactions is handled elsewhere
|
// self.interactions is handled elsewhere
|
||||||
|
|
||||||
|
self.options.begin_frame(new_raw_input);
|
||||||
|
|
||||||
self.focus
|
self.focus
|
||||||
.entry(self.viewport_id)
|
.entry(self.viewport_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/// 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 {
|
||||||
|
/// Default visuals for this theme.
|
||||||
|
pub fn default_visuals(self) -> crate::Visuals {
|
||||||
|
match self {
|
||||||
|
Self::Dark => crate::Visuals::dark(),
|
||||||
|
Self::Light => crate::Visuals::light(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Chooses between [`Self::Dark`] or [`Self::Light`] based on a boolean value.
|
||||||
|
pub fn from_dark_mode(dark_mode: bool) -> Self {
|
||||||
|
if dark_mode {
|
||||||
|
Self::Dark
|
||||||
|
} else {
|
||||||
|
Self::Light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -192,7 +192,8 @@ impl winit::application::ApplicationHandler<UserEvent> for GlowApp {
|
||||||
let gl = std::sync::Arc::new(gl);
|
let gl = std::sync::Arc::new(gl);
|
||||||
gl_window.window().set_visible(true);
|
gl_window.window().set_visible(true);
|
||||||
|
|
||||||
let egui_glow = egui_glow::EguiGlow::new(event_loop, gl.clone(), None, None, true);
|
let egui_glow =
|
||||||
|
egui_glow::EguiGlow::new(event_loop, &gl_window.window, gl.clone(), None, None, true);
|
||||||
|
|
||||||
let event_loop_proxy = egui::mutex::Mutex::new(self.proxy.clone());
|
let event_loop_proxy = egui::mutex::Mutex::new(self.proxy.clone());
|
||||||
egui_glow
|
egui_glow
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ impl EguiGlow {
|
||||||
/// For automatic shader version detection set `shader_version` to `None`.
|
/// For automatic shader version detection set `shader_version` to `None`.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||||
|
window: &winit::window::Window,
|
||||||
gl: std::sync::Arc<glow::Context>,
|
gl: std::sync::Arc<glow::Context>,
|
||||||
shader_version: Option<ShaderVersion>,
|
shader_version: Option<ShaderVersion>,
|
||||||
native_pixels_per_point: Option<f32>,
|
native_pixels_per_point: Option<f32>,
|
||||||
|
|
@ -42,6 +43,7 @@ impl EguiGlow {
|
||||||
ViewportId::ROOT,
|
ViewportId::ROOT,
|
||||||
event_loop,
|
event_loop,
|
||||||
native_pixels_per_point,
|
native_pixels_per_point,
|
||||||
|
window.theme(),
|
||||||
Some(painter.max_texture_side()),
|
Some(painter.max_texture_side()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue