1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 13:50:04 -04:00

Add Frame::expand_in_place, Frame::invisible and TextVisuals constructors

The expansion/stroke correction on a widget's margins was written out by
hand. Move it into `Frame::expand_in_place`, which knows the frame's own
stroke width, so a caller states the intent instead of the arithmetic.
`Frame::invisible` goes with it: a frame that keeps its layout but drops
its paint.

Two fixes fall out of it in the button style:

* A small button zeroed its vertical padding after the correction, which
  left the frame a `stroke.width - expansion` tall. Zeroing before the
  correction makes it exactly zero in every state, which is what "must
  not add any height" means.
* The invisible frame of a button that hides its frame when inactive
  dropped the outer margin and the stroke width, so it was not, in fact,
  as big as the painted one. It now keeps the whole frame and only drops
  the paint.

Also give `TextVisuals` a `new` and a `from_widget_visuals`, replacing the
local `text_visuals` helper, and add `HasClasses::add_classes` and
`HasClasses::with_classes`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Meurer
2026-08-31 13:49:10 +02:00
parent 2c856e335d
commit 64470b2d40
5 changed files with 85 additions and 36 deletions

View File

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

View File

@@ -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<ButtonStyle> for DefaultStyle {
fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle {
let StyleArgs {
@@ -59,17 +47,24 @@ impl StyleProvider<ButtonStyle> 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<ButtonStyle> 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<ButtonStyle> 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<CheckboxStyle> 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,
}
}

View File

@@ -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<ClassName>, 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<ClassName>, condition: bool) -> &mut Self

View File

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