Skip to content

Commit 9b01154

Browse files
committed
Tons of small improvements to make the new button implementation match the current one.
- offset frame margin for stroke width - handle expansion - add min_size - calculate image size based on font height - correctly handle alignment / ui layout
1 parent 6536763 commit 9b01154

File tree

3 files changed

+99
-34
lines changed

3 files changed

+99
-34
lines changed

crates/egui/src/ui.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ use crate::{
2525
color_picker, Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link,
2626
RadioButton, SelectableLabel, Separator, Spinner, TextEdit, Widget,
2727
},
28-
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, LayerId,
29-
Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText, Sense,
30-
Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2, WidgetRect,
31-
WidgetText,
28+
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtomics,
29+
LayerId, Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText,
30+
Sense, Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2,
31+
WidgetRect, WidgetText,
3232
};
3333
// ----------------------------------------------------------------------------
3434

@@ -2046,7 +2046,7 @@ impl Ui {
20462046
/// ```
20472047
#[must_use = "You should check if the user clicked this with `if ui.button(…).clicked() { … } "]
20482048
#[inline]
2049-
pub fn button(&mut self, text: impl Into<WidgetText>) -> Response {
2049+
pub fn button<'a>(&mut self, text: impl IntoAtomics<'a>) -> Response {
20502050
Button::new(text).ui(self)
20512051
}
20522052

crates/egui/src/widget_layout.rs

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::{Frame, Id, Image, ImageSource, Response, Sense, TextStyle, Ui, Widget, WidgetText};
1+
use crate::{Frame, Id, Image, Response, Sense, Style, TextStyle, Ui, Widget, WidgetText};
22
use ahash::HashMap;
3-
use emath::{Align2, Rect, Vec2};
4-
use epaint::{Color32, Galley};
3+
use emath::{Align2, NumExt, Rect, Vec2};
4+
use epaint::{Color32, Fonts, Galley};
55
use std::sync::Arc;
66

77
pub enum SizedAtomicKind<'a> {
@@ -29,6 +29,7 @@ pub struct WidgetLayout<'a> {
2929
pub(crate) frame: Frame,
3030
pub(crate) sense: Sense,
3131
fallback_text_color: Option<Color32>,
32+
min_size: Vec2,
3233
}
3334

3435
impl<'a> WidgetLayout<'a> {
@@ -39,6 +40,7 @@ impl<'a> WidgetLayout<'a> {
3940
frame: Frame::default(),
4041
sense: Sense::hover(),
4142
fallback_text_color: None,
43+
min_size: Vec2::ZERO,
4244
}
4345
}
4446

@@ -68,14 +70,28 @@ impl<'a> WidgetLayout<'a> {
6870
self
6971
}
7072

