Skip to content

Commit

Permalink
Add separate light and dark styles
Browse files Browse the repository at this point in the history
  • Loading branch information
bash committed Aug 7, 2024
1 parent 62164f6 commit 8fae839
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 67 deletions.
76 changes: 59 additions & 17 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1619,7 +1619,7 @@ impl Context {
self.options(|opt| opt.style().clone())
}

/// Mutate the [`Style`] used by all subsequent windows, panels etc.
/// Mutate the [`Style`]s used by all subsequent windows, panels etc in both dark and light mode.
///
/// Example:
/// ```
Expand All @@ -1628,30 +1628,65 @@ impl Context {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
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)));
pub fn style_mut(&self, mut mutate_style: impl FnMut(&mut Style)) {
self.options_mut(|opt| {
mutate_style(std::sync::Arc::make_mut(&mut opt.dark_style));
mutate_style(std::sync::Arc::make_mut(&mut opt.light_style));
});
}

/// The [`Style`] used by all subsequent windows, panels etc in dark mode.
pub fn dark_style(&self) -> Arc<Style> {
self.options(|opt| opt.dark_style.clone())
}

/// The [`Style`] used by all new windows, panels etc.
/// Mutate the [`Style`] used by all subsequent windows, panels etc in dark mode.
///
/// You can also change this using [`Self::style_mut`]
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.dark_style_mut(|style| {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
pub fn dark_style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
self.options_mut(|opt| mutate_style(std::sync::Arc::make_mut(&mut opt.dark_style)));
}

/// The [`Style`] used by all new windows, panels etc. in dark mode.
///
/// You can also change this using [`Self::dark_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());
pub fn set_dark_style(&self, style: impl Into<std::sync::Arc<crate::Style>>) {
self.options_mut(|opt| opt.dark_style = style.into());
}

/// The [`Visuals`] used by all subsequent windows, panels etc.
///
/// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`].
/// The [`Style`] used by all subsequent windows, panels etc in light mode.
pub fn light_style(&self) -> Arc<Style> {
self.options(|opt| opt.light_style.clone())
}

/// Mutate the [`Style`] used by all subsequent windows, panels etc in light mode.
///
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.set_visuals(egui::Visuals::light()); // Switch to light mode
/// ctx.light_style_mut(|style| {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
pub fn set_visuals(&self, visuals: crate::Visuals) {
self.options_mut(|opt| std::sync::Arc::make_mut(&mut opt.style).visuals = visuals);
pub fn light_style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
self.options_mut(|opt| mutate_style(std::sync::Arc::make_mut(&mut opt.light_style)));
}

/// The [`Style`] used by all new windows, panels etc. in light mode.
///
/// You can also change this using [`Self::dark_style_mut`]
///
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
pub fn set_light_style(&self, style: impl Into<std::sync::Arc<crate::Style>>) {
self.options_mut(|opt| opt.light_style = style.into());
}

/// The number of physical pixels for each logical point.
Expand Down Expand Up @@ -2857,11 +2892,18 @@ 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`] used in dark mode.
pub fn dark_style_ui(&self, ui: &mut Ui) {
let mut style: Style = (*self.dark_style()).clone();
style.ui(ui);
self.set_dark_style(style);
}

/// Edit the [`Style`] used in light mode.
pub fn light_style_ui(&self, ui: &mut Ui) {
let mut style: Style = (*self.light_style()).clone();
style.ui(ui);
self.set_style(style);
self.set_light_style(style);
}
}

Expand Down
46 changes: 23 additions & 23 deletions crates/egui/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,12 @@ 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))]
pub(crate) style: std::sync::Arc<Style>,
pub(crate) dark_style: std::sync::Arc<Style>,

/// The default style for new [`Ui`](crate::Ui):s in light mode.
pub(crate) light_style: std::sync::Arc<Style>,

/// Whether to update the visuals according to the system theme or not.
///
Expand Down Expand Up @@ -279,7 +282,8 @@ impl Default for Options {
};

Self {
style: Default::default(),
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,
Expand All @@ -301,22 +305,7 @@ impl Default for Options {

impl Options {
pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput) {
if self.theme_preference == ThemePreference::System {
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).
Expand All @@ -329,16 +318,20 @@ impl Options {
}

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

impl Options {
/// Show the options in the ui.
pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self {
style, // covered above
theme_preference: _,
dark_style, // covered above
light_style,
theme_preference,
fallback_theme: _,
system_theme: _,
zoom_factor: _, // TODO(emilk)
Expand Down Expand Up @@ -378,7 +371,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
40 changes: 40 additions & 0 deletions crates/egui/src/memory/theme.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::Button;

/// Dark or Light theme.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand Down Expand Up @@ -36,6 +38,33 @@ impl Theme {
}
}

impl Theme {
/// Show small toggle-button for light and dark mode.
/// This is not the best design as it doesn't allow switching back to "follow system".
#[must_use]
pub(crate) fn small_toggle_button(self, ui: &mut crate::Ui) -> Option<Self> {
#![allow(clippy::collapsible_else_if)]
if self == Theme::Dark {
if ui
.add(Button::new("☀").frame(false))
.on_hover_text("Switch to light mode")
.clicked()
{
return Some(Theme::Light);
}
} else {
if ui
.add(Button::new("🌙").frame(false))
.on_hover_text("Switch to dark mode")
.clicked()
{
return Some(Theme::Dark);
}
}
None
}
}

/// The user's theme preference.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand All @@ -58,3 +87,14 @@ impl From<Theme> for ThemePreference {
}
}
}

impl ThemePreference {
/// Show radio-buttons to switch between light mode, dark mode and following the system theme.
pub fn radio_buttons(&mut self, ui: &mut crate::Ui) {
ui.horizontal(|ui| {
ui.selectable_value(self, ThemePreference::Light, "☀ Light");
ui.selectable_value(self, ThemePreference::Dark, "🌙 Dark");
ui.selectable_value(self, ThemePreference::System, "System");
});
}
}
10 changes: 0 additions & 10 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1514,8 +1514,6 @@ impl Style {
scroll_animation,
} = self;

visuals.light_dark_radio_buttons(ui);

crate::Grid::new("_options").show(ui, |ui| {
ui.label("Override font id");
ui.vertical(|ui| {
Expand Down Expand Up @@ -1909,14 +1907,6 @@ impl WidgetVisuals {
}

impl Visuals {
/// Show radio-buttons to switch between light and dark mode.
pub fn light_dark_radio_buttons(&mut self, ui: &mut crate::Ui) {
ui.horizontal(|ui| {
ui.selectable_value(self, Self::light(), "☀ Light");
ui.selectable_value(self, Self::dark(), "🌙 Dark");
});
}

/// Show small toggle-button for light and dark mode.
#[must_use]
pub fn light_dark_small_toggle_button(&self, ui: &mut crate::Ui) -> Option<Self> {
Expand Down
12 changes: 5 additions & 7 deletions crates/egui/src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,14 @@ pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) {

/// Show a small button to switch to/from dark/light mode (globally).
pub fn global_dark_light_mode_switch(ui: &mut Ui) {
let style: crate::Style = (*ui.ctx().style()).clone();
let new_visuals = style.visuals.light_dark_small_toggle_button(ui);
if let Some(visuals) = new_visuals {
ui.ctx().set_visuals(visuals);
if let Some(new_theme) = ui.ctx().theme().small_toggle_button(ui) {
ui.ctx().set_theme(new_theme);
}
}

/// Show larger buttons for switching between light and dark mode (globally).
pub fn global_dark_light_mode_buttons(ui: &mut Ui) {
let mut visuals = ui.ctx().style().visuals.clone();
visuals.light_dark_radio_buttons(ui);
ui.ctx().set_visuals(visuals);
let mut theme_preference = ui.ctx().options(|opt| opt.theme_preference);
theme_preference.radio_buttons(ui);
ui.ctx().set_theme(theme_preference);
}
7 changes: 3 additions & 4 deletions crates/egui_demo_lib/src/demo/scrolling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ impl ScrollAppearance {
visibility,
} = self;

let mut style: Style = (*ui.ctx().style()).clone();
let mut scroll = ui.ctx().style().spacing.scroll;

style.spacing.scroll.ui(ui);
scroll.ui(ui);

ui.add_space(8.0);

Expand All @@ -132,8 +132,7 @@ impl ScrollAppearance {

ui.add_space(8.0);

ui.ctx().set_style(style.clone());
ui.set_style(style);
ui.ctx().style_mut(|s| s.spacing.scroll = scroll);

ui.separator();

Expand Down
6 changes: 3 additions & 3 deletions examples/custom_font_style/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use eframe::egui;
use egui::{FontFamily, FontId, RichText, TextStyle};
use std::collections::BTreeMap;

fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
Expand All @@ -28,8 +29,7 @@ fn heading3() -> TextStyle {
fn configure_text_styles(ctx: &egui::Context) {
use FontFamily::{Monospace, Proportional};

let mut style = (*ctx.style()).clone();
style.text_styles = [
let text_styles: BTreeMap<TextStyle, FontId> = [
(TextStyle::Heading, FontId::new(25.0, Proportional)),
(heading2(), FontId::new(22.0, Proportional)),
(heading3(), FontId::new(19.0, Proportional)),
Expand All @@ -39,7 +39,7 @@ fn configure_text_styles(ctx: &egui::Context) {
(TextStyle::Small, FontId::new(8.0, Proportional)),
]
.into();
ctx.set_style(style);
ctx.style_mut(move |style| style.text_styles = text_styles.clone());
}

fn content(ui: &mut egui::Ui) {
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_keypad/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn main() -> eframe::Result {
options,
Box::new(|cc| {
// Use the dark theme
cc.egui_ctx.set_visuals(egui::Visuals::dark());
cc.egui_ctx.set_theme(egui::Theme::Dark);
// This gives us image support:
egui_extras::install_image_loaders(&cc.egui_ctx);

Expand Down
4 changes: 2 additions & 2 deletions examples/screenshot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ impl eframe::App for MyApp {
.add(egui::Label::new("hover me!").sense(egui::Sense::hover()))
.hovered()
{
ctx.set_visuals(egui::Visuals::dark());
ctx.set_theme(egui::Theme::Dark);
} else {
ctx.set_visuals(egui::Visuals::light());
ctx.set_theme(egui::Theme::Light);
};
ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot);
} else if ui.button("take screenshot!").clicked() {
Expand Down

0 comments on commit 8fae839

Please sign in to comment.