1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Give TextEdit a min_size and a gap, via a shared LayoutStyle

`ButtonStyle` carried a `min_size` and a gap; `TextEditStyle` carried
neither, so a themed text edit could set its padding but not its height or
the gap to its prefix — the height had to travel separately on the widget,
and the gap fell back to the ambient `spacing.icon_spacing`.

A theme decides the two together, so they move into a `LayoutStyle` that
both styles embed. `TextEdit` now honors both, with defaults that keep
today's rendering: no floor of egui's own, and the gap `AtomLayout` was
already falling back to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Meurer
2026-08-25 10:40:35 +02:00
parent 5c08eb5684
commit 887d9b0415
5 changed files with 68 additions and 24 deletions

View File

@@ -5,8 +5,9 @@ use crate::{
Frame, TextStyle,
theme::StyleProvider,
widget_style::{
BaseStyle, ButtonStyle, CheckboxStyle, HasClasses as _, LabelStyle, READ_ONLY_CLASS,
SELECTED_CLASS, SeparatorStyle, StyleArgs, TextEditStyle, TextVisuals, WidgetState,
BaseStyle, ButtonStyle, CheckboxStyle, HasClasses as _, LabelStyle, LayoutStyle,
READ_ONLY_CLASS, SELECTED_CLASS, SeparatorStyle, StyleArgs, TextEditStyle, TextVisuals,
WidgetState,
},
};
@@ -87,11 +88,13 @@ impl StyleProvider<ButtonStyle> for DefaultStyle {
.into(),
..Default::default()
},
layout: LayoutStyle {
// Historically only the height was floored, so that a button is at least as tall
// as any other interactive widget on the same row.
min_size: Vec2::new(0.0, spacing.interact_size.y),
gap: spacing.icon_spacing,
},
text_style: ws.text,
// Historically only the height was floored, so that a button is at least as tall as
// any other interactive widget on the same row.
min_size: Vec2::new(0.0, spacing.interact_size.y),
gap: spacing.icon_spacing,
}
}
}
@@ -145,6 +148,12 @@ impl StyleProvider<TextEditStyle> for DefaultStyle {
outer_margin: Margin::same(-(widget_visuals.expansion as i8)),
..Default::default()
},
layout: LayoutStyle {
// A text edit sizes itself from the rows it holds; egui's own theme adds no floor
// of its own.
min_size: Vec2::ZERO,
gap: style.spacing.icon_spacing,
},
text: ws.text,
hint_text_color: style.visuals.weak_text_color(),
}

View File

@@ -65,22 +65,35 @@ pub struct BaseStyle {
impl WidgetStyle for BaseStyle {}
/// How a widget's contents are laid out
///
/// A theme that gives a widget a size decides both together — the height and the gap of a small
/// button are one look, not two — so they travel as one struct, shared by every widget style that
/// lays its contents out with an [`crate::AtomLayout`].
#[derive(Debug, Clone)]
pub struct LayoutStyle {
/// How small the widget may get, before its contents are taken into account.
///
/// A floor, not a size: a widget is never smaller than what it holds.
pub min_size: Vec2,
/// The gap between the widget's atoms, e.g. between an icon and the text beside it.
pub gap: f32,
}
/// Dedicated button style
#[derive(Debug, Clone)]
pub struct ButtonStyle {
pub frame: Frame,
/// How the button's contents are laid out.
///
/// [`LayoutStyle::min_size`] is ignored by a [`crate::Button::small`] button, which sizes
/// itself purely from its contents and its own [`crate::Button::min_size`];
/// [`LayoutStyle::gap`] is overridden by [`crate::Button::gap`].
pub layout: LayoutStyle,
pub text_style: TextVisuals,
/// How small the button may get, before its contents are taken into account.
///
/// Ignored by a [`crate::Button::small`] button, which sizes itself purely from its contents
/// and its own [`crate::Button::min_size`].
pub min_size: Vec2,
/// The gap between the button's atoms, e.g. between its icon and its text.
///
/// Overridden by [`crate::Button::gap`].
pub gap: f32,
}
impl WidgetStyle for ButtonStyle {}
@@ -91,6 +104,13 @@ pub struct TextEditStyle {
/// Frame around the text, including its padding.
pub frame: Frame,
/// How the field's contents are laid out.
///
/// [`LayoutStyle::min_size`] is raised by [`crate::TextEdit::min_size`] and by the rows of
/// text the field holds, so it only ever sets a floor; [`LayoutStyle::gap`] separates the
/// text from a [`crate::TextEdit::prefix`] or [`crate::TextEdit::suffix`].
pub layout: LayoutStyle,
/// The text being edited.
pub text: TextVisuals,

View File

@@ -4,7 +4,7 @@ use crate::{
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Atoms, Color32, CornerRadius,
Frame, Image, IntoAtoms, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget,
WidgetInfo, WidgetText, WidgetType,
widget_style::{ButtonStyle, Classes, HasClasses, SELECTED_CLASS, WidgetState},
widget_style::{ButtonStyle, Classes, HasClasses, LayoutStyle, SELECTED_CLASS, WidgetState},
};
/// Clickable button with text.
@@ -325,9 +325,12 @@ impl<'a> Button<'a> {
let ButtonStyle {
frame,
layout:
LayoutStyle {
min_size: style_min_size,
gap,
},
text_style,
min_size: style_min_size,
gap,
} = ui.widget_style(id, &classes);
layout = layout.fallback_gap(gap);

View File

@@ -17,7 +17,7 @@ use crate::{
self, CCursorRange, text_cursor_state::cursor_rect, visuals::paint_text_selection,
},
vec2,
widget_style::{Classes, HasClasses, READ_ONLY_CLASS, TextEditStyle},
widget_style::{Classes, HasClasses, LayoutStyle, READ_ONLY_CLASS, TextEditStyle},
};
use super::{TextEditOutput, TextEditState};
@@ -489,10 +489,19 @@ impl TextEdit<'_> {
classes.add_class_if(READ_ONLY_CLASS, !text.is_mutable());
let TextEditStyle {
frame: styled_frame,
layout:
LayoutStyle {
min_size: style_min_size,
gap,
},
text: text_visuals,
hint_text_color,
} = ui.widget_style(id, &classes);
// The theme sets a floor on the size; the builder's own `min_size` can only raise it,
// the same way it does for a button.
let min_size = min_size.max(style_min_size);
let text_color = text_color
.or_else(|| ui.visuals().override_text_color)
.unwrap_or(text_visuals.color);
@@ -736,6 +745,7 @@ impl TextEdit<'_> {
let allocated = AtomLayout::new(atoms)
.id(id)
.fallback_gap(gap)
.min_size(Vec2::new(allocate_width, min_height.at_least(min_size.y)))
.max_width(allocate_width)
.sense(sense)

View File

@@ -6,7 +6,7 @@
use eframe::egui::{
self, CentralPanel, Color32, Frame, Panel,
theme::StyleProvider,
widget_style::{BaseStyle, ButtonStyle, HasClasses as _, StyleArgs, WidgetState},
widget_style::{BaseStyle, ButtonStyle, HasClasses as _, LayoutStyle, StyleArgs, WidgetState},
};
/// Buttons with this class are styled as a destructive action.
@@ -67,9 +67,11 @@ impl StyleProvider<ButtonStyle> for MyTheme {
.fill(fill)
.corner_radius(self.corner_radius)
.inner_margin(8),
layout: LayoutStyle {
min_size: args.style.spacing.interact_size,
gap: args.style.spacing.icon_spacing,
},
text_style: base.text,
min_size: args.style.spacing.interact_size,
gap: args.style.spacing.icon_spacing,
}
}
}