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

Merge branch 'main' into common-panels

This commit is contained in:
Emil Ernerfeldt
2025-11-16 11:31:23 +01:00
575 changed files with 22977 additions and 10608 deletions

View 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

View File

@@ -0,0 +1,120 @@
use egui::accesskit::Role;
use egui::{Align, Color32, Image, Label, Layout, RichText, Sense, TextWrapMode, include_image};
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable as _;
#[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");
}
#[test]
fn hovering_should_preserve_text_format() {
let mut harness = Harness::builder().with_size((200.0, 70.0)).build_ui(|ui| {
ui.add(
Label::new(
RichText::new("Long text that should be elided and has lots of styling and is long enough to have multiple lines.")
.italics()
.underline()
.color(Color32::LIGHT_BLUE),
)
.wrap_mode(TextWrapMode::Truncate),
);
});
harness.get_by_label_contains("Long text").hover();
harness.run_steps(5);
harness.snapshot("hovering_should_preserve_text_format");
}
#[test]
fn text_edit_rtl() {
let mut text = "hello ".to_owned();
let mut harness = Harness::builder().with_size((200.0, 50.0)).build_ui(|ui| {
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
_ = ui.button("right");
ui.add(
egui::TextEdit::singleline(&mut text)
.desired_width(10.0)
.clip_text(false),
);
_ = ui.button("left");
});
});
harness.get_by_role(Role::TextInput).focus();
harness.step();
harness.snapshot("text_edit_rtl_0");
harness.get_by_role(Role::TextInput).type_text("world");
for i in 1..3 {
harness.step();
harness.snapshot(format!("text_edit_rtl_{i}"));
}
}
#[test]
fn combobox_should_have_value() {
let harness = Harness::new_ui(|ui| {
egui::ComboBox::from_label("Select an option")
.selected_text("Option 1")
.show_ui(ui, |_ui| {});
});
assert_eq!(
harness.get_by_label("Select an option").value().as_deref(),
Some("Option 1")
);
}
/// This test ensures that `ui.response().interact(...)` works correctly.
///
/// This was broken, because there was an optimization in [`egui::Response::interact`]
/// which caused the [`Sense`] of the original response to flip-flop between `click` and `hover`
/// between frames.
///
/// See <https://github.com/emilk/egui/pull/7713> for more details.
#[test]
fn interact_on_ui_response_should_be_stable() {
let mut first_frame = true;
let mut click_count = 0;
let mut harness = Harness::new_ui(|ui| {
let ui_response = ui.response();
if !first_frame {
assert!(
ui_response.sense.contains(Sense::click()),
"ui.response() didn't have click sense even though we called interact(Sense::click()) last frame"
);
}
// Add a label so we have something to click with kittest
ui.add(
Label::new("senseless label")
.sense(Sense::hover())
.selectable(false),
);
let click_response = ui_response.interact(Sense::click());
if click_response.clicked() {
click_count += 1;
}
first_frame = false;
});
for i in 0..=10 {
harness.run_steps(i);
harness.get_by_label("senseless label").click();
}
drop(harness);
assert_eq!(click_count, 10, "We missed some clicks!");
}

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5befd84158b582c79a968f36e43c7017187b364824eb4470b048d133e62f9360
size 1600

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:469a1b1faa71da472c07bcc7103933db9964440a4c49dc596220f070e9b483f5
size 14375

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cac533a01c65c8eef093efcd4c9036da50f898ea2436612990f4c2365c98ad83
size 12126

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:070638cd5c174200161498123a612e4a58d58517b539e91e289d1e3dd38670bb
size 388531

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac8885d6e5325b5f1f0ccb11f185df5c4937b66c80159aa8cf53930f5c8045e2
size 395599

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:05b891dd999050150f4cd4226a1c68673b352f04d08d405751d66c698c7711c5
size 309736

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6e8a7835e3fd22aafc22b6c536051367e2c27c8607fbe5d8b6b6cf6d0ba3d54f
size 332771

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59cbb416865f8bede12b449d8e65baddb6949df017face2782a3492de7a058e3
size 355276

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cad07a16b6f0eb8c7647c7d0500ea99c68dddf2eef892b4df48aa20f581e8a85
size 438837

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4747efdf758e7e8e2d7f3954d9595dfd45d3b4b86923b8ff39c8a96002bb4825
size 408726

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4a3ca4b3a47ff516f9b05799cdf5f92845ae1e058728d635986cc61b7317f110
size 437102

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c168f197bec3bc780553db0a47f464f8d76afc606e28e2545ecd91f174abe551
size 249781

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:39f1985a3a975b1b9a179d3b1bd5832e0b4c30d10232babf2e4736f55b43989f
size 350051

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18a38fe66d5ac8f2cf5c109f1cd9c29951e5bf8428b6bf0d4587dfc8f8c5c890
size 370205

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bfc900ea84b408564652df487e705311b164d9bd3ff5631c3cebb83b06497a7b
size 410131

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9d08ce85c9210a7d9046480ab208040e5ba399c40acaecf5cb43f807534bce9
size 423523

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b402195e54bdbd09985e4b30a025083298f29ab747b809fbb864c8dfef0975eb
size 289714

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ed6af3a92790e07b71e71637b5d6bb45d55a7d26738d438714aca64d7f4534c
size 245394

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0f107d95fee9a5fb5fbfd2422452e1820738a84c81774587dbfa8153e91e4c73
size 414552

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6c1aebada9349f8cb4046469b0a6f9796a21f88b6724bd85cd832a40b8007409
size 540527

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:685de2e33ff26aafa87426bcda18bb9963c2deb2a811cd0aae4450af0e245a06
size 390735

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e2dc33b2d4caddac86dd8649d09ff3d57187a0152240f824a6aa170a35dd719
size 8570

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36ab2c05eade94dcfe524651a0954c122aea754976af94a73a7efd950055c9eb
size 8574

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c2ba53264abcaa2ee79858ddbd475ed35aa28d146b846cbc080c26911d373ea6
size 11881

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:082d80ae144338dce45169c02038b6dc5b75d7b6d93c2a8213ddbb2a8784cc92
size 12435

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e455b08f4674a9326682771f12456a71cc22dfd733ee965fdbbb5582cba0380
size 8176

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc
size 8963

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8
size 1700

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35
size 1305

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d36fb3c42b7f74e0b6dd8e73b9f8455e7fac035f5979cb93769e6a3f5453fdbc
size 7239

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc
size 8963

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8
size 1700

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35
size 1305

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89d9445d7aaff34ace5322af6efc308f261cf5becfdd11aa7fec016236ecfa84
size 7064

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc
size 8963

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8
size 1700

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35
size 1305

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b7487a1b77fd2db2493bca7d42128aad7a0049962599c8b5add2b7b25376a37d
size 9381

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc
size 8963

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8
size 1700

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35
size 1305

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1f614fb9b2aba8cc5a6997a519ca92083fb4378a36f335570d9e870159267f40
size 9495

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc
size 8963

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8
size 1700

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35
size 1305

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5230bd00a43990b30e523528b609e04916dab3cf87a59d56e39dcd5926f47aa5
size 8838

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:336581facb1ec989a43291ed76bd8ddb552c46137a75601f466e6dc4dae77278
size 2395

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3b0684c53a20eaa90a9dccef8ac3eaa2a6eede7c770e7bbbba6d995f43584d99
size 2353

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:38f325f2e741f18f897502c176f9a7efe276e9adab41a144511121dd8b8a3073
size 3079

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c05992e16c1abf6d174fed73d19cad6bb2266e0adb87b8232e765d75fcf3f14
size 10310

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7681d33a5a764187c084c966a4e47063136e2832094c44f62718447b1b3027ec
size 11292

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b99a82e9f3dfa24c079545272d680b55c4285c276befa0efc492fe273422f541
size 14195

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cdf079228b762949dbc67308103f8fe1328b6c0175f312ccc492d4e86d42127b
size 13868

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0bafe4c157696bfb52940b69501416d4da0b4eab52f34f52220d2e9ed01357cf
size 12901

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72175bf108135b422d978b701d29e6d9a5348c536e25abc924234bc11b6b7f21
size 14016

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:129121534b5f1a2a668898ebb3560820fe50aa4d3546ef46cc764d5513787e9e
size 7529

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9999c7921f8b277f456189ce0f1185120b4cde7c9a01485a5a7d83f12e95527
size 11710

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8fec1fd9f80e5fa17b1cda690c0856e7e5fd674d113a10b1d60b14f5a6c6dd6b
size 12401

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac18e2eef000a80858b2d0811f9ee31304c6ff96f7a91dc60cc1a404ae28ce38
size 13246

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c11fe0399c85db5a618580ec4c1f2fe76176c6ea0ead3710a430d9a2bf8acc5d
size 13352

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a41bf44780feefa108a230ae617830445791bde16d712ac35530350d5d009481
size 9045

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a103b51df184d5480438e8b537106432205a6d86f2927ab1bd507fe8ed3bb29b
size 7656

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf4236b1a8f63d184cd780c334d9f996e4d47817a96a29f0d81658d2d897597f
size 10529

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c7a63953853f526b83f80d63335b03e60258ea9a3416d19f8ed57d746b5c551d
size 21557

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1f7d802a4de7e30f8d254cab6d9ca127866c104c1738103bc4a579917e8f42d3
size 9850