73+
pub fn min_size(mut self, size: Vec2) -> Self {
74+
self.min_size = size;
75+
self
76+
}
77+
7178
pub fn show(self, ui: &mut Ui) -> AtomicLayoutResponse {
79+
let Self {
80+
atomics,
81+
gap,
82+
frame,
83+
sense,
84+
fallback_text_color,
85+
min_size,
86+
} = self;
87+
7288
let fallback_text_color = self
7389
.fallback_text_color
7490
.unwrap_or_else(|| ui.style().visuals.text_color());
75-
let gap = self.gap.unwrap_or(ui.spacing().icon_spacing);
91+
let gap = gap.unwrap_or(ui.spacing().icon_spacing);
7692

7793
// The size available for the content
78-
let available_inner_size = ui.available_size() - self.frame.total_margin().sum();
94+
let available_inner_size = ui.available_size() - frame.total_margin().sum();
7995

8096
let mut desired_width = 0.0;
8197
let mut preferred_width = 0.0;
@@ -88,13 +104,25 @@ impl<'a> WidgetLayout<'a> {
88104

89105
let mut shrink_item = None;
90106

91-
if self.atomics.0.len() > 1 {
92-
let gap_space = gap * (self.atomics.0.len() as f32 - 1.0);
107+
let align2 = Align2([ui.layout().horizontal_align(), ui.layout().vertical_align()]);
108+
109+
if atomics.0.len() > 1 {
110+
let gap_space = gap * (atomics.0.len() as f32 - 1.0);
93111
desired_width += gap_space;
94112
preferred_width += gap_space;
95113
}
96114

97-
for ((idx, item)) in self.atomics.0.into_iter().enumerate() {
115+
let max_font_size = ui
116+
.fonts(|fonts| {
117+
atomics
118+
.0
119+
.iter()
120+
.filter_map(|a| a.get_min_height_for_image(fonts, ui.style()))
121+
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
122+
})
123+
.unwrap_or_else(|| ui.text_style_height(&TextStyle::Body));
124+
125+
for ((idx, item)) in atomics.0.into_iter().enumerate() {
98126
if item.shrink {
99127
debug_assert!(
100128
shrink_item.is_none(),
@@ -108,7 +136,9 @@ impl<'a> WidgetLayout<'a> {
108136
if item.grow {
109137
grow_count += 1;
110138
}
111-
let (preferred_size, sized) = item.kind.into_sized(ui, available_inner_size);
139+
let (preferred_size, sized) =
140+
item.kind
141+
.into_sized(ui, available_inner_size, max_font_size);
112142
let size = sized.size();
113143

114144
desired_width += size.x;
@@ -125,7 +155,7 @@ impl<'a> WidgetLayout<'a> {
125155
available_inner_size.x - desired_width,
126156
available_inner_size.y,
127157
);
128-
let (preferred_size, sized) = item.kind.into_sized(ui, shrunk_size);
158+
let (preferred_size, sized) = item.kind.into_sized(ui, shrunk_size, max_font_size);
129159
let size = sized.size();
130160

131161
desired_width += size.x;
@@ -136,25 +166,31 @@ impl<'a> WidgetLayout<'a> {
136166
sized_items.insert(index, sized);
137167
}
138168

139-
let margin = self.frame.total_margin();
169+
let margin = frame.total_margin();
140170
let content_size = Vec2::new(desired_width, height);
141-
let frame_size = content_size + margin.sum();
171+
let frame_size = (content_size + margin.sum()).at_least(min_size);
142172

143-
let (rect, response) = ui.allocate_at_least(frame_size, self.sense);
173+
let (rect, response) = ui.allocate_at_least(frame_size, sense);
144174

145175
let mut response = AtomicLayoutResponse {
146176
response,
147177
custom_rects: HashMap::default(),
148178
};
149179

150-
let content_rect = rect - margin;
151-
ui.painter().add(self.frame.paint(content_rect));
180+
let inner_rect = rect - margin;
181+
ui.painter().add(frame.paint(inner_rect));
152182

153-
let width_to_fill = content_rect.width();
183+
let width_to_fill = inner_rect.width();
154184
let extra_space = f32::max(width_to_fill - desired_width, 0.0);
155185
let grow_width = f32::max(extra_space / grow_count as f32, 0.0);
156186

157-
let mut cursor = content_rect.left();
187+
let aligned_rect = if grow_count > 0 {
188+
align2.align_size_within_rect(Vec2::new(width_to_fill, content_size.y), inner_rect)
189+
} else {
190+
align2.align_size_within_rect(content_size, inner_rect)
191+
};
192+
193+
let mut cursor = aligned_rect.left();
158194

159195
for sized in sized_items {
160196
let size = sized.size();
@@ -164,7 +200,7 @@ impl<'a> WidgetLayout<'a> {
164200
_ => size.x,
165201
};
166202

167-
let frame = content_rect.with_min_x(cursor).with_max_x(cursor + width);
203+
let frame = aligned_rect.with_min_x(cursor).with_max_x(cursor + width);
168204
cursor = frame.right() + gap;
169205

170206
let align = Align2::CENTER_CENTER;
@@ -262,7 +298,12 @@ pub enum AtomicKind<'a> {
262298

263299
impl<'a> AtomicKind<'a> {
264300
/// First returned argument is the preferred size.
265-
pub fn into_sized(self, ui: &Ui, available_size: Vec2) -> (Vec2, SizedAtomicKind<'a>) {
301+
pub fn into_sized(
302+
self,
303+
ui: &Ui,
304+
available_size: Vec2,
305+
font_size: f32,
306+
) -> (Vec2, SizedAtomicKind<'a>) {
266307
match self {
267308
AtomicKind::Text(text) => {
268309
let galley = text.into_galley(ui, None, available_size.x, TextStyle::Button);
@@ -272,9 +313,9 @@ impl<'a> AtomicKind<'a> {
272313
)
273314
}
274315
AtomicKind::Image(image) => {
275-
let size =
276-
image.load_and_calc_size(ui, Vec2::min(available_size, Vec2::splat(16.0)));
277-
let size = size.unwrap_or_default();
316+
let max_size = Vec2::splat(font_size);
317+
let size = image.load_and_calc_size(ui, Vec2::min(available_size, max_size));
318+
let size = size.unwrap_or(max_size);
278319
(size, SizedAtomicKind::Image(image, size))
279320
}
280321
AtomicKind::Custom(id, size) => (size, SizedAtomicKind::Custom(id, size)),
@@ -300,6 +341,19 @@ pub fn a<'a>(i: impl Into<AtomicKind<'a>>) -> Atomic<'a> {
300341
}
301342

302343
impl Atomic<'_> {
344+
fn get_min_height_for_image(&self, fonts: &Fonts, style: &Style) -> Option<f32> {
345+
self.size.map(|s| s.y).or_else(|| {
346+
match &self.kind {
347+
AtomicKind::Text(text) => Some(text.font_height(fonts, style)),
348+
AtomicKind::Custom(_, size) => Some(size.y),
349+
AtomicKind::Grow => None,
350+
// Since this method is used to calculate the best height for an image, we always return
351+
// None for images.
352+
AtomicKind::Image(_) => None,
353+
}
354+
})
355+
}
356+
303357
// pub fn size(mut self, size: Vec2) -> Self {
304358
// self.size = Some(size);
305359
// self

crates/egui/src/widgets/button.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,12 @@ impl<'a> Button<'a> {
184184
/// See also [`Self::right_text`].
185185
#[inline]
186186
pub fn shortcut_text(mut self, shortcut_text: impl Into<Atomic<'a>>) -> Self {
187-
self.wl = self
188-
.wl
189-
.add(AtomicKind::Grow.a_grow(true))
190-
.add(shortcut_text);
187+
let mut atomic = shortcut_text.into();
188+
atomic.kind = match atomic.kind {
189+
AtomicKind::Text(text) => AtomicKind::Text(text.weak()),
190+
other => other,
191+
};
192+
self.wl = self.wl.add(AtomicKind::Grow.a_grow(true)).add(atomic);
191193
self
192194
}
193195

@@ -215,7 +217,7 @@ impl<'a> Button<'a> {
215217
stroke,
216218
small,
217219
frame,
218-
min_size,
220+
mut min_size,
219221
corner_radius,
220222
selected,
221223
image_tint_follows_text_color,
@@ -266,15 +268,24 @@ impl<'a> Button<'a> {
266268
wl = wl.fallback_text_color(visuals.text_color());
267269

268270
wl.frame = if has_frame {
271+
let stroke = stroke.unwrap_or(visuals.bg_stroke);
269272
wl.frame
270-
.inner_margin(button_padding)
273+
.inner_margin(
274+
button_padding + Vec2::splat(visuals.expansion) - Vec2::splat(stroke.width),
275+
)
276+
.outer_margin(-Vec2::splat(visuals.expansion))
271277
.fill(fill.unwrap_or(visuals.weak_bg_fill))
272-
.stroke(stroke.unwrap_or(visuals.bg_stroke))
278+
.stroke(stroke)
273279
.corner_radius(corner_radius.unwrap_or(visuals.corner_radius))
274280
} else {
275281
Frame::new()
276282
};
277283

284+
if !small {
285+
min_size.y = min_size.y.at_least(ui.spacing().interact_size.y);
286+
}
287+
wl = wl.min_size(min_size);
288+
278289
let text = wl.atomics.text();
279290

280291
let response = wl.show(ui);

0 commit comments

Comments
 (0)