Make Light & Dark Visuals Customizable When Following The System Theme (#4744)

* Closes <https://github.com/emilk/egui/issues/4490>
* [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:

<img
src="https://github.com/user-attachments/assets/0bf728c6-2720-47b0-a908-18bd250d15a6"
width="250" alt="A screenshot of egui's widget gallery demo in dark mode
using a purple accent color instead of the default blue accent">

<img
src="https://github.com/user-attachments/assets/e816b380-3e59-4f11-b841-8c20285988d6"
width="250" alt="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 <emil.ernerfeldt@gmail.com>
This commit is contained in:
Tau Gärtli 2024-09-11 17:52:53 +02:00 committed by GitHub
parent f4697bc007
commit b5627c7d40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 382 additions and 123 deletions

View File

@ -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"

View File

@ -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.

View File

@ -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.
pub fn style(&self) -> Arc<Style> {
self.options(|opt| opt.style.clone())
/// 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)
}
/// Mutate the [`Style`] used by all subsequent windows, panels etc.
/// 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())
}
/// 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:
/// ```
@ -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.
///
/// You can also change this using [`Self::style_mut`]
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
///
/// 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.
@ -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.
@ -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);
}
}
@ -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);
}
}

View File

@ -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,

View File

@ -9,7 +9,7 @@ use crate::{
};
mod theme;
pub use theme::Theme;
pub use theme::{Theme, ThemePreference};
// ----------------------------------------------------------------------------
@ -168,24 +168,30 @@ 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 dark_style: std::sync::Arc<Style>,
/// Whether to update the visuals according to the system theme or not.
/// The default style for new [`Ui`](crate::Ui):s in light mode.
#[cfg_attr(feature = "serde", serde(skip))]
pub light_style: std::sync::Arc<Style>,
/// 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.
///
@ -282,8 +288,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,
@ -305,21 +312,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);
self.system_theme = new_raw_input.system_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;
}
}
/// 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,
}
}
}
@ -328,8 +343,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)
@ -370,7 +386,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")

View File

@ -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))]
@ -18,6 +20,14 @@ impl Theme {
}
}
/// Default style for this theme.
pub fn default_style(self) -> crate::Style {
crate::Style {
visuals: self.default_visuals(),
..Default::default()
}
}
/// Chooses between [`Self::Dark`] or [`Self::Light`] based on a boolean value.
pub fn from_dark_mode(dark_mode: bool) -> Self {
if dark_mode {
@ -27,3 +37,64 @@ 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 == Self::Dark {
if ui
.add(Button::new("").frame(false))
.on_hover_text("Switch to light mode")
.clicked()
{
return Some(Self::Light);
}
} else {
if ui
.add(Button::new("🌙").frame(false))
.on_hover_text("Switch to dark mode")
.clicked()
{
return Some(Self::Dark);
}
}
None
}
}
/// The user's theme preference.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ThemePreference {
/// Dark mode: light text on a dark background.
Dark,
/// Light mode: dark text on a light background.
Light,
/// Follow the system's theme preference.
System,
}
impl From<Theme> for ThemePreference {
fn from(value: Theme) -> Self {
match value {
Theme::Dark => Self::Dark,
Theme::Light => Self::Light,
}
}
}
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, Self::Light, "☀ Light");
ui.selectable_value(self, Self::Dark, "🌙 Dark");
ui.selectable_value(self, Self::System, "System");
});
}
}

View File

