From bbecc350953c43197637f4b561f8b715b24cab9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Wed, 11 Sep 2024 17:52:53 +0200 Subject: [PATCH] Make Light & Dark Visuals Customizable When Following The System Theme (#4744) * Closes * [x] I have followed the instructions in the PR template --- Unfortunately, this PR contains a bunch of breaking changes because `Context` no longer has one style, but two. I could try to add some of the methods back if that's desired. The most subtle change is probably that `style_mut` mutates both the dark and the light style (which from the usage in egui itself felt like the right choice but might be surprising to users). I decided to deviate a bit from the data structure suggested in the linked issue. Instead of this: ```rust pub theme: Theme, // Dark or Light pub follow_system_theme: bool, // Change [`Self::theme`] based on `RawInput::system_theme`? ``` I decided to add a `ThemePreference` enum and track the current system theme separately. This has a couple of benefits: * The user's theme choice is not magically overwritten on the next frame. * A widget for changing the theme preference only needs to know the `ThemePreference` and not two values. * Persisting the `theme_preference` is fine (as opposed to persisting the `theme` field which may actually be the system theme). The `small_toggle_button` currently only toggles between dark and light (so you can never get back to following the system). I think it's easy to improve on this in a follow-up PR :) I made the function `pub(crate)` for now because it should eventually be a method on `ThemePreference`, not `Theme`. To showcase the new capabilities I added a new example that uses different "accent" colors in dark and light mode: A screenshot of egui's widget gallery demo in dark mode
using a purple accent color instead of the default blue accent A screenshot of egui's widget gallery demo in light
mode using a green accent color instead of the default blue accent --------- Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 11 ++ crates/eframe/src/epi.rs | 2 +- crates/egui/src/context.rs | 121 +++++++++++++++--- crates/egui/src/lib.rs | 2 +- crates/egui/src/memory/mod.rs | 81 +++++++----- crates/egui/src/memory/theme.rs | 71 ++++++++++ crates/egui/src/style.rs | 51 ++------ crates/egui/src/ui.rs | 6 +- crates/egui/src/widgets/color_picker.rs | 2 +- crates/egui/src/widgets/mod.rs | 26 ++-- crates/egui_demo_app/src/wrap_app.rs | 2 +- crates/egui_demo_lib/src/demo/scrolling.rs | 9 +- crates/egui_extras/src/syntax_highlighting.rs | 4 +- examples/custom_font_style/src/main.rs | 6 +- examples/custom_keypad/src/main.rs | 2 +- examples/custom_style/Cargo.toml | 23 ++++ examples/custom_style/README.md | 7 + examples/custom_style/screenshot.png | Bin 0 -> 127741 bytes examples/custom_style/src/main.rs | 69 ++++++++++ examples/custom_window_frame/src/main.rs | 2 +- examples/screenshot/src/main.rs | 4 +- tests/test_ui_stack/src/main.rs | 2 +- 22 files changed, 381 insertions(+), 122 deletions(-) create mode 100644 examples/custom_style/Cargo.toml create mode 100644 examples/custom_style/README.md create mode 100644 examples/custom_style/screenshot.png create mode 100644 examples/custom_style/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index f11f4d03164..d787f6aba03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1052,6 +1052,17 @@ dependencies = [ "env_logger", ] +[[package]] +name = "custom_style" +version = "0.1.0" +dependencies = [ + "eframe", + "egui_demo_lib", + "egui_extras", + "env_logger", + "image", +] + [[package]] name = "custom_window_frame" version = "0.1.0" diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 0fec5a070fd..f567507c4da 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -54,7 +54,7 @@ pub struct CreationContext<'s> { /// The egui Context. /// /// You can use this to customize the look of egui, e.g to call [`egui::Context::set_fonts`], - /// [`egui::Context::set_visuals`] etc. + /// [`egui::Context::set_visuals_of`] etc. pub egui_ctx: egui::Context, /// Information about the surrounding environment. diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 81f0c0a9624..dda34db920f 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -21,7 +21,7 @@ use crate::{ layers::GraphicLayers, load, load::{Bytes, Loaders, SizedTexture}, - memory::Options, + memory::{Options, Theme}, menu, os::OperatingSystem, output::FullOutput, @@ -487,7 +487,7 @@ impl ContextImpl { }); viewport.hits = if let Some(pos) = viewport.input.pointer.interact_pos() { - let interact_radius = self.memory.options.style.interaction.interact_radius; + let interact_radius = self.memory.options.style().interaction.interact_radius; crate::hit_test::hit_test( &viewport.prev_frame.widgets, @@ -583,7 +583,7 @@ impl ContextImpl { crate::profile_scope!("preload_font_glyphs"); // Preload the most common characters for the most common fonts. // This is not very important to do, but may save a few GPU operations. - for font_id in self.memory.options.style.text_styles.values() { + for font_id in self.memory.options.style().text_styles.values() { fonts.lock().fonts.font(font_id).preload_common_characters(); } } @@ -1245,7 +1245,7 @@ impl Context { pub fn register_widget_info(&self, id: Id, make_info: impl Fn() -> crate::WidgetInfo) { #[cfg(debug_assertions)] self.write(|ctx| { - if ctx.memory.options.style.debug.show_interactive_widgets { + if ctx.memory.options.style().debug.show_interactive_widgets { ctx.viewport().this_frame.widgets.set_info(id, make_info()); } }); @@ -1612,12 +1612,37 @@ impl Context { } } - /// The [`Style`] used by all subsequent windows, panels etc. + /// Does the OS use dark or light mode? + /// This is used when the theme preference is set to [`crate::ThemePreference::System`]. + pub fn system_theme(&self) -> Option { + self.memory(|mem| mem.options.system_theme) + } + + /// The [`Theme`] used to select the appropriate [`Style`] (dark or light) + /// used by all subsequent windows, panels etc. + pub fn theme(&self) -> Theme { + self.options(|opt| opt.theme()) + } + + /// The [`Theme`] used to select between dark and light [`Self::style`] + /// as the active style used by all subsequent windows, panels etc. + /// + /// Example: + /// ``` + /// # let mut ctx = egui::Context::default(); + /// ctx.set_theme(egui::Theme::Light); // Switch to light mode + /// ``` + pub fn set_theme(&self, theme_preference: impl Into) { + self.options_mut(|opt| opt.theme_preference = theme_preference.into()); + } + + /// The currently active [`Style`] used by all subsequent windows, panels etc. pub fn style(&self) -> Arc