Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make light & dark visuals customizable when following the system theme #4744

Merged
merged 20 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
121 changes: 101 additions & 20 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
layers::GraphicLayers,
load,
load::{Bytes, Loaders, SizedTexture},
memory::Options,
memory::{Options, Theme},
menu,
os::OperatingSystem,
output::FullOutput,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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());
}
});
Expand Down Expand Up @@ -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<Theme> {
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<crate::ThemePreference>) {
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<Style> {
self.options(|opt| opt.style.clone())
self.options(|opt| opt.style().clone())
}

/// Mutate the [`Style`] used by all subsequent windows, panels etc.
/// Mutate the currently active [`Style`] used by all subsequent windows, panels etc.
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
///
/// Example:
/// ```
Expand All @@ -1627,16 +1652,72 @@ impl Context {
/// });
/// ```
pub fn style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
self.options_mut(|opt| mutate_style(std::sync::Arc::make_mut(&mut opt.style)));
self.options_mut(|opt| mutate_style(Arc::make_mut(opt.style_mut())));
}

/// The [`Style`] used by all new windows, panels etc.
/// The currently active [`Style`] used by all new windows, panels etc.
///
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
///
/// You can also change this using [`Self::style_mut`]
/// You can also change this using [`Self::style_mut`].
///
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
pub fn set_style(&self, style: impl Into<Arc<Style>>) {
self.options_mut(|opt| opt.style = style.into());
self.options_mut(|opt| *opt.style_mut() = style.into());
}

/// Mutate the [`Style`]s used by all subsequent windows, panels etc. in both dark and light mode.
///
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.all_styles_mut(|style| {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
pub fn all_styles_mut(&self, mut mutate_style: impl FnMut(&mut Style)) {
self.options_mut(|opt| {
mutate_style(Arc::make_mut(&mut opt.dark_style));
mutate_style(Arc::make_mut(&mut opt.light_style));
});
}

/// The [`Style`] used by all subsequent windows, panels etc.
pub fn style_of(&self, theme: Theme) -> Arc<Style> {
self.options(|opt| match theme {
Theme::Dark => opt.dark_style.clone(),
Theme::Light => opt.light_style.clone(),
})
}

/// Mutate the [`Style`] used by all subsequent windows, panels etc.
///
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.style_mut_of(egui::Theme::Dark, |style| {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
pub fn style_mut_of(&self, theme: Theme, mutate_style: impl FnOnce(&mut Style)) {
self.options_mut(|opt| match theme {
Theme::Dark => mutate_style(Arc::make_mut(&mut opt.dark_style)),
Theme::Light => mutate_style(Arc::make_mut(&mut opt.light_style)),
});
}

/// The [`Style`] used by all new windows, panels etc.
/// Use [`Self::set_theme`] to choose between dark and light mode.
///
/// You can also change this using [`Self::style_mut_of`].
///
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
pub fn set_style_of(&self, theme: Theme, style: impl Into<Arc<Style>>) {
let style = style.into();
self.options_mut(|opt| match theme {
Theme::Dark => opt.dark_style = style,
Theme::Light => opt.light_style = style,
});
}

/// The [`crate::Visuals`] used by all subsequent windows, panels etc.
Expand All @@ -1646,10 +1727,10 @@ impl Context {
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.set_visuals(egui::Visuals::light()); // Switch to light mode
/// ctx.set_visuals_of(egui::Theme::Dark, egui::Visuals { panel_fill: egui::Color32::RED, ..Default::default() });
/// ```
pub fn set_visuals(&self, visuals: crate::Visuals) {
self.options_mut(|opt| std::sync::Arc::make_mut(&mut opt.style).visuals = visuals);
pub fn set_visuals_of(&self, theme: Theme, visuals: crate::Visuals) {
self.style_mut_of(theme, |style| style.visuals = visuals);
}

/// The number of physical pixels for each logical point.
Expand Down Expand Up @@ -2481,13 +2562,13 @@ impl Context {
/// Whether or not to debug widget layout on hover.
#[cfg(debug_assertions)]
pub fn debug_on_hover(&self) -> bool {
self.options(|opt| opt.style.debug.debug_on_hover)
self.options(|opt| opt.style().debug.debug_on_hover)
}

/// Turn on/off whether or not to debug widget layout on hover.
#[cfg(debug_assertions)]
pub fn set_debug_on_hover(&self, debug_on_hover: bool) {
self.style_mut(|style| style.debug.debug_on_hover = debug_on_hover);
self.all_styles_mut(|style| style.debug.debug_on_hover = debug_on_hover);
}
}

Expand Down Expand Up @@ -2871,11 +2952,11 @@ impl Context {
}

impl Context {
/// Edit the active [`Style`].
pub fn style_ui(&self, ui: &mut Ui) {
let mut style: Style = (*self.style()).clone();
/// Edit the [`Style`].
pub fn style_ui(&self, ui: &mut Ui, theme: Theme) {
let mut style: Style = (*self.style_of(theme)).clone();
style.ui(ui);
self.set_style(style);
self.set_style_of(theme, style);
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ pub use self::{
layers::{LayerId, Order},
layout::*,
load::SizeHint,
memory::{Memory, Options, Theme},
memory::{Memory, Options, Theme, ThemePreference},
painter::Painter,
response::{InnerResponse, Response},
sense::Sense,
Expand Down
80 changes: 51 additions & 29 deletions crates/egui/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
};

mod theme;
pub use theme::Theme;
pub use theme::{Theme, ThemePreference};

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

Expand Down Expand Up @@ -168,24 +168,29 @@ impl FocusDirection {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Options {
/// The default style for new [`Ui`](crate::Ui):s.
/// The default style for new [`Ui`](crate::Ui):s in dark mode.
#[cfg_attr(feature = "serde", serde(skip))]
bash marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) style: std::sync::Arc<Style>,
pub(crate) dark_style: std::sync::Arc<Style>,
bash marked this conversation as resolved.
Show resolved Hide resolved

/// Whether to update the visuals according to the system theme or not.
/// The default style for new [`Ui`](crate::Ui):s in light mode.
pub(crate) light_style: std::sync::Arc<Style>,
bash marked this conversation as resolved.
Show resolved Hide resolved

/// A preference for how to select between dark and light [`crate::Context::style`]
/// as the active style used by all subsequent windows, panels etc.
///
/// Default: `true`.
pub follow_system_theme: bool,
/// Default: `ThemePreference::System`.
pub theme_preference: ThemePreference,

/// Which theme to use in case [`Self::follow_system_theme`] is set
/// Which theme to use in case [`Self::theme_preference`] is [`ThemePreference::System`]
/// and egui fails to detect the system theme.
///
/// Default: [`crate::Theme::Dark`].
pub fallback_theme: Theme,

/// Used to detect changes in system theme
/// The current system theme, used to choose between
/// dark and light style in case [`Self::theme_preference`] is [`ThemePreference::System`].
#[cfg_attr(feature = "serde", serde(skip))]
system_theme: Option<Theme>,
pub(crate) system_theme: Option<Theme>,

/// Global zoom factor of the UI.
///
Expand Down Expand Up @@ -279,8 +284,9 @@ impl Default for Options {
};

Self {
style: Default::default(),
follow_system_theme: true,
dark_style: std::sync::Arc::new(Theme::Dark.default_style()),
light_style: std::sync::Arc::new(Theme::Light.default_style()),
theme_preference: ThemePreference::System,
fallback_theme: Theme::Dark,
system_theme: None,
zoom_factor: 1.0,
Expand All @@ -301,21 +307,29 @@ 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;
}
}
self.system_theme = new_raw_input.system_theme;
}

/// The currently active theme (may depend on the system theme).
pub(crate) fn theme(&self) -> Theme {
match self.theme_preference {
ThemePreference::Dark => Theme::Dark,
ThemePreference::Light => Theme::Light,
ThemePreference::System => self.system_theme.unwrap_or(self.fallback_theme),
}
}

pub(crate) fn style(&self) -> &std::sync::Arc<Style> {
match self.theme() {
Theme::Dark => &self.dark_style,
Theme::Light => &self.light_style,
}
}

pub(crate) fn style_mut(&mut self) -> &mut std::sync::Arc<Style> {
match self.theme() {
Theme::Dark => &mut self.dark_style,
Theme::Light => &mut self.light_style,
}
}
}
Expand All @@ -324,8 +338,9 @@ impl Options {
/// Show the options in the ui.
pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self {
style, // covered above
follow_system_theme: _,
dark_style, // covered above
light_style,
theme_preference,
fallback_theme: _,
system_theme: _,
zoom_factor: _, // TODO(emilk)
Expand Down Expand Up @@ -365,7 +380,14 @@ impl Options {
CollapsingHeader::new("🎑 Style")
.default_open(true)
.show(ui, |ui| {
std::sync::Arc::make_mut(style).ui(ui);
theme_preference.radio_buttons(ui);

CollapsingHeader::new("Dark")
.default_open(true)
.show(ui, |ui| std::sync::Arc::make_mut(dark_style).ui(ui));
CollapsingHeader::new("Light")
.default_open(true)
.show(ui, |ui| std::sync::Arc::make_mut(light_style).ui(ui));
});

CollapsingHeader::new("✒ Painting")
Expand Down
Loading
Loading