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 13 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/native/glow_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ impl<'app> GlowWinitRunning<'app> {

let clear_color = self
.app
.clear_color(&self.integration.egui_ctx.style().visuals);
.clear_color(&self.integration.egui_ctx.active_style().visuals);

let has_many_viewports = self.glutin.borrow().viewports.len() > 1;
let clear_before_update = !has_many_viewports; // HACK: for some reason, an early clear doesn't "take" on Mac with multiple viewports.
Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/native/wgpu_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ impl<'app> WgpuWinitRunning<'app> {
let (vsync_secs, screenshot) = painter.paint_and_update_textures(
viewport_id,
pixels_per_point,
app.clear_color(&egui_ctx.style().visuals),
app.clear_color(&egui_ctx.active_style().visuals),
&clipped_primitives,
&textures_delta,
screenshot_requested,
Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ impl AppRunner {

if let Some(clipped_primitives) = clipped_primitives {
if let Err(err) = self.painter.paint_and_update_textures(
self.app.clear_color(&self.egui_ctx.style().visuals),
self.app.clear_color(&self.egui_ctx.active_style().visuals),
&clipped_primitives,
self.egui_ctx.pixels_per_point(),
&textures_delta,
Expand Down
5 changes: 3 additions & 2 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ impl Area {
// during the sizing pass we will use this as the max size
let mut size = default_size;

let default_area_size = ctx.style().spacing.default_area_size;
let default_area_size = ctx.active_style().spacing.default_area_size;
if size.x.is_nan() {
size.x = default_area_size.x;
}
Expand Down Expand Up @@ -553,7 +553,8 @@ impl Prepared {
if let Some(last_became_visible_at) = self.state.last_became_visible_at {
let age =
ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0);
let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0);
let opacity =
crate::remap_clamp(age, 0.0..=ctx.active_style().animation_time, 0.0..=1.0);
let opacity = emath::easing::quadratic_out(opacity); // slow fade-out = quick fade-in
ui.multiply_opacity(opacity);
if opacity < 1.0 {
Expand Down
6 changes: 3 additions & 3 deletions crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ impl TopBottomPanel {
let expanded_height = PanelState::load(ctx, self.id)
.map(|state| state.rect.height())
.or(self.default_height)
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
.unwrap_or_else(|| ctx.active_style().spacing.interact_size.y);
let fake_height = how_expanded * expanded_height;
Self {
id: self.id.with("animating_panel"),
Expand Down Expand Up @@ -986,12 +986,12 @@ impl TopBottomPanel {
let collapsed_height = PanelState::load(ctx, collapsed_panel.id)
.map(|state| state.rect.height())
.or(collapsed_panel.default_height)
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
.unwrap_or_else(|| ctx.active_style().spacing.interact_size.y);
bash marked this conversation as resolved.
Show resolved Hide resolved

let expanded_height = PanelState::load(ctx, expanded_panel.id)
.map(|state| state.rect.height())
.or(expanded_panel.default_height)
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
.unwrap_or_else(|| ctx.active_style().spacing.interact_size.y);

let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
Self {
Expand Down
6 changes: 4 additions & 2 deletions crates/egui/src/containers/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ fn show_tooltip_at_dyn<'c, R>(
.order(Order::Tooltip)
.pivot(pivot)
.fixed_pos(anchor)
.default_width(ctx.style().spacing.tooltip_width)
.default_width(ctx.active_style().spacing.tooltip_width)
.sense(Sense::hover()) // don't click to bring to front
.show(ctx, |ui| {
// By default the text in tooltips aren't selectable.
Expand All @@ -208,7 +208,9 @@ fn show_tooltip_at_dyn<'c, R>(
// will stick around when you try to click them.
ui.style_mut().interaction.selectable_labels = false;

Frame::popup(&ctx.style()).show_dyn(ui, add_contents).inner
Frame::popup(&ctx.active_style())
.show_dyn(ui, add_contents)
.inner
});

state.tooltip_count += 1;
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ impl Resize {
state.store(ui.ctx(), id);

#[cfg(debug_assertions)]
if ui.ctx().style().debug.show_resize {
if ui.ctx().active_style().debug.show_resize {
ui.ctx().debug_painter().debug_rect(
Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size),
Color32::GREEN,
Expand Down
14 changes: 8 additions & 6 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,9 +434,11 @@ impl<'open> Window<'open> {
fade_out,
} = self;

let header_color =
frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill);
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
let header_color = frame.map_or_else(
|| ctx.active_style().visuals.widgets.open.weak_bg_fill,
|f| f.fill,
);
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.active_style()));
// Keep the original inner margin for later use
let window_margin = window_frame.inner_margin;

Expand Down Expand Up @@ -468,7 +470,7 @@ impl<'open> Window<'open> {

// Calculate roughly how much larger the window size is compared to the inner rect
let (title_bar_height, title_content_spacing) = if with_title_bar {
let style = ctx.style();
let style = ctx.active_style();
let spacing = window_margin.top + window_margin.bottom;
let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing;
window_frame.rounding.ne = window_frame.rounding.ne.clamp(0.0, height / 2.0);
Expand Down Expand Up @@ -849,8 +851,8 @@ fn resize_interaction(

let id = Id::new(layer_id).with("edge_drag");

let side_grab_radius = ctx.style().interaction.resize_grab_radius_side;
let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner;
let side_grab_radius = ctx.active_style().interaction.resize_grab_radius_side;
let corner_grab_radius = ctx.active_style().interaction.resize_grab_radius_corner;

let corner_rect =
|center: Pos2| Rect::from_center_size(center, Vec2::splat(2.0 * corner_grab_radius));
Expand Down
112 changes: 84 additions & 28 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 @@ -988,7 +988,7 @@ impl Context {
let screen_rect = self.screen_rect();

let text = format!("🔥 {text}");
let color = self.style().visuals.error_fg_color;
let color = self.active_style().visuals.error_fg_color;
let painter = self.debug_painter();
painter.rect_stroke(widget_rect, 0.0, (1.0, color));

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 @@ -1357,7 +1357,7 @@ impl Context {
..
} = ModifierNames::SYMBOLS;

let font_id = TextStyle::Body.resolve(&self.style());
let font_id = TextStyle::Body.resolve(&self.active_style());
self.fonts(|f| {
let mut lock = f.lock();
let font = lock.fonts.font(&font_id);
Expand Down Expand Up @@ -1612,31 +1612,87 @@ impl Context {
}
}

/// 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 [`Style`] used by all subsequent windows, panels etc.
pub fn style(&self) -> Arc<Style> {
self.options(|opt| opt.style.clone())
pub fn active_style(&self) -> Arc<Style> {
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:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.style_mut(|style| {
/// ctx.styles_mut(|style| {
/// 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 styles_mut(&self, mut mutate_style: impl FnMut(&mut Style)) {
bash marked this conversation as resolved.
Show resolved Hide resolved
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.
pub fn style(&self, theme: Theme) -> Arc<Style> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted elsewhere, the active_style produces a big diff (=a lot of breaking changes). I suggest we instead call this style_of. I suspect this will be less used anyway, so keeping the often used function name shorter makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have additionally added back set_style (which sets the currently active style). Should I also add back set_visuals now that we have the _of suffix?

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 in dark mode.
///
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.style_mut(egui::Theme::Dark, |style| {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
pub fn style_mut(&self, theme: Theme, mutate_style: impl FnOnce(&mut Style)) {
bash marked this conversation as resolved.
Show resolved Hide resolved
self.options_mut(|opt| match theme {
Theme::Dark => mutate_style(std::sync::Arc::make_mut(&mut opt.dark_style)),
Theme::Light => mutate_style(std::sync::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`]
/// 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());
pub fn set_style(&self, theme: Theme, style: impl Into<std::sync::Arc<crate::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 +1702,10 @@ impl Context {
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.set_visuals(egui::Visuals::light()); // Switch to light mode
/// ctx.set_visuals(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(&self, theme: Theme, visuals: crate::Visuals) {
self.style_mut(theme, |style| style.visuals = visuals);
}

/// The number of physical pixels for each logical point.
Expand Down Expand Up @@ -1891,7 +1947,7 @@ impl Context {
}
};

if self.style().debug.show_interactive_widgets {
if self.active_style().debug.show_interactive_widgets {
// Show all interactive widgets:
let rects = self.write(|ctx| ctx.viewport().this_frame.widgets.clone());
for (layer_id, rects) in rects.layers() {
Expand Down Expand Up @@ -1967,7 +2023,7 @@ impl Context {
}
}

if self.style().debug.show_widget_hits {
if self.active_style().debug.show_widget_hits {
let hits = self.write(|ctx| ctx.viewport().hits.clone());
let WidgetHits {
contains_pointer,
Expand Down Expand Up @@ -2481,13 +2537,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.styles_mut(|style| style.debug.debug_on_hover = debug_on_hover);
}
}

Expand All @@ -2504,7 +2560,7 @@ impl Context {
/// The animation time is taken from [`Style::animation_time`].
#[track_caller] // To track repaint cause
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
let animation_time = self.style().animation_time;
let animation_time = self.active_style().animation_time;
self.animate_bool_with_time_and_easing(id, value, animation_time, emath::easing::linear)
}

Expand All @@ -2520,7 +2576,7 @@ impl Context {
/// Like [`Self::animate_bool`] but allows you to control the easing function.
#[track_caller] // To track repaint cause
pub fn animate_bool_with_easing(&self, id: Id, value: bool, easing: fn(f32) -> f32) -> f32 {
let animation_time = self.style().animation_time;
let animation_time = self.active_style().animation_time;
self.animate_bool_with_time_and_easing(id, value, animation_time, easing)
}

Expand Down Expand Up @@ -2871,11 +2927,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(theme)).clone();
style.ui(ui);
self.set_style(style);
self.set_style(theme, style);
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/debug_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl State {
let available_width = ctx.screen_rect().max.x - pos.x;
let galley = text.into_galley_impl(
ctx,
&ctx.style(),
&ctx.active_style(),
text::TextWrapping::wrap_at_width(available_width),
font_id.clone().into(),
Align::TOP,
Expand Down
Loading
Loading