View File

@@ -0,0 +1,121 @@
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)
}
#[test]
fn test_intrinsic_size() {
let widgets = [Ui::button, Ui::label];
for widget in widgets {
let mut intrinsic_size = None;
for wrapping in [
TextWrapMode::Extend,
TextWrapMode::Wrap,
TextWrapMode::Truncate,
] {
_ = HarnessBuilder::default()
.with_size(Vec2::new(100.0, 100.0))
.build_ui(|ui| {
ui.style_mut().wrap_mode = Some(wrapping);
let response = widget(
ui,
"Hello world this is a long text that should be wrapped.",
);
if let Some(current_intrinsic_size) = intrinsic_size {
assert_eq!(
Some(current_intrinsic_size),
response.intrinsic_size,
"For wrapping: {wrapping:?}"
);
}
assert!(
response.intrinsic_size.is_some(),
"intrinsic_size should be set for `Button`"
);
intrinsic_size = response.intrinsic_size;
if wrapping == TextWrapMode::Extend {
assert_eq!(Some(response.rect.size()), response.intrinsic_size);
}
});
}
}
}
#[test]
fn test_button_shortcut_text() {
let mut harness = HarnessBuilder::default().build_ui(|ui| {
ui.add(egui::Button::new("Click me").shortcut_text(("1", "2", "3")));
});
harness.run();
harness.fit_contents();
harness.snapshot("button_shortcut");
}

