diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index ebef299df..731c48f23 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -298,6 +298,18 @@ impl Frame { self } + /// Expand the frame without affecting layout. + /// + /// This handles `expansion` by subtracting it from the outer margin and adding it to the + /// inner margin. It also corrects for a stroke changing on hover, by subtracting the stroke + /// width from `inner_margin`. + #[inline] + pub fn expand_in_place(mut self, expansion: f32) -> Self { + self.outer_margin = self.outer_margin - Margin::from(expansion); + self.inner_margin = self.inner_margin + Margin::from(expansion - self.stroke.width); + self + } + /// Optional drop-shadow behind the frame. #[inline] pub fn shadow(mut self, shadow: Shadow) -> Self { @@ -316,6 +328,16 @@ impl Frame { self.shadow.color = self.shadow.color.gamma_multiply(opacity); self } + + /// Make this frame invisible by setting background and stroke to transparent. + /// + /// Will not affect layout or contents. + pub fn invisible(mut self) -> Self { + self.fill = Color32::TRANSPARENT; + self.stroke.color = Color32::TRANSPARENT; + self.shadow = Shadow::NONE; + self + } } /// ## Inspectors diff --git a/crates/egui/src/theme/default_style.rs b/crates/egui/src/theme/default_style.rs index 9c95d981a..571945793 100644 --- a/crates/egui/src/theme/default_style.rs +++ b/crates/egui/src/theme/default_style.rs @@ -2,8 +2,7 @@ use emath::Vec2; use epaint::Margin; use crate::{ - Button, Context, Frame, Style, TextStyle, - style::WidgetVisuals, + Button, Context, Frame, TextStyle, theme::StyleProvider, widget_style::{ ButtonStyle, CheckboxStyle, HasClasses as _, SeparatorStyle, StyleArgs, TextVisuals, @@ -30,17 +29,6 @@ impl DefaultStyle { } } -/// The text of a widget, based on the [`WidgetVisuals`] of its current state. -fn text_visuals(style: &Style, widget_visuals: &WidgetVisuals) -> TextVisuals { - TextVisuals { - color: widget_visuals.text_color(), - font_id: style - .override_font_id - .clone() - .unwrap_or_else(|| TextStyle::Body.resolve(style)), - } -} - impl StyleProvider for DefaultStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle { let StyleArgs { @@ -59,17 +47,24 @@ impl StyleProvider for DefaultStyle { widget_visuals.fg_stroke = visuals.selection.stroke; } - let mut inner_margin: Margin = (spacing.button_padding - + Vec2::splat(widget_visuals.expansion) - - Vec2::splat(widget_visuals.bg_stroke.width)) - .into(); + let mut inner_margin: Margin = spacing.button_padding.into(); - // A small button is meant to be embedded into text, so it must not add any height. + // A small button as high as regular text if classes.has_class(Button::CLASS_SMALL) { inner_margin.top = 0; inner_margin.bottom = 0; } + let painted_frame = Frame { + fill: widget_visuals.weak_bg_fill, + stroke: widget_visuals.bg_stroke, + corner_radius: widget_visuals.corner_radius, + inner_margin, + ..Default::default() + } + // Ensure changing expansion and stroke don't affect layout: + .expand_in_place(widget_visuals.expansion); + let has_frame = classes.has_class(Button::CLASS_FRAME) || (!classes.has_class(Button::CLASS_NO_FRAME) && style.visuals.button_frame); @@ -79,17 +74,10 @@ impl StyleProvider for DefaultStyle { } else if classes.has_class(Button::CLASS_HIDE_FRAME_WHEN_INACTIVE) && *state == WidgetState::Inactive { - // Invisible, but as big as it will be once the user interacts with it. - Frame::new().inner_margin(inner_margin) + // Hide the frame, but keep its spacing + painted_frame.invisible() } else { - Frame { - fill: widget_visuals.weak_bg_fill, - stroke: widget_visuals.bg_stroke, - corner_radius: widget_visuals.corner_radius, - outer_margin: (-Vec2::splat(widget_visuals.expansion)).into(), - inner_margin, - ..Default::default() - } + painted_frame }; ButtonStyle { @@ -99,7 +87,7 @@ impl StyleProvider for DefaultStyle { Vec2::new(0.0, spacing.interact_size.y) }, frame, - text_style: text_visuals(style, &widget_visuals), + text_style: TextVisuals::from_widget_visuals(style, TextStyle::Body, &widget_visuals), } } } @@ -120,7 +108,7 @@ impl StyleProvider for DefaultStyle { stroke: widget_visuals.bg_stroke, ..Default::default() }, - text_style: text_visuals(style, &widget_visuals), + text_style: TextVisuals::from_widget_visuals(style, TextStyle::Body, &widget_visuals), check_stroke: widget_visuals.fg_stroke, } } diff --git a/crates/egui/src/widget_style/classes.rs b/crates/egui/src/widget_style/classes.rs index 4d76fee25..41aabf3f4 100644 --- a/crates/egui/src/widget_style/classes.rs +++ b/crates/egui/src/widget_style/classes.rs @@ -78,6 +78,18 @@ pub trait HasClasses { self } + /// Add all the given classes by consuming `self` + /// + /// Useful to forward the classes of a composite widget to the widgets it is built from. + #[inline] + fn with_classes(mut self, classes: Classes) -> Self + where + Self: Sized, + { + self.classes_mut().classes.extend(classes.classes); + self + } + /// Add the given class by consuming `self` if the condition is true #[inline] fn with_class_if(mut self, class: impl Into, condition: bool) -> Self @@ -98,6 +110,12 @@ pub trait HasClasses { self } + #[inline] + fn add_classes(&mut self, classes: Classes) -> &mut Self { + self.classes_mut().classes.extend(classes.classes); + self + } + /// Add the given class in-place if the condition is true #[inline] fn add_class_if(&mut self, class: impl Into, condition: bool) -> &mut Self diff --git a/crates/egui/src/widget_style/mod.rs b/crates/egui/src/widget_style/mod.rs index f945116b9..1a28327d9 100644 --- a/crates/egui/src/widget_style/mod.rs +++ b/crates/egui/src/widget_style/mod.rs @@ -17,7 +17,7 @@ use core::fmt::Debug; use epaint::{Color32, FontId, Stroke, Vec2}; use crate::{ - Context, Frame, Response, Style, UiStack, + Context, Frame, Response, Style, TextStyle, UiStack, style::{WidgetVisuals, Widgets}, }; @@ -34,6 +34,30 @@ pub struct TextVisuals { pub color: Color32, } +impl TextVisuals { + /// Text in `color`, using the font of the given [`TextStyle`]. + /// + /// `style.override_font_id` wins over `text_style`, if it is set. + pub fn new(style: &Style, text_style: TextStyle, color: Color32) -> Self { + Self { + color, + font_id: style + .override_font_id + .clone() + .unwrap_or_else(|| text_style.resolve(style)), + } + } + + /// The text of a widget, colored by the [`WidgetVisuals`] of its current state. + pub fn from_widget_visuals( + style: &Style, + text_style: TextStyle, + widget_visuals: &WidgetVisuals, + ) -> Self { + Self::new(style, text_style, widget_visuals.text_color()) + } +} + /// Dedicated button style #[derive(Debug, Clone)] pub struct ButtonStyle { diff --git a/examples/styling_engine/src/main.rs b/examples/styling_engine/src/main.rs index 43a87710a..6ba03a9bc 100644 --- a/examples/styling_engine/src/main.rs +++ b/examples/styling_engine/src/main.rs @@ -65,11 +65,8 @@ impl StyleProvider for MyTheme { .fill(fill) .corner_radius(self.corner_radius) .inner_margin(8), - text_style: TextVisuals { - color: Color32::WHITE, - // Resolve the font from the style, so we follow the user's font sizes: - font_id: TextStyle::Button.resolve(style), - }, + // Resolve the font from the style, so we follow the user's font sizes: + text_style: TextVisuals::new(style, TextStyle::Button, Color32::WHITE), } } }