mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Add AtomLayout, abstracing layouting within widgets (#5830)
Today each widget does its own custom layout, which has some drawbacks:
- not very flexible
- you can add an `Image` to `Button` but it will always be shown on the
left side
- you can't add a `Image` to a e.g. a `SelectableLabel`
- a lot of duplicated code
This PR introduces `Atoms` and `AtomLayout` which abstracts over "widget
content" and layout within widgets, so it'd be possible to add images /
text / custom rendering (for e.g. the checkbox) to any widget.
A simple custom button implementation is now as easy as this:
```rs
pub struct ALButton<'a> {
al: AtomicLayout<'a>,
}
impl<'a> ALButton<'a> {
pub fn new(content: impl IntoAtomics) -> Self {
Self { al: content.into_atomics() }
}
}
impl<'a> Widget for ALButton<'a> {
fn ui(mut self, ui: &mut Ui) -> Response {
let response = ui.ctx().read_response(ui.next_auto_id());
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
ui.style().interact(&response)
});
self.al.frame = self
.al
.frame
.inner_margin(ui.style().spacing.button_padding)
.fill(visuals.bg_fill)
.stroke(visuals.bg_stroke)
.corner_radius(visuals.corner_radius);
self.al.show(ui)
}
}
```
The initial implementation only does very basic layout, just enough to
be able to implement most current egui widgets, so:
- only horizontal layout
- everything is centered
- a single item may grow/shrink based on the available space
- everything can be contained in a Frame
There is a trait `IntoAtoms` that conveniently allows you to construct
`Atoms` from a tuple
```
ui.button((Image::new("image.png"), "Click me!"))
```
to get a button with image and text.
This PR reimplements three egui widgets based on the new AtomLayout:
- Button
- matches the old button pixel-by-pixel
- Button with image is now [properly
aligned](https://github.com/emilk/egui/pull/5830/files#diff-962ce2c68ab50724b01c6b64c683c4067edd9b79fcdcb39a6071021e33ebe772)
in justified layouts
- selected button style now matches SelecatbleLabel look
- For some reason the DragValue text seems shifted by a pixel almost
everywhere, but I think it's more centered now, yay?
- Checkbox
- basically pixel-perfect but apparently the check mesh is very slightly
different so I had to update the snapshot
- somehow needs a bit more space in some snapshot tests?
- RadioButton
- pixel-perfect
- somehow needs a bit more space in some snapshot tests?
I plan on updating TextEdit based on AtomLayout in a separate PR (so
you could use it to add a icon within the textedit frame).
This commit is contained in:
3
tests/egui_tests/tests/snapshots/grow_all.png
Normal file
3
tests/egui_tests/tests/snapshots/grow_all.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:34f0c49cef96c7c3d08dbe835efd9366a4ced6ad2c6aa7facb0de08fd1a44648
|
||||
size 14011
|
||||
3
tests/egui_tests/tests/snapshots/layout/atoms_image.png
Normal file
3
tests/egui_tests/tests/snapshots/layout/atoms_image.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:39a13fdac498d6f851a28ea3ca19d523235d5e0ab8e765ea980cf8fb2f64ba35
|
||||
size 387619
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4a09e926d25e2b6f63dc6df00ab5e5b76745aae1f288231f1a602421b2bbb53b
|
||||
size 384721
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14a1dc826aeced98cab1413f915dcbbe904b5b1eadfc4d811232bc8ccbe7f550
|
||||
size 299556
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:975c279d6da2a2cb000df72bf5d9f3bdd200bb20adc00e29e8fd9ed4d2c6f6b1
|
||||
size 340923
|
||||
oid sha256:01309596ac9eb90b2dfc00074cfd39d26e3f6d1f83299f227cb4bbea9ccd3b66
|
||||
size 339917
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aaf9b032037d0708894e568cc8e256b32be9cfb586eaffdc6167143b85562b37
|
||||
size 415016
|
||||
oid sha256:1d842f88b6a94f19aa59bdae9dbbf42f4662aaead1b8f73ac0194f183112e1b8
|
||||
size 415066
|
||||
|
||||
3
tests/egui_tests/tests/snapshots/max_width.png
Normal file
3
tests/egui_tests/tests/snapshots/max_width.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:90cfa6e9be28ef538491ad94615e162ecc107df6a320084ec30840a75660ac35
|
||||
size 8759
|
||||
3
tests/egui_tests/tests/snapshots/max_width_and_grow.png
Normal file
3
tests/egui_tests/tests/snapshots/max_width_and_grow.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:effb4a69a7a6af12614be59a0afb0be2d2ebad402da3d7ee99fa25ae350bf4a0
|
||||
size 8761
|
||||
3
tests/egui_tests/tests/snapshots/shrink_first_text.png
Normal file
3
tests/egui_tests/tests/snapshots/shrink_first_text.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cf5032b2a08f993ae023934715222fe8d35a3a2e5cc09026d9e7ea3c296a9dc7
|
||||
size 11609
|
||||
3
tests/egui_tests/tests/snapshots/shrink_last_text.png
Normal file
3
tests/egui_tests/tests/snapshots/shrink_last_text.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:84d0c37a198fb56d8608a201dbe7ad19e7de7802bd5110316b36228e14b5f330
|
||||
size 12140
|
||||
3
tests/egui_tests/tests/snapshots/size_max_size.png
Normal file
3
tests/egui_tests/tests/snapshots/size_max_size.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c6a7555290f6121d6e48657e3ae810976b540ee9328909aca2d6c078b3d76ab4
|
||||
size 8735
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:09c5904877c8895d3ad41b7082019ef87db40c6a91ad47401bb9b8ac79a62bdc
|
||||
size 12914
|
||||
oid sha256:f9151f1c9d8a769ac2143a684cabf5d9ed1e453141fff555da245092003f1df1
|
||||
size 13563
|
||||
|
||||
71
tests/egui_tests/tests/test_atoms.rs
Normal file
71
tests/egui_tests/tests/test_atoms.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use egui::{Align, AtomExt as _, Button, Layout, TextWrapMode, Ui, Vec2};
|
||||
use egui_kittest::{HarnessBuilder, SnapshotResult, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn test_atoms() {
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
results.add(single_test("max_width", |ui| {
|
||||
ui.add(Button::new((
|
||||
"max width not grow".atom_max_width(30.0),
|
||||
"other text",
|
||||
)));
|
||||
}));
|
||||
results.add(single_test("max_width_and_grow", |ui| {
|
||||
ui.add(Button::new((
|
||||
"max width and grow".atom_max_width(30.0).atom_grow(true),
|
||||
"other text",
|
||||
)));
|
||||
}));
|
||||
results.add(single_test("shrink_first_text", |ui| {
|
||||
ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate);
|
||||
ui.add(Button::new(("this should shrink", "this shouldn't")));
|
||||
}));
|
||||
results.add(single_test("shrink_last_text", |ui| {
|
||||
ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate);
|
||||
ui.add(Button::new((
|
||||
"this shouldn't shrink",
|
||||
"this should".atom_shrink(true),
|
||||
)));
|
||||
}));
|
||||
results.add(single_test("grow_all", |ui| {
|
||||
ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate);
|
||||
ui.add(Button::new((
|
||||
"I grow".atom_grow(true),
|
||||
"I also grow".atom_grow(true),
|
||||
"I grow as well".atom_grow(true),
|
||||
)));
|
||||
}));
|
||||
results.add(single_test("size_max_size", |ui| {
|
||||
ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate);
|
||||
ui.add(Button::new((
|
||||
"size and max size"
|
||||
.atom_size(Vec2::new(80.0, 80.0))
|
||||
.atom_max_size(Vec2::new(20.0, 20.0)),
|
||||
"other text".atom_grow(true),
|
||||
)));
|
||||
}));
|
||||
}
|
||||
|
||||
fn single_test(name: &str, mut f: impl FnMut(&mut Ui)) -> SnapshotResult {
|
||||
let mut harness = HarnessBuilder::default()
|
||||
.with_size(Vec2::new(400.0, 200.0))
|
||||
.build_ui(move |ui| {
|
||||
ui.label("Normal");
|
||||
let normal_width = ui.horizontal(&mut f).response.rect.width();
|
||||
|
||||
ui.label("Justified");
|
||||
ui.with_layout(
|
||||
Layout::left_to_right(Align::Min).with_main_justify(true),
|
||||
&mut f,
|
||||
);
|
||||
|
||||
ui.label("Shrunk");
|
||||
ui.scope(|ui| {
|
||||
ui.set_max_width(normal_width / 2.0);
|
||||
f(ui);
|
||||
});
|
||||
});
|
||||
|
||||
harness.try_snapshot(name)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use egui::load::SizedTexture;
|
||||
use egui::{
|
||||
include_image, Align, Button, Color32, ColorImage, Direction, DragValue, Event, Grid, Layout,
|
||||
PointerButton, Pos2, Response, Slider, Stroke, StrokeKind, TextWrapMode, TextureHandle,
|
||||
TextureOptions, Ui, UiBuilder, Vec2, Widget as _,
|
||||
include_image, Align, AtomExt as _, AtomLayout, Button, Color32, ColorImage, Direction,
|
||||
DragValue, Event, Grid, IntoAtoms as _, Layout, PointerButton, Pos2, Response, Slider, Stroke,
|
||||
StrokeKind, TextWrapMode, TextureHandle, TextureOptions, Ui, UiBuilder, Vec2, Widget as _,
|
||||
};
|
||||
use egui_kittest::kittest::{by, Node, Queryable as _};
|
||||
use egui_kittest::{Harness, SnapshotResult, SnapshotResults};
|
||||
@@ -92,6 +92,25 @@ fn widget_tests() {
|
||||
},
|
||||
&mut results,
|
||||
);
|
||||
|
||||
let source = include_image!("../../../crates/eframe/data/icon.png");
|
||||
let interesting_atoms = vec![
|
||||
("minimal", ("Hello World!").into_atoms()),
|
||||
(
|
||||
"image",
|
||||
(source.clone().atom_max_height(12.0), "With Image").into_atoms(),
|
||||
),
|
||||
(
|
||||
"multi_grow",
|
||||
("g".atom_grow(true), "2", "g".atom_grow(true), "4").into_atoms(),
|
||||
),
|
||||
];
|
||||
|
||||
for atoms in interesting_atoms {
|
||||
results.add(test_widget_layout(&format!("atoms_{}", atoms.0), |ui| {
|
||||
AtomLayout::new(atoms.1.clone()).ui(ui)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn test_widget(name: &str, mut w: impl FnMut(&mut Ui) -> Response, results: &mut SnapshotResults) {
|
||||
|
||||
Reference in New Issue
Block a user