@ -176,7 +176,8 @@ impl From<TextStyle> for FontSelection {
/// Specifies the look and feel of egui.
///
/// You can change the visuals of a [`Ui`] with [`Ui::style_mut`]
/// and of everything with [`crate::Context::set_style`].
/// and of everything with [`crate::Context::set_style_of`].
/// To choose between dark and light style, use [`crate::Context::set_theme`].
///
/// If you want to change fonts, use [`crate::Context::set_fonts`] instead.
#[derive(Clone, Debug, PartialEq)]
@ -206,12 +207,10 @@ pub struct Style {
/// use egui::FontFamily::Proportional;
/// use egui::FontId;
/// use egui::TextStyle::*;
///
/// // Get current context style
/// let mut style = (*ctx.style()).clone();
/// use std::collections::BTreeMap;
///
/// // Redefine text_styles
/// style.text_styles = [
/// let text_styles: BTreeMap<_, _> = [
/// (Heading, FontId::new(30.0, Proportional)),
/// (Name("Heading2".into()), FontId::new(25.0, Proportional)),
/// (Name("Context".into()), FontId::new(23.0, Proportional)),
@ -221,8 +220,8 @@ pub struct Style {
/// (Small, FontId::new(10.0, Proportional)),
/// ].into();
///
/// // Mutate global style with above changes
/// ctx.set_style(style);
/// // Mutate global styles with new text styles
/// ctx.all_styles_mut(move |style| style.text_styles = text_styles.clone());
/// ```
pub text_styles: BTreeMap<TextStyle, FontId>,
@ -855,7 +854,7 @@ impl Default for TextCursorStyle {
/// Controls the visual style (colors etc) of egui.
///
/// You can change the visuals of a [`Ui`] with [`Ui::visuals_mut`]
/// and of everything with [`crate::Context::set_visuals`].
/// and of everything with [`crate::Context::set_visuals_of`].
///
/// If you want to change fonts, use [`crate::Context::set_fonts`] instead.
#[derive(Clone, Debug, PartialEq)]
@ -1492,7 +1491,7 @@ impl Default for Widgets {
// ----------------------------------------------------------------------------
use crate::{
widgets::{reset_button, Button, DragValue, Slider, Widget},
widgets::{reset_button, DragValue, Slider, Widget},
Ui,
};
@ -1519,8 +1518,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| {
@ -1931,38 +1928,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> {
#![allow(clippy::collapsible_else_if)]
if self.dark_mode {
if ui
.add(Button::new("").frame(false))
.on_hover_text("Switch to light mode")
.clicked()
{
return Some(Self::light());
}
} else {
if ui
.add(Button::new("🌙").frame(false))
.on_hover_text("Switch to dark mode")
.clicked()
{
return Some(Self::dark());
}
}
None
}
pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self {
dark_mode: _,

View File

@ -320,7 +320,7 @@ impl Ui {
/// Mutably borrow internal [`Style`].
/// Changes apply to this [`Ui`] and its subsequent children.
///
/// To set the style of all [`Ui`]:s, use [`Context::set_style`].
/// To set the style of all [`Ui`]:s, use [`Context::set_style_of`].
///
/// Example:
/// ```
@ -334,7 +334,7 @@ impl Ui {
/// Changes apply to this [`Ui`] and its subsequent children.
///
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals`].
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals_of`].
pub fn set_style(&mut self, style: impl Into<Arc<Style>>) {
self.style = style.into();
}
@ -374,7 +374,7 @@ impl Ui {
/// Mutably borrow internal `visuals`.
/// Changes apply to this [`Ui`] and its subsequent children.
///
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals`].
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals_of`].
///
/// Example:
/// ```

View File

@ -369,7 +369,7 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsvag: &mut HsvaGamma, alpha: Alpha) {
fn input_type_button_ui(ui: &mut Ui) {
let mut input_type = ui.ctx().style().visuals.numeric_color_space;
if input_type.toggle_button_ui(ui).changed() {
ui.ctx().style_mut(|s| {
ui.ctx().all_styles_mut(|s| {
s.visuals.numeric_color_space = input_type;
});
}

View File

@ -133,17 +133,27 @@ 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);
pub fn global_theme_preference_switch(ui: &mut Ui) {
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);
pub fn global_theme_preference_buttons(ui: &mut Ui) {
let mut theme_preference = ui.ctx().options(|opt| opt.theme_preference);
theme_preference.radio_buttons(ui);
ui.ctx().set_theme(theme_preference);
}
/// Show a small button to switch to/from dark/light mode (globally).
#[deprecated = "Use global_theme_preference_switch instead"]
pub fn global_dark_light_mode_switch(ui: &mut Ui) {
global_theme_preference_switch(ui);
}
/// Show larger buttons for switching between light and dark mode (globally).
#[deprecated = "Use global_theme_preference_buttons instead"]
pub fn global_dark_light_mode_buttons(ui: &mut Ui) {
global_theme_preference_buttons(ui);
}

View File

@ -385,7 +385,7 @@ impl WrapApp {
}
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cmd: &mut Command) {
egui::widgets::global_dark_light_mode_switch(ui);
egui::widgets::global_theme_preference_switch(ui);
ui.separator();

View File

@ -1,6 +1,6 @@
use egui::{
pos2, scroll_area::ScrollBarVisibility, Align, Align2, Color32, DragValue, NumExt, Rect,
ScrollArea, Sense, Slider, Style, TextStyle, TextWrapMode, Ui, Vec2, Widget,
ScrollArea, Sense, Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget,
};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
@ -119,9 +119,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);
@ -135,8 +135,7 @@ impl ScrollAppearance {
ui.add_space(8.0);
ui.ctx().set_style(style.clone());
ui.set_style(style);
ui.ctx().all_styles_mut(|s| s.spacing.scroll = scroll);
ui.separator();

View File

@ -272,7 +272,7 @@ impl CodeTheme {
/// Show UI for changing the color theme.
pub fn ui(&mut self, ui: &mut egui::Ui) {
egui::widgets::global_dark_light_mode_buttons(ui);
egui::widgets::global_theme_preference_buttons(ui);
for theme in SyntectTheme::all() {
if theme.is_dark() == self.dark_mode {
@ -335,7 +335,7 @@ impl CodeTheme {
ui.vertical(|ui| {
ui.set_width(150.0);
egui::widgets::global_dark_light_mode_buttons(ui);
egui::widgets::global_theme_preference_buttons(ui);
ui.add_space(8.0);
ui.separator();

View File

@ -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`).
@ -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)),
@ -39,7 +39,7 @@ fn configure_text_styles(ctx: &egui::Context) {
(TextStyle::Small, FontId::new(8.0, Proportional)),
]
.into();
ctx.set_style(style);
ctx.all_styles_mut(move |style| style.text_styles = text_styles.clone());
}
fn content(ui: &mut egui::Ui) {

View File

@ -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);

View File

@ -0,0 +1,23 @@
[package]
name = "custom_style"
version = "0.1.0"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.76"
publish = false
[lints]
workspace = true
[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
] }
egui_demo_lib.workspace = true
egui_extras = { workspace = true, features = ["image"] }
image = { workspace = true, features = ["png"] }

View File

@ -0,0 +1,7 @@
Example of how to customize the style.
```sh
cargo run -p custom_style
```
![](screenshot.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@ -0,0 +1,69 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
use eframe::egui::{
self, global_theme_preference_buttons, style::Selection, Color32, Stroke, Style, Theme,
};
use egui_demo_lib::{View, WidgetGallery};
fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([350.0, 590.0]),
..Default::default()
};
eframe::run_native(
"egui example: custom style",
options,
Box::new(|cc| Ok(Box::new(MyApp::new(cc)))),
)
}
fn setup_custom_style(ctx: &egui::Context) {
ctx.style_mut_of(Theme::Light, use_light_green_accent);
ctx.style_mut_of(Theme::Dark, use_dark_purple_accent);
}
fn use_light_green_accent(style: &mut Style) {
style.visuals.hyperlink_color = Color32::from_rgb(18, 180, 85);
style.visuals.text_cursor.stroke.color = Color32::from_rgb(28, 92, 48);
style.visuals.selection = Selection {
bg_fill: Color32::from_rgb(157, 218, 169),
stroke: Stroke::new(1.0, Color32::from_rgb(28, 92, 48)),
};
}
fn use_dark_purple_accent(style: &mut Style) {
style.visuals.hyperlink_color = Color32::from_rgb(202, 135, 227);
style.visuals.text_cursor.stroke.color = Color32::from_rgb(234, 208, 244);
style.visuals.selection = Selection {
bg_fill: Color32::from_rgb(105, 67, 119),
stroke: Stroke::new(1.0, Color32::from_rgb(234, 208, 244)),
};
}
struct MyApp {
widget_gallery: WidgetGallery,
}
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
setup_custom_style(&cc.egui_ctx);
egui_extras::install_image_loaders(&cc.egui_ctx); // Needed for the "Widget Gallery" demo
Self {
widget_gallery: WidgetGallery::default(),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("egui using a customized style");
ui.label("Switch between dark and light mode to see the different styles in action.");
global_theme_preference_buttons(ui);
ui.separator();
self.widget_gallery.ui(ui);
});
}
}

View File

@ -36,7 +36,7 @@ impl eframe::App for MyApp {
ui.label("This is just the contents of the window.");
ui.horizontal(|ui| {
ui.label("egui theme:");
egui::widgets::global_dark_light_mode_buttons(ui);
egui::widgets::global_theme_preference_buttons(ui);
});
});
}

View File

@ -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() {

View File

@ -32,7 +32,7 @@ struct MyApp {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.style_mut(|style| style.interaction.tooltip_delay = 0.0);
ctx.all_styles_mut(|style| style.interaction.tooltip_delay = 0.0);
egui::SidePanel::left("side_panel_left").show(ctx, |ui| {
ui.heading("Information");