View File

@@ -0,0 +1,76 @@
use egui::{TextWrapMode, Vec2, containers::Sides};
use egui_kittest::{Harness, SnapshotResults};
#[test]
fn sides_container_tests() {
let mut results = SnapshotResults::new();
test_variants("default", |sides| sides, &mut results);
test_variants(
"shrink_left",
|sides| sides.shrink_left().truncate(),
&mut results,
);
test_variants(
"shrink_right",
|sides| sides.shrink_right().truncate(),
&mut results,
);
test_variants(
"wrap_left",
|sides| sides.shrink_left().wrap_mode(TextWrapMode::Wrap),
&mut results,
);
test_variants(
"wrap_right",
|sides| sides.shrink_right().wrap_mode(TextWrapMode::Wrap),
&mut results,
);
}
fn test_variants(
name: &str,
mut create_sides: impl FnMut(Sides) -> Sides,
results: &mut SnapshotResults,
) {
for (variant_name, left_text, right_text, fit_contents) in [
("short", "Left", "Right", false),
(
"long",
"Very long left content that should not fit.",
"Very long right text that should also not fit.",
false,
),
("short_fit_contents", "Left", "Right", true),
(
"long_fit_contents",
"Very long left content that should not fit.",
"Very long right text that should also not fit.",
true,
),
] {
let mut harness = Harness::builder()
.with_size(Vec2::new(400.0, 50.0))
.build_ui(|ui| {
create_sides(Sides::new()).show(
ui,
|left| {
left.label(left_text);
},
|right| {
right.label(right_text);
},
);
});
if fit_contents {
harness.fit_contents();
}
results.add(harness.try_snapshot(format!("sides/{name}_{variant_name}")));
}
}

View File

@@ -0,0 +1,419 @@
use egui::accesskit::Role;
use egui::load::SizedTexture;
use egui::{
Align, AtomExt as _, AtomLayout, Button, Color32, ColorImage, Direction, DragValue, Event,
Grid, IntoAtoms as _, Layout, PointerButton, Response, Slider, Stroke, StrokeKind, TextEdit,
TextWrapMode, TextureHandle, TextureOptions, Ui, UiBuilder, Vec2, Widget as _, include_image,
};
use egui_kittest::kittest::{Queryable as _, by};
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(
"text_edit_clip",
|ui| {
ui.spacing_mut().text_edit_width = 45.0;
TextEdit::singleline(&mut "This is a very very long text".to_owned())
.clip_text(true)
.ui(ui)
},
&mut results,
);
test_widget(
"text_edit_no_clip",
|ui| {
ui.spacing_mut().text_edit_width = 45.0;
TextEdit::singleline(&mut "This is a very very long text".to_owned())
.clip_text(false)
.ui(ui)
},
&mut results,
);
test_widget(
"text_edit_placeholder_clip",
|ui| {
ui.spacing_mut().text_edit_width = 45.0;
TextEdit::singleline(&mut String::new())
.hint_text("This is a very very long placeholder")
.clip_text(true)
.ui(ui)
},
&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_widget().hover();
let rect = harness.get_next_widget().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_widget();
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_widget(&self) -> Node<'_>;
}
impl HarnessExt for Harness<'_> {
fn get_next_widget(&self) -> Node<'_> {
self.get_all(by().predicate(|node| node.role() != Role::GenericContainer))
.next()
.unwrap()
}
}

