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:
parent
f4697bc007
commit
b5627c7d40
11
Cargo.lock
11
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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: _,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
Example of how to customize the style.
|
||||
|
||||
```sh
|
||||
cargo run -p custom_style
|
||||
```
|
||||
|
||||

|
||||
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Reference in New Issue