mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
Merge remote-tracking branch 'origin/master' into default_affects_opacity
This commit is contained in:
15
tests/egui_tests/Cargo.toml
Normal file
15
tests/egui_tests/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "egui_tests"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
egui = { workspace = true, default-features = true }
|
||||
egui_kittest = { workspace = true, features = ["snapshot", "wgpu"] }
|
||||
egui_extras = { workspace = true, features = ["image"]}
|
||||
image = { workspace = true, features = ["png"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
14
tests/egui_tests/tests/regression_tests.rs
Normal file
14
tests/egui_tests/tests/regression_tests.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use egui::{include_image, Image};
|
||||
use egui_kittest::kittest::Queryable as _;
|
||||
use egui_kittest::Harness;
|
||||
|
||||
#[test]
|
||||
fn image_button_should_have_alt_text() {
|
||||
let harness = Harness::new_ui(|ui| {
|
||||
_ = ui.button(
|
||||
Image::new(include_image!("../../../crates/eframe/data/icon.png")).alt_text("Egui"),
|
||||
);
|
||||
});
|
||||
|
||||
harness.get_by_label("Egui");
|
||||
}
|
||||
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:8cfab74bc5a6623f6a6108dd72072fbab68297668c341d95747d9c0eb98edf4c
|
||||
size 262
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aaf9b032037d0708894e568cc8e256b32be9cfb586eaffdc6167143b85562b37
|
||||
size 415016
|
||||
oid sha256:c59df7250a1b9734c6171250131318cafcba309f842a0ed41d99c17b32d8298a
|
||||
size 262
|
||||
|
||||
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:e96c3e61513b05d252e0325dd65fd137c11c7b2151d680402b4f828af74c7a48
|
||||
size 260
|
||||
|
||||
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)
|
||||
}
|
||||
385
tests/egui_tests/tests/test_widgets.rs
Normal file
385
tests/egui_tests/tests/test_widgets.rs
Normal file
@@ -0,0 +1,385 @@
|
||||
use egui::load::SizedTexture;
|
||||
use egui::{
|
||||
include_image, Align, AtomExt as _, AtomLayout, Button, Color32, ColorImage, Direction,
|
||||
DragValue, Event, Grid, IntoAtoms as _, Layout, PointerButton, Response, Slider, Stroke,
|
||||
StrokeKind, TextWrapMode, TextureHandle, TextureOptions, Ui, UiBuilder, Vec2, Widget as _,
|
||||
};
|
||||
use egui_kittest::kittest::{by, Queryable as _};
|
||||
use egui_kittest::{Harness, Node, SnapshotResult, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn widget_tests() {
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
test_widget("button", |ui| ui.button("Button"), &mut results);
|
||||
test_widget(
|
||||
"button_image",
|
||||
|ui| {
|
||||
Button::image_and_text(
|
||||
include_image!("../../../crates/eframe/data/icon.png"),
|
||||
"Button",
|
||||
)
|
||||
.ui(ui)
|
||||
},
|
||||
&mut results,
|
||||
);
|
||||
test_widget(
|
||||
"button_image_shortcut",
|
||||
|ui| {
|
||||
Button::image_and_text(
|
||||
include_image!("../../../crates/eframe/data/icon.png"),
|
||||
"Open",
|
||||
)
|
||||
.shortcut_text("⌘O")
|
||||
.ui(ui)
|
||||
},
|
||||
&mut results,
|
||||
);
|
||||
results.add(VisualTests::test("button_image_shortcut_selected", |ui| {
|
||||
Button::image_and_text(
|
||||
include_image!("../../../crates/eframe/data/icon.png"),
|
||||
"Open",
|
||||
)
|
||||
.shortcut_text("⌘O")
|
||||
.selected(true)
|
||||
.ui(ui)
|
||||
}));
|
||||
|
||||
test_widget(
|
||||
"selectable_value",
|
||||
|ui| ui.selectable_label(false, "Selectable"),
|
||||
&mut results,
|
||||
);
|
||||
test_widget(
|
||||
"selectable_value_selected",
|
||||
|ui| ui.selectable_label(true, "Selectable"),
|
||||
&mut results,
|
||||
);
|
||||
|
||||
test_widget(
|
||||
"checkbox",
|
||||
|ui| ui.checkbox(&mut false, "Checkbox"),
|
||||
&mut results,
|
||||
);
|
||||
test_widget(
|
||||
"checkbox_checked",
|
||||
|ui| ui.checkbox(&mut true, "Checkbox"),
|
||||
&mut results,
|
||||
);
|
||||
test_widget("radio", |ui| ui.radio(false, "Radio"), &mut results);
|
||||
test_widget("radio_checked", |ui| ui.radio(true, "Radio"), &mut results);
|
||||
|
||||
test_widget(
|
||||
"drag_value",
|
||||
|ui| DragValue::new(&mut 12.0).ui(ui),
|
||||
&mut results,
|
||||
);
|
||||
|
||||
test_widget(
|
||||
"text_edit",
|
||||
|ui| {
|
||||
ui.spacing_mut().text_edit_width = 45.0;
|
||||
ui.text_edit_singleline(&mut "Hi!".to_owned())
|
||||
},
|
||||
&mut results,
|
||||
);
|
||||
|
||||
test_widget(
|
||||
"slider",
|
||||
|ui| {
|
||||
ui.spacing_mut().slider_width = 45.0;
|
||||
Slider::new(&mut 12.0, 0.0..=100.0).ui(ui)
|
||||
},
|
||||
&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) {
|
||||
results.add(test_widget_layout(name, &mut w));
|
||||
results.add(VisualTests::test(name, &mut w));
|
||||
}
|
||||
|
||||
fn test_widget_layout(name: &str, mut w: impl FnMut(&mut Ui) -> Response) -> SnapshotResult {
|
||||
let test_size = Vec2::new(110.0, 45.0);
|
||||
|
||||
struct Row {
|
||||
main_dir: Direction,
|
||||
main_align: Align,
|
||||
main_justify: bool,
|
||||
}
|
||||
|
||||
struct Col {
|
||||
cross_align: Align,
|
||||
cross_justify: bool,
|
||||
}
|
||||
|
||||
let mut rows = Vec::new();
|
||||
let mut cols = Vec::new();
|
||||
|
||||
for main_justify in [false, true] {
|
||||
for main_dir in [
|
||||
Direction::LeftToRight,
|
||||
Direction::TopDown,
|
||||
Direction::RightToLeft,
|
||||
Direction::BottomUp,
|
||||
] {
|
||||
for main_align in [Align::Min, Align::Center, Align::Max] {
|
||||
rows.push(Row {
|
||||
main_dir,
|
||||
main_align,
|
||||
main_justify,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for cross_justify in [false, true] {
|
||||
for cross_align in [Align::Min, Align::Center, Align::Max] {
|
||||
cols.push(Col {
|
||||
cross_align,
|
||||
cross_justify,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut harness = Harness::builder().build_ui(|ui| {
|
||||
egui_extras::install_image_loaders(ui.ctx());
|
||||
|
||||
{
|
||||
let mut wrap_test_size = test_size;
|
||||
wrap_test_size.x /= 3.0;
|
||||
ui.heading("Wrapping");
|
||||
|
||||
let modes = [
|
||||
TextWrapMode::Extend,
|
||||
TextWrapMode::Truncate,
|
||||
TextWrapMode::Wrap,
|
||||
];
|
||||
Grid::new("wrapping")
|
||||
.spacing(Vec2::new(test_size.x / 2.0, 4.0))
|
||||
.show(ui, |ui| {
|
||||
for mode in &modes {
|
||||
ui.label(format!("{mode:?}"));
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
for mode in &modes {
|
||||
let (_, rect) = ui.allocate_space(wrap_test_size);
|
||||
|
||||
let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect));
|
||||
child_ui.style_mut().wrap_mode = Some(*mode);
|
||||
w(&mut child_ui);
|
||||
|
||||
ui.painter().rect_stroke(
|
||||
rect,
|
||||
0.0,
|
||||
Stroke::new(1.0, Color32::WHITE),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ui.heading("Layout");
|
||||
Grid::new("layout").striped(true).show(ui, |ui| {
|
||||
ui.label("");
|
||||
for col in &cols {
|
||||
ui.label(format!(
|
||||
"cross_align: {:?}\ncross_justify:{:?}",
|
||||
col.cross_align, col.cross_justify
|
||||
));
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
for row in &rows {
|
||||
ui.label(format!(
|
||||
"main_dir: {:?}\nmain_align: {:?}\nmain_justify: {:?}",
|
||||
row.main_dir, row.main_align, row.main_justify
|
||||
));
|
||||
for col in &cols {
|
||||
let layout = Layout {
|
||||
main_dir: row.main_dir,
|
||||
main_align: row.main_align,
|
||||
main_justify: row.main_justify,
|
||||
cross_align: col.cross_align,
|
||||
cross_justify: col.cross_justify,
|
||||
main_wrap: false,
|
||||
};
|
||||
|
||||
let (_, rect) = ui.allocate_space(test_size);
|
||||
|
||||
let mut child_ui = ui.new_child(UiBuilder::new().layout(layout).max_rect(rect));
|
||||
w(&mut child_ui);
|
||||
|
||||
ui.painter().rect_stroke(
|
||||
rect,
|
||||
0.0,
|
||||
Stroke::new(1.0, Color32::WHITE),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
}
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
harness.fit_contents();
|
||||
harness.try_snapshot(&format!("layout/{name}"))
|
||||
}
|
||||
|
||||
/// Utility to create a snapshot test of the different states of a egui widget.
|
||||
/// This renders each state to a texture to work around the fact only a single widget can be
|
||||
/// hovered / pressed / focused at a time.
|
||||
struct VisualTests<'a> {
|
||||
name: String,
|
||||
w: &'a mut dyn FnMut(&mut Ui) -> Response,
|
||||
results: Vec<(String, ColorImage)>,
|
||||
}
|
||||
|
||||
impl<'a> VisualTests<'a> {
|
||||
pub fn test(name: &str, mut w: impl FnMut(&mut Ui) -> Response) -> SnapshotResult {
|
||||
let mut vis = VisualTests::new(name, &mut w);
|
||||
vis.add_default_states();
|
||||
vis.render()
|
||||
}
|
||||
|
||||
pub fn new(name: &str, w: &'a mut dyn FnMut(&mut Ui) -> Response) -> Self {
|
||||
Self {
|
||||
name: name.to_owned(),
|
||||
w,
|
||||
results: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_default_states(&mut self) {
|
||||
self.add("idle", |_| {});
|
||||
self.add_node("hover", |node| {
|
||||
node.hover();
|
||||
});
|
||||
self.add("pressed", |harness| {
|
||||
harness.get_next().hover();
|
||||
let rect = harness.get_next().rect();
|
||||
harness.input_mut().events.push(Event::PointerButton {
|
||||
button: PointerButton::Primary,
|
||||
pos: rect.center(),
|
||||
pressed: true,
|
||||
modifiers: Default::default(),
|
||||
});
|
||||
});
|
||||
self.add_node("focussed", |node| {
|
||||
node.focus();
|
||||
});
|
||||
self.add_disabled();
|
||||
}
|
||||
|
||||
fn single_test(&mut self, f: impl FnOnce(&mut Harness<'_>), enabled: bool) -> ColorImage {
|
||||
let mut harness = Harness::builder().with_step_dt(0.05).build_ui(|ui| {
|
||||
egui_extras::install_image_loaders(ui.ctx());
|
||||
ui.add_enabled_ui(enabled, |ui| {
|
||||
(self.w)(ui);
|
||||
});
|
||||
});
|
||||
|
||||
harness.fit_contents();
|
||||
|
||||
// Wait for images to load
|
||||
harness.try_run_realtime().ok();
|
||||
|
||||
f(&mut harness);
|
||||
|
||||
harness.step();
|
||||
|
||||
let image = harness.render().expect("Failed to render harness");
|
||||
|
||||
ColorImage::from_rgba_unmultiplied(
|
||||
[image.width() as usize, image.height() as usize],
|
||||
image.as_ref(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, name: &str, test: impl FnOnce(&mut Harness<'_>)) {
|
||||
let image = self.single_test(test, true);
|
||||
self.results.push((name.to_owned(), image));
|
||||
}
|
||||
|
||||
pub fn add_disabled(&mut self) {
|
||||
let image = self.single_test(|_| {}, false);
|
||||
self.results.push(("disabled".to_owned(), image));
|
||||
}
|
||||
|
||||
pub fn add_node(&mut self, name: &str, test: impl FnOnce(&Node<'_>)) {
|
||||
self.add(name, |harness| {
|
||||
let node = harness.get_next();
|
||||
test(&node);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render(self) -> SnapshotResult {
|
||||
let mut results = Some(self.results);
|
||||
let mut images: Option<Vec<(String, TextureHandle, SizedTexture)>> = None;
|
||||
|
||||
let mut harness = Harness::new_ui(|ui| {
|
||||
let results = images.get_or_insert_with(|| {
|
||||
results
|
||||
.take()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(name, image)| {
|
||||
let size = Vec2::new(image.width() as f32, image.height() as f32);
|
||||
let texture_handle =
|
||||
ui.ctx()
|
||||
.load_texture(name.clone(), image, TextureOptions::default());
|
||||
let texture = SizedTexture::new(texture_handle.id(), size);
|
||||
(name.clone(), texture_handle, texture)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
Grid::new("results").show(ui, |ui| {
|
||||
for (name, _, image) in results {
|
||||
ui.label(&*name);
|
||||
|
||||
ui.scope(|ui| {
|
||||
ui.image(*image);
|
||||
});
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
harness.fit_contents();
|
||||
|
||||
harness.try_snapshot(&format!("visuals/{}", self.name))
|
||||
}
|
||||
}
|
||||
|
||||
trait HarnessExt {
|
||||
fn get_next(&self) -> Node<'_>;
|
||||
}
|
||||
|
||||
impl HarnessExt for Harness<'_> {
|
||||
fn get_next(&self) -> Node<'_> {
|
||||
self.get_all(by()).next().unwrap()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user