View File

@@ -2,19 +2,16 @@
name = "test_egui_extras_compilation"
version = "0.1.0"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
edition = "2024"
rust-version = "1.88"
publish = false
[lints]
workspace = true
[package.metadata.cargo-machete]
ignored = [
"eframe",
"egui_extras",
] # We don't use them, just check that things compile
ignored = ["eframe", "egui_extras"] # We don't use them, just check that things compile
[dependencies]
eframe = { workspace = true, features = ["default", "persistence"] }
egui_extras = { workspace = true }
egui_extras.workspace = true

View File

@@ -3,8 +3,8 @@ name = "test_inline_glow_paint"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
edition = "2024"
rust-version = "1.88"
publish = false
[lints]
@@ -14,10 +14,7 @@ workspace = true
[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { workspace = true, features = ["auto-color", "humantime"] }

View File

@@ -29,7 +29,7 @@ impl eframe::App for MyTestApp {
use glow::HasContext as _;
let gl = frame.gl().unwrap();
#[allow(unsafe_code)]
#[expect(unsafe_code)]
unsafe {
gl.disable(glow::SCISSOR_TEST);
gl.viewport(0, 0, 100, 100);

View File

@@ -3,8 +3,8 @@ name = "test_size_pass"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
edition = "2024"
rust-version = "1.88"
publish = false
[lints]
@@ -15,10 +15,7 @@ wgpu = ["eframe/wgpu"]
[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { workspace = true, features = ["auto-color", "humantime"] }

View File

@@ -3,8 +3,8 @@ name = "test_ui_stack"
version = "0.1.0"
authors = ["Antoine Beyeler <abeyeler@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
edition = "2024"
rust-version = "1.88"
publish = false
[lints]
@@ -21,7 +21,4 @@ eframe = { workspace = true, features = [
# For image support:
egui_extras = { workspace = true, features = ["default", "image", "serde"] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
] }
env_logger = { workspace = true, features = ["auto-color", "humantime"] }

View File

@@ -3,8 +3,8 @@ name = "test_viewports"
version = "0.1.0"
authors = ["konkitoman"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
edition = "2024"
rust-version = "1.88"
publish = false
[lints]
@@ -15,10 +15,7 @@ wgpu = ["eframe/wgpu"]
[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { workspace = true, features = ["auto-color", "humantime"] }

View File

@@ -4,7 +4,7 @@
use std::sync::Arc;
use eframe::egui;
use egui::{mutex::RwLock, Id, InnerResponse, UiBuilder, ViewportBuilder, ViewportId};
use egui::{Id, InnerResponse, UiBuilder, ViewportBuilder, ViewportId, mutex::RwLock};
// Drag-and-drop between windows is not yet implemented, but if you wanna work on it, enable this:
pub const DRAG_AND_DROP_TEST: bool = false;
@@ -247,8 +247,12 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc<RwLock<ViewportState>>], close_
if let Some(monitor_size) = ctx.input(|i| i.viewport().monitor_size) {
ui.label(format!("monitor_size: {monitor_size:?} (points)"));
}
if let Some(screen_rect) = ui.input(|i| i.raw.screen_rect) {
ui.label(format!("Screen rect size: Pos: {:?}", screen_rect.size()));
if let Some(viewport_rect) = ui.input(|i| i.raw.screen_rect) {
ui.label(format!(
"Viewport Rect: Pos: {:?}, Size: {:?} (points)",
viewport_rect.min,
viewport_rect.size()
));
}
if let Some(inner_rect) = ctx.input(|i| i.viewport().inner_rect) {
ui.label(format!(
@@ -367,6 +371,7 @@ fn drag_and_drop_test(ui: &mut egui::Ui) {
assert!(col <= COLS, "The col should be less then: {COLS}");
// Should be a better way to do this!
#[expect(clippy::iter_over_hash_type)]
for container_data in self.containers_data.values_mut() {
for ids in container_data {
ids.retain(|i| *i != id);