From 011e0d261a36c42d3988010dbca2921c1f2bc051 Mon Sep 17 00:00:00 2001 From: Zach Bateman <39414345+zachbateman@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:27:26 -0500 Subject: [PATCH 01/56] egui_extras: Enable setting DatePickerButton start and end year explicitly (#7061) Add the ability to set the `DatePickerButton`'s start and end years via new `start_year` and `end_year` methods. Continue to use the existing today - 100 years and today + 10 years behavior if a year is not specified. * This more fully closes and expands on . * [x] I have followed the instructions in the PR template --- crates/egui_extras/src/datepicker/button.rs | 15 +++++++++++++++ crates/egui_extras/src/datepicker/popup.rs | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/egui_extras/src/datepicker/button.rs b/crates/egui_extras/src/datepicker/button.rs index 601c16f67..0ec0680f8 100644 --- a/crates/egui_extras/src/datepicker/button.rs +++ b/crates/egui_extras/src/datepicker/button.rs @@ -1,6 +1,7 @@ use super::popup::DatePickerPopup; use chrono::NaiveDate; use egui::{Area, Button, Frame, InnerResponse, Key, Order, RichText, Ui, Widget}; +use std::ops::RangeInclusive; #[derive(Default, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -19,6 +20,7 @@ pub struct DatePickerButton<'a> { show_icon: bool, format: String, highlight_weekends: bool, + start_end_years: Option>, } impl<'a> DatePickerButton<'a> { @@ -33,6 +35,7 @@ impl<'a> DatePickerButton<'a> { show_icon: true, format: "%Y-%m-%d".to_owned(), highlight_weekends: true, + start_end_years: None, } } @@ -101,6 +104,17 @@ impl<'a> DatePickerButton<'a> { self.highlight_weekends = highlight_weekends; self } + + /// Set the start and end years for the date picker. (Default: today's year - 100 to today's year + 10) + /// This will limit the years you can choose from in the dropdown to the specified range. + /// + /// For example, if you want to provide the range of years from 2000 to 2035, you can use: + /// `start_end_years(2000..=2035)`. + #[inline] + pub fn start_end_years(mut self, start_end_years: RangeInclusive) -> Self { + self.start_end_years = Some(start_end_years); + self + } } impl Widget for DatePickerButton<'_> { @@ -167,6 +181,7 @@ impl Widget for DatePickerButton<'_> { calendar: self.calendar, calendar_week: self.calendar_week, highlight_weekends: self.highlight_weekends, + start_end_years: self.start_end_years, } .draw(ui) }) diff --git a/crates/egui_extras/src/datepicker/popup.rs b/crates/egui_extras/src/datepicker/popup.rs index 79f3d37f6..c63de3a91 100644 --- a/crates/egui_extras/src/datepicker/popup.rs +++ b/crates/egui_extras/src/datepicker/popup.rs @@ -35,6 +35,7 @@ pub(crate) struct DatePickerPopup<'a> { pub calendar: bool, pub calendar_week: bool, pub highlight_weekends: bool, + pub start_end_years: Option>, } impl DatePickerPopup<'_> { @@ -84,7 +85,11 @@ impl DatePickerPopup<'_> { ComboBox::from_id_salt("date_picker_year") .selected_text(popup_state.year.to_string()) .show_ui(ui, |ui| { - for year in today.year() - 100..today.year() + 10 { + let (start_year, end_year) = match &self.start_end_years { + Some(range) => (*range.start(), *range.end()), + None => (today.year() - 100, today.year() + 10), + }; + for year in start_year..=end_year { if ui .selectable_value( &mut popup_state.year, From 8c2df4802c36e3937c8af8ba90862c09c87f30af Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 16 Jun 2025 19:36:19 +0200 Subject: [PATCH 02/56] Add back old `Tooltip::new` (#7156) I was a bit too hasty in https://github.com/emilk/egui/pull/7151 and changed a public API in a breaking way, for no good reason --- crates/egui/src/containers/old_popup.rs | 6 +++--- crates/egui/src/containers/tooltip.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/old_popup.rs b/crates/egui/src/containers/old_popup.rs index cc75494e1..3ddf77bf6 100644 --- a/crates/egui/src/containers/old_popup.rs +++ b/crates/egui/src/containers/old_popup.rs @@ -61,7 +61,7 @@ pub fn show_tooltip_at_pointer( widget_id: Id, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { - Tooltip::new(ctx.clone(), parent_layer, widget_id, PopupAnchor::Pointer) + Tooltip::always_open(ctx.clone(), parent_layer, widget_id, PopupAnchor::Pointer) .gap(12.0) .show(add_contents) .map(|response| response.inner) @@ -78,7 +78,7 @@ pub fn show_tooltip_for( widget_rect: &Rect, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { - Tooltip::new(ctx.clone(), parent_layer, widget_id, *widget_rect) + Tooltip::always_open(ctx.clone(), parent_layer, widget_id, *widget_rect) .show(add_contents) .map(|response| response.inner) } @@ -94,7 +94,7 @@ pub fn show_tooltip_at( suggested_position: Pos2, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { - Tooltip::new(ctx.clone(), parent_layer, widget_id, suggested_position) + Tooltip::always_open(ctx.clone(), parent_layer, widget_id, suggested_position) .show(add_contents) .map(|response| response.inner) } diff --git a/crates/egui/src/containers/tooltip.rs b/crates/egui/src/containers/tooltip.rs index a6cb3199e..2060c61cf 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -17,7 +17,25 @@ pub struct Tooltip<'a> { impl Tooltip<'_> { /// Show a tooltip that is always open. + #[deprecated = "Use `Tooltip::always_open` instead."] pub fn new( + parent_widget: Id, + ctx: Context, + anchor: impl Into, + parent_layer: LayerId, + ) -> Self { + Self { + popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer) + .kind(PopupKind::Tooltip) + .gap(4.0) + .sense(Sense::hover()), + parent_layer, + parent_widget, + } + } + + /// Show a tooltip that is always open. + pub fn always_open( ctx: Context, parent_layer: LayerId, parent_widget: Id, From 0152a875192490d482aed3890f682ec406968ea3 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 17 Jun 2025 12:17:38 +0200 Subject: [PATCH 03/56] Create custom `egui_kittest::Node` (#7138) This adds a custom Node struct with proper support for egui types (`Key`, `Modifiers`, `egui::Event`, `Rect`) instead of needing to use the kittest / accesskit types. I also changed the `click` function to do a proper mouse move / mouse down instead of the accesskit click. Also added `accesskit_click` to trigger the accesskit event. This resulted in some changed snapshots, since the elements are now hovered. Also renamed `press_key` to `key_press` for consistency with `key_down/key_up`. Also removed the Deref to the AccessKit Node, to make it clearer when to expect egui and when to expect accesskit types. * Closes #5705 * [x] I have followed the instructions in the PR template --- Cargo.lock | 2 +- .../egui_demo_app/tests/snapshots/clock.png | 4 +- .../tests/snapshots/custom3d.png | 4 +- .../tests/snapshots/easymarkeditor.png | 4 +- .../tests/snapshots/imageviewer.png | 4 +- crates/egui_demo_app/tests/test_demo_app.rs | 2 +- .../src/demo/demo_app_windows.rs | 10 +- crates/egui_demo_lib/src/demo/modals.rs | 6 +- crates/egui_demo_lib/src/demo/text_edit.rs | 7 +- crates/egui_demo_lib/src/rendering_test.rs | 5 +- crates/egui_kittest/README.md | 6 +- crates/egui_kittest/src/event.rs | 194 ------------------ crates/egui_kittest/src/lib.rs | 183 +++++++++++++---- crates/egui_kittest/src/node.rs | 162 +++++++++++++++ crates/egui_kittest/tests/menu.rs | 24 +-- crates/egui_kittest/tests/popup.rs | 2 +- crates/egui_kittest/tests/regression_tests.rs | 13 +- .../tests/snapshots/readme_example.png | 4 +- crates/egui_kittest/tests/tests.rs | 14 +- tests/egui_tests/tests/test_widgets.rs | 14 +- 20 files changed, 359 insertions(+), 305 deletions(-) delete mode 100644 crates/egui_kittest/src/event.rs create mode 100644 crates/egui_kittest/src/node.rs diff --git a/Cargo.lock b/Cargo.lock index 9a441cb30..abd6aca03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2422,7 +2422,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kittest" version = "0.1.0" -source = "git+https://github.com/rerun-io/kittest?branch=main#679f9ade828021295c5f86f38275d9271d001004" +source = "git+https://github.com/rerun-io/kittest?branch=main#91bf0fd98b5afe04427bb3aea4c68c6e0034b4bd" dependencies = [ "accesskit", "accesskit_consumer", diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 2b0bfc6c1..31bb331ef 100644 --- a/crates/egui_demo_app/tests/snapshots/clock.png +++ b/crates/egui_demo_app/tests/snapshots/clock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61db3807f755ac832ba069e1adaf8aeb550c88737b4907748667a271ae29863d -size 334792 +oid sha256:0bd688ff74f9a096edab545fbcbf61b61a464183da066ae4a120ce1e2abf3e7b +size 334969 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index ce19412f7..2458cd8ba 100644 --- a/crates/egui_demo_app/tests/snapshots/custom3d.png +++ b/crates/egui_demo_app/tests/snapshots/custom3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21e0a6cdf175606a513ddf410ae1b873a9817305ecad403116fad3c6ff795fa3 -size 92185 +oid sha256:c80c4ae4c2bfbc5c91e9cd94213a4f87646fe910b4a7c747531a1efcf23def47 +size 92364 diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index 7666b658e..08f5fc98f 100644 --- a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png +++ b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e6a383dca7e91d07df4bf501e2de13d046f04546a08d026efe3f82fc96b6e29 -size 178887 +oid sha256:8cf6d0b20f127f22d49daefed27fc2d0ca43d645fe1486cf7f6fcbb676bdec82 +size 179065 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index d5bde1f98..a13af2e71 100644 --- a/crates/egui_demo_app/tests/snapshots/imageviewer.png +++ b/crates/egui_demo_app/tests/snapshots/imageviewer.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2fae780123389ca0affa762a5c031b84abcdd31c7a830d485c907c8c370b006 -size 100780 +oid sha256:0e37b3ce49c9ccc1a64beb58b176e23ab6c1fa2d897f676b0de85e510e6bfa85 +size 100845 diff --git a/crates/egui_demo_app/tests/test_demo_app.rs b/crates/egui_demo_app/tests/test_demo_app.rs index 8b0b272fa..65afff10f 100644 --- a/crates/egui_demo_app/tests/test_demo_app.rs +++ b/crates/egui_demo_app/tests/test_demo_app.rs @@ -55,7 +55,7 @@ fn test_demo_app() { harness .get_by_role_and_label(Role::TextInput, "URI:") .focus(); - harness.press_key_modifiers(egui::Modifiers::COMMAND, egui::Key::A); + harness.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A); harness .get_by_role_and_label(Role::TextInput, "URI:") diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index cc43645c6..6e9a92ef4 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -371,8 +371,8 @@ fn file_menu_button(ui: &mut Ui) { #[cfg(test)] mod tests { use crate::{demo::demo_app_windows::DemoGroups, Demo as _}; - use egui::Vec2; - use egui_kittest::kittest::Queryable as _; + + use egui_kittest::kittest::{NodeT as _, Queryable as _}; use egui_kittest::{Harness, SnapshotOptions, SnapshotResults}; #[test] @@ -399,12 +399,12 @@ mod tests { demo.show(ctx, &mut true); }); - let window = harness.node().children().next().unwrap(); + let window = harness.queryable_node().children().next().unwrap(); // TODO(lucasmerlin): Windows should probably have a label? //let window = harness.get_by_label(name); - let size = window.raw_bounds().expect("window bounds").size(); - harness.set_size(Vec2::new(size.width as f32, size.height as f32)); + let size = window.rect().size(); + harness.set_size(size); // Run the app for some more frames... harness.run_ok(); diff --git a/crates/egui_demo_lib/src/demo/modals.rs b/crates/egui_demo_lib/src/demo/modals.rs index fcb33f0bb..5fb1548e3 100644 --- a/crates/egui_demo_lib/src/demo/modals.rs +++ b/crates/egui_demo_lib/src/demo/modals.rs @@ -190,7 +190,7 @@ mod tests { assert!(harness.ctx.memory(|mem| mem.any_popup_open())); assert!(harness.state().user_modal_open); - harness.press_key(Key::Escape); + harness.key_press(Key::Escape); harness.run_ok(); assert!(!harness.ctx.memory(|mem| mem.any_popup_open())); assert!(harness.state().user_modal_open); @@ -214,7 +214,7 @@ mod tests { assert!(harness.state().user_modal_open); assert!(harness.state().save_modal_open); - harness.press_key(Key::Escape); + harness.key_press(Key::Escape); harness.run(); assert!(harness.state().user_modal_open); @@ -267,7 +267,7 @@ mod tests { harness.run_ok(); - harness.get_by_label("Yes Please").simulate_click(); + harness.get_by_label("Yes Please").click(); harness.run_ok(); diff --git a/crates/egui_demo_lib/src/demo/text_edit.rs b/crates/egui_demo_lib/src/demo/text_edit.rs index 685a9c38f..4ef34a51e 100644 --- a/crates/egui_demo_lib/src/demo/text_edit.rs +++ b/crates/egui_demo_lib/src/demo/text_edit.rs @@ -113,8 +113,8 @@ impl crate::View for TextEditDemo { #[cfg(test)] mod tests { - use egui::{accesskit, CentralPanel}; - use egui_kittest::kittest::{Key, Queryable as _}; + use egui::{accesskit, CentralPanel, Key, Modifiers}; + use egui_kittest::kittest::Queryable as _; use egui_kittest::Harness; #[test] @@ -133,8 +133,9 @@ mod tests { let text_edit = harness.get_by_role(accesskit::Role::TextInput); assert_eq!(text_edit.value().as_deref(), Some("Hello, world!")); + text_edit.focus(); - text_edit.key_combination(&[Key::Command, Key::A]); + harness.key_press_modifiers(Modifiers::COMMAND, Key::A); text_edit.type_text("Hi "); harness.run(); diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index 5f0e91bc5..32e51a82a 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -737,8 +737,9 @@ mod tests { }); { - // Expand color-test collapsing header - harness.get_by_label("Color test").click(); + // Expand color-test collapsing header. We accesskit-click since collapsing header + // might not be on screen at this point. + harness.get_by_label("Color test").click_accesskit(); harness.run(); } diff --git a/crates/egui_kittest/README.md b/crates/egui_kittest/README.md index ff071c9f4..31a55f618 100644 --- a/crates/egui_kittest/README.md +++ b/crates/egui_kittest/README.md @@ -10,7 +10,7 @@ Ui testing library for egui, based on [kittest](https://github.com/rerun-io/kitt ## Example usage ```rust use egui::accesskit::Toggled; -use egui_kittest::{Harness, kittest::Queryable}; +use egui_kittest::{Harness, kittest::{Queryable, NodeT}}; fn main() { let mut checked = false; @@ -21,13 +21,13 @@ fn main() { let mut harness = Harness::new_ui(app); let checkbox = harness.get_by_label("Check me!"); - assert_eq!(checkbox.toggled(), Some(Toggled::False)); + assert_eq!(checkbox.accesskit_node().toggled(), Some(Toggled::False)); checkbox.click(); harness.run(); let checkbox = harness.get_by_label("Check me!"); - assert_eq!(checkbox.toggled(), Some(Toggled::True)); + assert_eq!(checkbox.accesskit_node().toggled(), Some(Toggled::True)); // Shrink the window size to the smallest size possible harness.fit_contents(); diff --git a/crates/egui_kittest/src/event.rs b/crates/egui_kittest/src/event.rs deleted file mode 100644 index e756d4dc9..000000000 --- a/crates/egui_kittest/src/event.rs +++ /dev/null @@ -1,194 +0,0 @@ -use egui::Event::PointerButton; -use egui::{Event, Modifiers, Pos2}; -use kittest::{ElementState, MouseButton, SimulatedEvent}; - -#[derive(Default)] -pub(crate) struct EventState { - last_mouse_pos: Pos2, -} - -impl EventState { - /// Map the kittest event to an egui event, add it to the input and update the modifiers. - /// This function accesses `egui::RawInput::modifiers`. Make sure it is not reset after each - /// frame (Since we use [`egui::RawInput::take`], this should be fine). - pub fn update(&mut self, event: kittest::Event, input: &mut egui::RawInput) { - if let Some(event) = self.kittest_event_to_egui(&mut input.modifiers, event) { - input.events.push(event); - } - } - - fn kittest_event_to_egui( - &mut self, - modifiers: &mut Modifiers, - event: kittest::Event, - ) -> Option { - match event { - kittest::Event::ActionRequest(e) => Some(Event::AccessKitActionRequest(e)), - kittest::Event::Simulated(e) => match e { - SimulatedEvent::CursorMoved { position } => { - self.last_mouse_pos = Pos2::new(position.x as f32, position.y as f32); - Some(Event::PointerMoved(Pos2::new( - position.x as f32, - position.y as f32, - ))) - } - SimulatedEvent::MouseInput { state, button } => { - pointer_button_to_egui(button).map(|button| PointerButton { - button, - modifiers: *modifiers, - pos: self.last_mouse_pos, - pressed: matches!(state, ElementState::Pressed), - }) - } - SimulatedEvent::Ime(text) => Some(Event::Text(text)), - SimulatedEvent::KeyInput { state, key } => { - match key { - kittest::Key::Alt => { - modifiers.alt = matches!(state, ElementState::Pressed); - } - kittest::Key::Command => { - modifiers.command = matches!(state, ElementState::Pressed); - } - kittest::Key::Control => { - modifiers.ctrl = matches!(state, ElementState::Pressed); - } - kittest::Key::Shift => { - modifiers.shift = matches!(state, ElementState::Pressed); - } - _ => {} - } - kittest_key_to_egui(key).map(|key| Event::Key { - key, - modifiers: *modifiers, - pressed: matches!(state, ElementState::Pressed), - repeat: false, - physical_key: None, - }) - } - }, - } - } -} - -fn kittest_key_to_egui(value: kittest::Key) -> Option { - use egui::Key as EKey; - use kittest::Key; - match value { - Key::ArrowDown => Some(EKey::ArrowDown), - Key::ArrowLeft => Some(EKey::ArrowLeft), - Key::ArrowRight => Some(EKey::ArrowRight), - Key::ArrowUp => Some(EKey::ArrowUp), - Key::Escape => Some(EKey::Escape), - Key::Tab => Some(EKey::Tab), - Key::Backspace => Some(EKey::Backspace), - Key::Enter => Some(EKey::Enter), - Key::Space => Some(EKey::Space), - Key::Insert => Some(EKey::Insert), - Key::Delete => Some(EKey::Delete), - Key::Home => Some(EKey::Home), - Key::End => Some(EKey::End), - Key::PageUp => Some(EKey::PageUp), - Key::PageDown => Some(EKey::PageDown), - Key::Copy => Some(EKey::Copy), - Key::Cut => Some(EKey::Cut), - Key::Paste => Some(EKey::Paste), - Key::Colon => Some(EKey::Colon), - Key::Comma => Some(EKey::Comma), - Key::Backslash => Some(EKey::Backslash), - Key::Slash => Some(EKey::Slash), - Key::Pipe => Some(EKey::Pipe), - Key::Questionmark => Some(EKey::Questionmark), - Key::OpenBracket => Some(EKey::OpenBracket), - Key::CloseBracket => Some(EKey::CloseBracket), - Key::Backtick => Some(EKey::Backtick), - Key::Minus => Some(EKey::Minus), - Key::Period => Some(EKey::Period), - Key::Plus => Some(EKey::Plus), - Key::Equals => Some(EKey::Equals), - Key::Semicolon => Some(EKey::Semicolon), - Key::Quote => Some(EKey::Quote), - Key::Num0 => Some(EKey::Num0), - Key::Num1 => Some(EKey::Num1), - Key::Num2 => Some(EKey::Num2), - Key::Num3 => Some(EKey::Num3), - Key::Num4 => Some(EKey::Num4), - Key::Num5 => Some(EKey::Num5), - Key::Num6 => Some(EKey::Num6), - Key::Num7 => Some(EKey::Num7), - Key::Num8 => Some(EKey::Num8), - Key::Num9 => Some(EKey::Num9), - Key::A => Some(EKey::A), - Key::B => Some(EKey::B), - Key::C => Some(EKey::C), - Key::D => Some(EKey::D), - Key::E => Some(EKey::E), - Key::F => Some(EKey::F), - Key::G => Some(EKey::G), - Key::H => Some(EKey::H), - Key::I => Some(EKey::I), - Key::J => Some(EKey::J), - Key::K => Some(EKey::K), - Key::L => Some(EKey::L), - Key::M => Some(EKey::M), - Key::N => Some(EKey::N), - Key::O => Some(EKey::O), - Key::P => Some(EKey::P), - Key::Q => Some(EKey::Q), - Key::R => Some(EKey::R), - Key::S => Some(EKey::S), - Key::T => Some(EKey::T), - Key::U => Some(EKey::U), - Key::V => Some(EKey::V), - Key::W => Some(EKey::W), - Key::X => Some(EKey::X), - Key::Y => Some(EKey::Y), - Key::Z => Some(EKey::Z), - Key::F1 => Some(EKey::F1), - Key::F2 => Some(EKey::F2), - Key::F3 => Some(EKey::F3), - Key::F4 => Some(EKey::F4), - Key::F5 => Some(EKey::F5), - Key::F6 => Some(EKey::F6), - Key::F7 => Some(EKey::F7), - Key::F8 => Some(EKey::F8), - Key::F9 => Some(EKey::F9), - Key::F10 => Some(EKey::F10), - Key::F11 => Some(EKey::F11), - Key::F12 => Some(EKey::F12), - Key::F13 => Some(EKey::F13), - Key::F14 => Some(EKey::F14), - Key::F15 => Some(EKey::F15), - Key::F16 => Some(EKey::F16), - Key::F17 => Some(EKey::F17), - Key::F18 => Some(EKey::F18), - Key::F19 => Some(EKey::F19), - Key::F20 => Some(EKey::F20), - Key::F21 => Some(EKey::F21), - Key::F22 => Some(EKey::F22), - Key::F23 => Some(EKey::F23), - Key::F24 => Some(EKey::F24), - Key::F25 => Some(EKey::F25), - Key::F26 => Some(EKey::F26), - Key::F27 => Some(EKey::F27), - Key::F28 => Some(EKey::F28), - Key::F29 => Some(EKey::F29), - Key::F30 => Some(EKey::F30), - Key::F31 => Some(EKey::F31), - Key::F32 => Some(EKey::F32), - Key::F33 => Some(EKey::F33), - Key::F34 => Some(EKey::F34), - Key::F35 => Some(EKey::F35), - _ => None, - } -} - -fn pointer_button_to_egui(value: MouseButton) -> Option { - match value { - MouseButton::Left => Some(egui::PointerButton::Primary), - MouseButton::Right => Some(egui::PointerButton::Secondary), - MouseButton::Middle => Some(egui::PointerButton::Middle), - MouseButton::Back => Some(egui::PointerButton::Extra1), - MouseButton::Forward => Some(egui::PointerButton::Extra2), - MouseButton::Other(_) => None, - } -} diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 0963a9491..ac67c8dad 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -4,7 +4,6 @@ #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] mod builder; -mod event; #[cfg(feature = "snapshot")] mod snapshot; @@ -14,6 +13,7 @@ use std::fmt::{Debug, Display, Formatter}; use std::time::Duration; mod app_kind; +mod node; mod renderer; #[cfg(feature = "wgpu")] mod texture_to_image; @@ -23,13 +23,13 @@ pub mod wgpu; pub use kittest; use crate::app_kind::AppKind; -use crate::event::EventState; pub use builder::*; +pub use node::*; pub use renderer::*; -use egui::{Modifiers, Pos2, Rect, RepaintCause, Vec2, ViewportId}; -use kittest::{Node, Queryable}; +use egui::{Key, Modifiers, Pos2, Rect, RepaintCause, Vec2, ViewportId}; +use kittest::Queryable; #[derive(Debug, Clone)] pub struct ExceededMaxStepsError { @@ -61,13 +61,13 @@ pub struct Harness<'a, State = ()> { kittest: kittest::State, output: egui::FullOutput, app: AppKind<'a, State>, - event_state: EventState, response: Option, state: State, renderer: Box, max_steps: u64, step_dt: f32, wait_for_pending_images: bool, + queued_events: EventQueue, } impl Debug for Harness<'_, State> { @@ -126,12 +126,12 @@ impl<'a, State> Harness<'a, State> { ), output, response, - event_state: EventState::default(), state, renderer, max_steps, step_dt, wait_for_pending_images, + queued_events: Default::default(), }; // Run the harness until it is stable, ensuring that all Areas are shown and animations are done harness.run_ok(); @@ -227,12 +227,19 @@ impl<'a, State> Harness<'a, State> { /// This will call the app closure with each queued event and /// update the Harness. pub fn step(&mut self) { - let events = self.kittest.take_events(); + let events = std::mem::take(&mut *self.queued_events.lock()); if events.is_empty() { self._step(false); } for event in events { - self.event_state.update(event, &mut self.input); + match event { + EventType::Event(event) => { + self.input.events.push(event); + } + EventType::Modifiers(modifiers) => { + self.input.modifiers = modifiers; + } + } self._step(false); } } @@ -414,52 +421,128 @@ impl<'a, State> Harness<'a, State> { &mut self.state } - /// Press a key. - /// This will create a key down event and a key up event. - pub fn press_key(&mut self, key: egui::Key) { - self.input.events.push(egui::Event::Key { + fn event(&self, event: egui::Event) { + self.queued_events.lock().push(EventType::Event(event)); + } + + fn event_modifiers(&self, event: egui::Event, modifiers: Modifiers) { + let mut queue = self.queued_events.lock(); + queue.push(EventType::Modifiers(modifiers)); + queue.push(EventType::Event(event)); + queue.push(EventType::Modifiers(Modifiers::default())); + } + + fn modifiers(&self, modifiers: Modifiers) { + self.queued_events + .lock() + .push(EventType::Modifiers(modifiers)); + } + + pub fn key_down(&self, key: egui::Key) { + self.event(egui::Event::Key { key, pressed: true, - modifiers: self.input.modifiers, - repeat: false, - physical_key: None, - }); - self.input.events.push(egui::Event::Key { - key, - pressed: false, - modifiers: self.input.modifiers, + modifiers: Modifiers::default(), repeat: false, physical_key: None, }); } - /// Press a key with modifiers. - /// This will create a key-down event, a key-up event, and update the modifiers. - /// - /// NOTE: In contrast to the event fns on [`Node`], this will call [`Harness::step`], in - /// order to properly update modifiers. - pub fn press_key_modifiers(&mut self, modifiers: Modifiers, key: egui::Key) { - // Combine the modifiers with the current modifiers - let previous_modifiers = self.input.modifiers; - self.input.modifiers |= modifiers; - - self.input.events.push(egui::Event::Key { - key, - pressed: true, + pub fn key_down_modifiers(&self, modifiers: Modifiers, key: egui::Key) { + self.event_modifiers( + egui::Event::Key { + key, + pressed: true, + modifiers, + repeat: false, + physical_key: None, + }, modifiers, - repeat: false, - physical_key: None, - }); - self.step(); - self.input.events.push(egui::Event::Key { + ); + } + + pub fn key_up(&self, key: egui::Key) { + self.event(egui::Event::Key { key, pressed: false, - modifiers, + modifiers: Modifiers::default(), repeat: false, physical_key: None, }); + } - self.input.modifiers = previous_modifiers; + pub fn key_up_modifiers(&self, modifiers: Modifiers, key: egui::Key) { + self.event_modifiers( + egui::Event::Key { + key, + pressed: false, + modifiers, + repeat: false, + physical_key: None, + }, + modifiers, + ); + } + + /// Press the given keys in combination. + /// + /// For e.g. [`Key::A`] + [`Key::B`] this would generate: + /// - Press [`Key::A`] + /// - Press [`Key::B`] + /// - Release [`Key::B`] + /// - Release [`Key::A`] + pub fn key_combination(&self, keys: &[Key]) { + for key in keys { + self.key_down(*key); + } + for key in keys.iter().rev() { + self.key_up(*key); + } + } + + /// Press the given keys in combination, with modifiers. + /// + /// For e.g. [`Modifiers::COMMAND`] + [`Key::A`] + [`Key::B`] this would generate: + /// - Press [`Modifiers::COMMAND`] + /// - Press [`Key::A`] + /// - Press [`Key::B`] + /// - Release [`Key::B`] + /// - Release [`Key::A`] + /// - Release [`Modifiers::COMMAND`] + pub fn key_combination_modifiers(&self, modifiers: Modifiers, keys: &[Key]) { + self.modifiers(modifiers); + + for pressed in [true, false] { + for key in keys { + self.event(egui::Event::Key { + key: *key, + pressed, + modifiers, + repeat: false, + physical_key: None, + }); + } + } + + self.modifiers(Modifiers::default()); + } + + /// Press a key. + /// + /// This will create a key down event and a key up event. + pub fn key_press(&self, key: egui::Key) { + self.key_combination(&[key]); + } + + /// Press a key with modifiers. + /// + /// This will + /// - set the modifiers + /// - create a key down event + /// - create a key up event + /// - reset the modifiers + pub fn key_press_modifiers(&self, modifiers: Modifiers, key: egui::Key) { + self.key_combination_modifiers(modifiers, &[key]); } /// Render the last output to an image. @@ -478,6 +561,18 @@ impl<'a, State> Harness<'a, State> { .get(&ViewportId::ROOT) .expect("Missing root viewport") } + + fn root(&self) -> Node<'_> { + Node { + accesskit_node: self.kittest.root(), + queue: &self.queued_events, + } + } + + #[deprecated = "Use `Harness::root` instead."] + pub fn node(&self) -> Node<'_> { + self.root() + } } /// Utilities for stateless harnesses. @@ -526,11 +621,11 @@ impl<'a> Harness<'a> { } } -impl<'t, 'n, State> Queryable<'t, 'n> for Harness<'_, State> +impl<'tree, 'node, State> Queryable<'tree, 'node, Node<'tree>> for Harness<'_, State> where - 'n: 't, + 'node: 'tree, { - fn node(&'n self) -> Node<'t> { - self.kittest_state().node() + fn queryable_node(&'node self) -> Node<'tree> { + self.root() } } diff --git a/crates/egui_kittest/src/node.rs b/crates/egui_kittest/src/node.rs new file mode 100644 index 000000000..384a5dbd4 --- /dev/null +++ b/crates/egui_kittest/src/node.rs @@ -0,0 +1,162 @@ +use egui::accesskit::ActionRequest; +use egui::mutex::Mutex; +use egui::{accesskit, Modifiers, PointerButton, Pos2}; +use kittest::{debug_fmt_node, AccessKitNode, NodeT}; +use std::fmt::{Debug, Formatter}; + +pub(crate) enum EventType { + Event(egui::Event), + Modifiers(Modifiers), +} + +pub(crate) type EventQueue = Mutex>; + +#[derive(Clone, Copy)] +pub struct Node<'tree> { + pub(crate) accesskit_node: AccessKitNode<'tree>, + pub(crate) queue: &'tree EventQueue, +} + +impl Debug for Node<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + debug_fmt_node(self, f) + } +} + +impl<'tree> NodeT<'tree> for Node<'tree> { + fn accesskit_node(&self) -> AccessKitNode<'tree> { + self.accesskit_node + } + + fn new_related(&self, child_node: AccessKitNode<'tree>) -> Self { + Self { + queue: self.queue, + accesskit_node: child_node, + } + } +} + +impl Node<'_> { + fn event(&self, event: egui::Event) { + self.queue.lock().push(EventType::Event(event)); + } + + fn modifiers(&self, modifiers: Modifiers) { + self.queue.lock().push(EventType::Modifiers(modifiers)); + } + + pub fn hover(&self) { + self.event(egui::Event::PointerMoved(self.rect().center())); + } + + /// Click at the node center with the primary button. + pub fn click(&self) { + self.click_button(PointerButton::Primary); + } + + #[deprecated = "Use `click()` instead."] + pub fn simulate_click(&self) { + self.click(); + } + + pub fn click_secondary(&self) { + self.click_button(PointerButton::Secondary); + } + + pub fn click_button(&self, button: PointerButton) { + self.hover(); + for pressed in [true, false] { + self.event(egui::Event::PointerButton { + pos: self.rect().center(), + button, + pressed, + modifiers: Modifiers::default(), + }); + } + } + + pub fn click_modifiers(&self, modifiers: Modifiers) { + self.click_button_modifiers(PointerButton::Primary, modifiers); + } + + pub fn click_button_modifiers(&self, button: PointerButton, modifiers: Modifiers) { + self.hover(); + self.modifiers(modifiers); + for pressed in [true, false] { + self.event(egui::Event::PointerButton { + pos: self.rect().center(), + button, + pressed, + modifiers, + }); + } + self.modifiers(Modifiers::default()); + } + + /// Click the node via accesskit. + /// + /// This will trigger a [`accesskit::Action::Click`] action. + /// In contrast to `click()`, this can also click widgets that are not currently visible. + pub fn click_accesskit(&self) { + self.event(egui::Event::AccessKitActionRequest( + accesskit::ActionRequest { + target: self.accesskit_node.id(), + action: accesskit::Action::Click, + data: None, + }, + )); + } + + pub fn rect(&self) -> egui::Rect { + let rect = self + .accesskit_node + .bounding_box() + .expect("Every egui node should have a rect"); + egui::Rect { + min: Pos2::new(rect.x0 as f32, rect.y0 as f32), + max: Pos2::new(rect.x1 as f32, rect.y1 as f32), + } + } + + pub fn focus(&self) { + self.event(egui::Event::AccessKitActionRequest(ActionRequest { + action: accesskit::Action::Focus, + target: self.accesskit_node.id(), + data: None, + })); + } + + #[deprecated = "Use `Harness::key_down` instead."] + pub fn key_down(&self, key: egui::Key) { + self.event(egui::Event::Key { + key, + pressed: true, + modifiers: Modifiers::default(), + repeat: false, + physical_key: None, + }); + } + + #[deprecated = "Use `Harness::key_up` instead."] + pub fn key_up(&self, key: egui::Key) { + self.event(egui::Event::Key { + key, + pressed: false, + modifiers: Modifiers::default(), + repeat: false, + physical_key: None, + }); + } + + pub fn type_text(&self, text: &str) { + self.event(egui::Event::Text(text.to_owned())); + } + + pub fn value(&self) -> Option { + self.accesskit_node.value() + } + + pub fn is_focused(&self) -> bool { + self.accesskit_node.is_focused() + } +} diff --git a/crates/egui_kittest/tests/menu.rs b/crates/egui_kittest/tests/menu.rs index fc19e8040..48f348a21 100644 --- a/crates/egui_kittest/tests/menu.rs +++ b/crates/egui_kittest/tests/menu.rs @@ -95,7 +95,7 @@ fn menu_close_on_click_outside() { TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick)) .into_harness(); - harness.get_by_label("Menu A").simulate_click(); + harness.get_by_label("Menu A").click(); harness.run(); harness @@ -106,9 +106,7 @@ fn menu_close_on_click_outside() { // We should be able to check the checkbox without closing the menu // Click a couple of times, just in case for expect_checked in [true, false, true, false] { - harness - .get_by_label("Checkbox in Submenu C") - .simulate_click(); + harness.get_by_label("Checkbox in Submenu C").click(); harness.run(); assert_eq!(expect_checked, harness.state().checked); } @@ -119,7 +117,7 @@ fn menu_close_on_click_outside() { assert!(harness.query_by_label("Checkbox in Submenu C").is_some()); // Clicking outside should close the menu - harness.get_by_label("Some other label").simulate_click(); + harness.get_by_label("Some other label").click(); harness.run(); assert!(harness.query_by_label("Checkbox in Submenu C").is_none()); } @@ -130,14 +128,14 @@ fn menu_close_on_click() { TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick)) .into_harness(); - harness.get_by_label("Menu A").simulate_click(); + harness.get_by_label("Menu A").click(); harness.run(); harness.get_by_label_contains("Submenu B with icon").hover(); harness.run(); // Clicking the button should close the menu (even if ui.close() is not called by the button) - harness.get_by_label("Button in Submenu B").simulate_click(); + harness.get_by_label("Button in Submenu B").click(); harness.run(); assert!(harness.query_by_label("Button in Submenu B").is_none()); } @@ -145,21 +143,19 @@ fn menu_close_on_click() { #[test] fn clicking_submenu_button_should_never_close_menu() { // We test for this since otherwise the menu wouldn't work on touch devices - // The other tests use .hover to open submenus, but this test explicitly uses .simulate_click + // The other tests use .hover to open submenus, but this test explicitly uses .click let mut harness = TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick)) .into_harness(); - harness.get_by_label("Menu A").simulate_click(); + harness.get_by_label("Menu A").click(); harness.run(); // Clicking the submenu button should not close the menu - harness - .get_by_label_contains("Submenu B with icon") - .simulate_click(); + harness.get_by_label_contains("Submenu B with icon").click(); harness.run(); - harness.get_by_label("Button in Submenu B").simulate_click(); + harness.get_by_label("Button in Submenu B").click(); harness.run(); assert!(harness.query_by_label("Button in Submenu B").is_none()); } @@ -174,7 +170,7 @@ fn menu_snapshots() { harness.run(); results.add(harness.try_snapshot("menu/closed_hovered")); - harness.get_by_label("Menu A").simulate_click(); + harness.get_by_label("Menu A").click(); harness.run(); results.add(harness.try_snapshot("menu/opened")); diff --git a/crates/egui_kittest/tests/popup.rs b/crates/egui_kittest/tests/popup.rs index 368e8de7e..a8d3bcc9d 100644 --- a/crates/egui_kittest/tests/popup.rs +++ b/crates/egui_kittest/tests/popup.rs @@ -23,7 +23,7 @@ fn test_interactive_tooltip() { harness.run(); harness.get_by_label("link").hover(); harness.run(); - harness.get_by_label("link").simulate_click(); + harness.get_by_label("link").click(); harness.run(); diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index 50ed1095a..5107799f5 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -10,19 +10,19 @@ pub fn focus_should_skip_over_disabled_buttons() { ui.add(Button::new("Button 3")); }); - harness.press_key(egui::Key::Tab); + harness.key_press(egui::Key::Tab); harness.run(); let button_1 = harness.get_by_label("Button 1"); assert!(button_1.is_focused()); - harness.press_key(egui::Key::Tab); + harness.key_press(egui::Key::Tab); harness.run(); let button_3 = harness.get_by_label("Button 3"); assert!(button_3.is_focused()); - harness.press_key(egui::Key::Tab); + harness.key_press(egui::Key::Tab); harness.run(); let button_1 = harness.get_by_label("Button 1"); @@ -41,13 +41,13 @@ pub fn focus_should_skip_over_disabled_drag_values() { ui.add(egui::DragValue::new(&mut value_3)); }); - harness.press_key(egui::Key::Tab); + harness.key_press(egui::Key::Tab); harness.run(); let drag_value_1 = harness.get_by(|node| node.numeric_value() == Some(1.0)); assert!(drag_value_1.is_focused()); - harness.press_key(egui::Key::Tab); + harness.key_press(egui::Key::Tab); harness.run(); let drag_value_3 = harness.get_by(|node| node.numeric_value() == Some(3.0)); @@ -103,8 +103,7 @@ fn test_combobox() { results.add(harness.try_snapshot("combobox_opened")); let item_2 = harness.get_by_role_and_label(Role::Button, "Item 2"); - // Node::click doesn't close the popup, so we use simulate_click - item_2.simulate_click(); + item_2.click(); harness.run(); diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index 4b8288326..a3d4ca79a 100644 --- a/crates/egui_kittest/tests/snapshots/readme_example.png +++ b/crates/egui_kittest/tests/snapshots/readme_example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1d172484712e3e12038f8ff427db8c0073aba124aa1b6be17edcc7dccb12f74 -size 1656 +oid sha256:341658df1dfe665e79180d4540965a986a21de09c9cbc1a8744bdcff1a7c2086 +size 1892 diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index 2b223f457..8c92f4314 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -1,6 +1,6 @@ use egui::{include_image, Modifiers, Vec2}; use egui_kittest::Harness; -use kittest::{Key, Queryable as _}; +use kittest::Queryable as _; #[test] fn test_shrink() { @@ -39,17 +39,15 @@ fn test_modifiers() { State::default(), ); - harness.get_by_label("Click me").key_down(Key::Command); - // This run isn't necessary, but allows us to test whether modifiers are remembered between frames - harness.run(); - harness.get_by_label("Click me").click(); - harness.get_by_label("Click me").key_up(Key::Command); + harness + .get_by_label("Click me") + .click_modifiers(Modifiers::COMMAND); harness.run(); - harness.press_key_modifiers(Modifiers::COMMAND, egui::Key::Z); + harness.key_press_modifiers(Modifiers::COMMAND, egui::Key::Z); harness.run(); - harness.node().key_combination(&[Key::Command, Key::Y]); + harness.key_combination_modifiers(Modifiers::COMMAND, &[egui::Key::Y]); harness.run(); let state = harness.state(); diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs index 110eff810..e7082d472 100644 --- a/tests/egui_tests/tests/test_widgets.rs +++ b/tests/egui_tests/tests/test_widgets.rs @@ -1,11 +1,11 @@ use egui::load::SizedTexture; use egui::{ include_image, Align, AtomExt as _, AtomLayout, Button, Color32, ColorImage, Direction, - DragValue, Event, Grid, IntoAtoms as _, Layout, PointerButton, Pos2, Response, Slider, Stroke, + DragValue, Event, Grid, IntoAtoms as _, Layout, PointerButton, 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}; +use egui_kittest::kittest::{by, Queryable as _}; +use egui_kittest::{Harness, Node, SnapshotResult, SnapshotResults}; #[test] fn widget_tests() { @@ -278,14 +278,10 @@ impl<'a> VisualTests<'a> { }); self.add("pressed", |harness| { harness.get_next().hover(); - let rect = harness.get_next().bounding_box().unwrap(); - let pos = Pos2::new( - ((rect.x0 + rect.x1) / 2.0) as f32, - ((rect.y0 + rect.y1) / 2.0) as f32, - ); + let rect = harness.get_next().rect(); harness.input_mut().events.push(Event::PointerButton { button: PointerButton::Primary, - pos, + pos: rect.center(), pressed: true, modifiers: Default::default(), }); From c8b844cd83e4b03cb0f4223aa61ac59fcd87b39a Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 18 Jun 2025 19:19:05 +0200 Subject: [PATCH 04/56] Use the new Popup api for the color picker button (#7137) The color picker popup is now aligned to the bottom left edge (instead of the bottom right), but I think this makes more sense and is aligned with ComboBox etc. It also gets the new nice auto positioning. * closes #5832 * [x] I have followed the instructions in the PR template --- crates/egui/src/widgets/color_picker.rs | 40 +++++++------------------ 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index 07678d458..ebcd45b27 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -2,8 +2,8 @@ use crate::util::fixed_cache::FixedCache; use crate::{ - epaint, lerp, remap_clamp, Area, Context, DragValue, Frame, Id, Key, Order, Painter, Response, - Sense, Ui, UiKind, Widget as _, WidgetInfo, WidgetType, + epaint, lerp, remap_clamp, Context, DragValue, Id, Painter, Popup, PopupCloseBehavior, + Response, Sense, Ui, Widget as _, WidgetInfo, WidgetType, }; use epaint::{ ecolor::{Color32, Hsva, HsvaGamma, Rgba}, @@ -496,35 +496,17 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Res button_response = button_response.on_hover_text("Click to edit color"); } - if button_response.clicked() { - ui.memory_mut(|mem| mem.toggle_popup(popup_id)); - } - const COLOR_SLIDER_WIDTH: f32 = 275.0; - // TODO(lucasmerlin): Update this to use new Popup struct - if ui.memory(|mem| mem.is_popup_open(popup_id)) { - ui.memory_mut(|mem| mem.keep_popup_open(popup_id)); - let area_response = Area::new(popup_id) - .kind(UiKind::Picker) - .order(Order::Foreground) - .fixed_pos(button_response.rect.max) - .show(ui.ctx(), |ui| { - ui.spacing_mut().slider_width = COLOR_SLIDER_WIDTH; - Frame::popup(ui.style()).show(ui, |ui| { - if color_picker_hsva_2d(ui, hsva, alpha) { - button_response.mark_changed(); - } - }); - }) - .response; - - if !button_response.clicked() - && (ui.input(|i| i.key_pressed(Key::Escape)) || area_response.clicked_elsewhere()) - { - ui.memory_mut(|mem| mem.close_popup(popup_id)); - } - } + Popup::menu(&button_response) + .id(popup_id) + .close_behavior(PopupCloseBehavior::CloseOnClickOutside) + .show(|ui| { + ui.spacing_mut().slider_width = COLOR_SLIDER_WIDTH; + if color_picker_hsva_2d(ui, hsva, alpha) { + button_response.mark_changed(); + } + }); button_response } From 98add13933d55cbe1fa23e2b6af62f117145d368 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Thu, 19 Jun 2025 10:30:05 +0200 Subject: [PATCH 05/56] Workaround libpng crash on macos by not creating `NSImage` from png data (#7252) --- crates/eframe/src/native/app_icon.rs | 49 ++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 1f1bf52f2..ed240021b 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -205,13 +205,18 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS use objc2::ClassType as _; use objc2_app_kit::{NSApplication, NSImage}; - use objc2_foundation::{NSData, NSString}; + use objc2_foundation::NSString; - let png_bytes = if let Some(icon_data) = icon_data { - match icon_data.to_png_bytes() { - Ok(png_bytes) => Some(png_bytes), + // Do NOT use png even though creating `NSImage` from it is much easier than from raw images data! + // + // Some MacOS versions have a bug where creating an `NSImage` from a png will cause it to load an arbitrary `libpng.dylib`. + // If this dylib isn't the right version, the application will crash with SIGBUS. + // For details see https://github.com/emilk/egui/issues/7155 + let image = if let Some(icon_data) = icon_data { + match icon_data.to_image() { + Ok(image) => Some(image), Err(err) => { - log::warn!("Failed to convert IconData to png: {err}"); + log::warn!("Failed to read icon data: {err}"); return AppIconStatus::NotSetIgnored; } } @@ -231,15 +236,39 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS return AppIconStatus::NotSetIgnored; }; - if let Some(png_bytes) = png_bytes { - let data = NSData::from_vec(png_bytes); + if let Some(image) = image { + use objc2_app_kit::{NSBitmapImageRep, NSDeviceRGBColorSpace}; + use objc2_foundation::NSSize; - log::trace!("NSImage::initWithData…"); - let app_icon = NSImage::initWithData(NSImage::alloc(), &data); + log::trace!("NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel"); + let Some(image_rep) = NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel( + NSBitmapImageRep::alloc(), + [image.as_raw().as_ptr().cast_mut()].as_mut_ptr(), + image.width() as isize, + image.height() as isize, + 8, // bits per sample + 4, // samples per pixel + true, // has alpha + false, // is not planar + NSDeviceRGBColorSpace, + (image.width() * 4) as isize, // bytes per row + 32 // bits per pixel + ) else { + log::warn!("Failed to create NSBitmapImageRep from app icon data."); + return AppIconStatus::NotSetIgnored; + }; + + log::trace!("NSImage::initWithSize"); + let app_icon = NSImage::initWithSize( + NSImage::alloc(), + NSSize::new(image.width() as f64, image.height() as f64), + ); + log::trace!("NSImage::addRepresentation"); + app_icon.addRepresentation(&image_rep); profiling::scope!("setApplicationIconImage_"); log::trace!("setApplicationIconImage…"); - app.setApplicationIconImage(app_icon.as_deref()); + app.setApplicationIconImage(Some(&app_icon)); } // Change the title in the top bar - for python processes this would be again "python" otherwise. From 853feea46480266cf6147159b4fe35abf695e59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20=E2=80=9CGoldstein=E2=80=9D=20Siling?= Date: Tue, 24 Jun 2025 14:44:56 +0300 Subject: [PATCH 06/56] Fix incorrect window sizes for non-resizable windows on Wayland (#7103) Using physical window sizes leads to all kinds of fun stuff: winit always uses scale factor 1.0 on start to convert it back to logical pixels and uses these logical pixels to set min/max size for non-resizeable windows. You're supposed to adjust size after getting a scale change event if you're using physical sizes, but adjusting min/max sizes doesn't seem to work on sway, so the window is stuck with an incorrect size. The scale factor we guessed might also be wrong even if there's only a single display since it doesn't take fractional scale into account. TL;DR: winit actually wants logical sizes in these methods (since Wayland in general operates mostly on logical sizes) and converting them back and forth is lossy. * Closes https://github.com/emilk/egui/issues/7095 * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/glow_integration.rs | 2 - crates/egui-winit/src/lib.rs | 79 ++++++++------------ 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 946210c86..15ade4a38 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -959,7 +959,6 @@ impl GlutinWindowContext { .with_preference(glutin_winit::ApiPreference::FallbackEgl) .with_window_attributes(Some(egui_winit::create_winit_window_attributes( egui_ctx, - event_loop, viewport_builder.clone(), ))); @@ -1113,7 +1112,6 @@ impl GlutinWindowContext { log::debug!("Creating a window for viewport {viewport_id:?}"); let window_attributes = egui_winit::create_winit_window_attributes( &self.egui_ctx, - event_loop, viewport.builder.clone(), ); if window_attributes.transparent() diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index f205c3108..b07d47b0e 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1566,8 +1566,7 @@ pub fn create_window( ) -> Result { profiling::function_scope!(); - let window_attributes = - create_winit_window_attributes(egui_ctx, event_loop, viewport_builder.clone()); + let window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone()); let window = event_loop.create_window(window_attributes)?; apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder); Ok(window) @@ -1575,28 +1574,10 @@ pub fn create_window( pub fn create_winit_window_attributes( egui_ctx: &egui::Context, - event_loop: &ActiveEventLoop, viewport_builder: ViewportBuilder, ) -> winit::window::WindowAttributes { profiling::function_scope!(); - // We set sizes and positions in egui:s own ui points, which depends on the egui - // zoom_factor and the native pixels per point, so we need to know that here. - // We don't know what monitor the window will appear on though, but - // we'll try to fix that after the window is created in the call to `apply_viewport_builder_to_window`. - let native_pixels_per_point = event_loop - .primary_monitor() - .or_else(|| event_loop.available_monitors().next()) - .map_or_else( - || { - log::debug!("Failed to find a monitor - assuming native_pixels_per_point of 1.0"); - 1.0 - }, - |m| m.scale_factor() as f32, - ); - let zoom_factor = egui_ctx.zoom_factor(); - let pixels_per_point = zoom_factor * native_pixels_per_point; - let ViewportBuilder { title, position, @@ -1672,40 +1653,46 @@ pub fn create_winit_window_attributes( }) .with_active(active.unwrap_or(true)); + // Here and below: we create `LogicalSize` / `LogicalPosition` taking + // zoom factor into account. We don't have a good way to get physical size here, + // and trying to do it anyway leads to weird bugs on Wayland, see: + // https://github.com/emilk/egui/issues/7095#issuecomment-2920545377 + // https://github.com/rust-windowing/winit/issues/4266 + #[expect( + clippy::disallowed_types, + reason = "zoom factor is manually accounted for" + )] #[cfg(not(target_os = "ios"))] - if let Some(size) = inner_size { - window_attributes = window_attributes.with_inner_size(PhysicalSize::new( - pixels_per_point * size.x, - pixels_per_point * size.y, - )); - } + { + use winit::dpi::{LogicalPosition, LogicalSize}; + let zoom_factor = egui_ctx.zoom_factor(); - #[cfg(not(target_os = "ios"))] - if let Some(size) = min_inner_size { - window_attributes = window_attributes.with_min_inner_size(PhysicalSize::new( - pixels_per_point * size.x, - pixels_per_point * size.y, - )); - } + if let Some(size) = inner_size { + window_attributes = window_attributes + .with_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y)); + } - #[cfg(not(target_os = "ios"))] - if let Some(size) = max_inner_size { - window_attributes = window_attributes.with_max_inner_size(PhysicalSize::new( - pixels_per_point * size.x, - pixels_per_point * size.y, - )); - } + if let Some(size) = min_inner_size { + window_attributes = window_attributes + .with_min_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y)); + } - #[cfg(not(target_os = "ios"))] - if let Some(pos) = position { - window_attributes = window_attributes.with_position(PhysicalPosition::new( - pixels_per_point * pos.x, - pixels_per_point * pos.y, - )); + if let Some(size) = max_inner_size { + window_attributes = window_attributes + .with_max_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y)); + } + + if let Some(pos) = position { + window_attributes = window_attributes.with_position(LogicalPosition::new( + zoom_factor * pos.x, + zoom_factor * pos.y, + )); + } } #[cfg(target_os = "ios")] { // Unused: + _ = egui_ctx; _ = pixels_per_point; _ = position; _ = inner_size; From f11a3510ba07ae87747d744d952676476a88c24e Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Tue, 24 Jun 2025 09:09:29 -0400 Subject: [PATCH 07/56] Support custom syntect settings in syntax highlighter (#7084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Closes * [X] I have followed the instructions in the PR template This PR adds support for syntax highlighting with custom `syntect::parsing::SyntaxSet` and `syntect::highlighting::ThemeSet`. It adds a new `egui_extras::highlight_with` function (enabled with `feature = "syntect"`), which takes a new public`struct SyntectSettings` containing the syntax and theme sets. ```rust let mut builder = SyntaxSetBuilder::new(); builder.add_from_folder("syntax", true).unwrap(); let ps = builder.build(); let ts = syntect::highlighting::ThemeSet::load_defaults(); let syntax = egui_extras::syntax_highlighting::SyntectSettings { ps, ts }; // ...elsewhere egui_extras::syntax_highlighting::highlight_with( ui.ctx(), ui.style(), &theme, buf, "rhai", &syntax, ); ``` There's a little bit of architectural complexity, but it all emerges naturally from the problem's constraints. Previously, the `Highlighter` both contained the `syntect` settings _and_ implemented `egui::cache::ComputerMut` to highlight a string; the settings would never change. After this change, the `syntect` settings have become part of the cache key, so we should redo highlighting if they change. The `Highlighter` becomes an empty `struct` which just serves to implement `ComputerMut`. `SyntaxSet` and `ThemeSet` are not hasheable themselves, so can't be used as cache keys direction. Instead, we can use the *address* of the `&SyntectSettings` as the key. This requires an object with a custom `Hash` implementation, so I added a new `HighlightSettings(&'a SyntectSettings)`, implementing `Hash` using `std::ptr::hash` on the reference. I think using the address is reasonable – it would be _weird_ for a user to be constantly moving around their `SyntectSettings`, and there's a warning in the docstring to this effect. To work _without_ custom settings, `SyntectSettings::default` preserves the same behavior as before, using `SyntaxSet::load_defaults_newlines` and `ThemeSet::load_defaults`. If the user doesn't provide custom settings, then we instantiate a singleton `SyntectSettings` in `data` and use it; this will only be constructed once. Finally, in cases where the `syntect` feature is disabled, `SyntectSettings` are replaced with a unit `()`. This adds a _tiny_ amount of overhead – one singleton `Arc<()>` allocation and a lookup in `data` per `highlight` – but I think that's better than dramatically different implementations. If this is an issue, I can refactor to make it zero-cost when the feature is disabled. --- crates/egui_extras/src/syntax_highlighting.rs | 125 +++++++++++++++--- 1 file changed, 104 insertions(+), 21 deletions(-) diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 8d688ae8e..2476e783b 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -28,18 +28,65 @@ pub fn highlight( theme: &CodeTheme, code: &str, language: &str, +) -> LayoutJob { + highlight_inner(ctx, style, theme, code, language, None) +} + +/// Add syntax highlighting to a code string, with custom `syntect` settings +/// +/// The results are memoized, so you can call this every frame without performance penalty. +/// +/// The `syntect` settings are memoized by *address*, so a stable reference should +/// be used to avoid unnecessary recomputation. +#[cfg(feature = "syntect")] +pub fn highlight_with( + ctx: &egui::Context, + style: &egui::Style, + theme: &CodeTheme, + code: &str, + language: &str, + settings: &SyntectSettings, +) -> LayoutJob { + highlight_inner( + ctx, + style, + theme, + code, + language, + Some(HighlightSettings(settings)), + ) +} + +fn highlight_inner( + ctx: &egui::Context, + style: &egui::Style, + theme: &CodeTheme, + code: &str, + language: &str, + settings: Option>, ) -> LayoutJob { // We take in both context and style so that in situations where ui is not available such as when // performing it at a separate thread (ctx, ctx.style()) can be used and when ui is available // (ui.ctx(), ui.style()) can be used #[expect(non_local_definitions)] - impl egui::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> for Highlighter { + impl + egui::cache::ComputerMut< + (&egui::FontId, &CodeTheme, &str, &str, HighlightSettings<'_>), + LayoutJob, + > for Highlighter + { fn compute( &mut self, - (font_id, theme, code, lang): (&egui::FontId, &CodeTheme, &str, &str), + (font_id, theme, code, lang, settings): ( + &egui::FontId, + &CodeTheme, + &str, + &str, + HighlightSettings<'_>, + ), ) -> LayoutJob { - self.highlight(font_id.clone(), theme, code, lang) + Self::highlight(font_id.clone(), theme, code, lang, settings) } } @@ -50,10 +97,27 @@ pub fn highlight( .clone() .unwrap_or_else(|| TextStyle::Monospace.resolve(style)); + // Private type, so that users can't interfere with it in the `IdTypeMap` + #[cfg(feature = "syntect")] + #[derive(Clone, Default)] + struct PrivateSettings(std::sync::Arc); + + // Dummy private settings, to minimize code changes without `syntect` + #[cfg(not(feature = "syntect"))] + #[derive(Clone, Default)] + struct PrivateSettings(std::sync::Arc<()>); + ctx.memory_mut(|mem| { + let settings = settings.unwrap_or_else(|| { + HighlightSettings( + &mem.data + .get_temp_mut_or_default::(egui::Id::NULL) + .0, + ) + }); mem.caches .cache::() - .get((&font_id, theme, code, language)) + .get((&font_id, theme, code, language, settings)) }) } @@ -396,13 +460,13 @@ impl CodeTheme { // ---------------------------------------------------------------------------- #[cfg(feature = "syntect")] -struct Highlighter { - ps: syntect::parsing::SyntaxSet, - ts: syntect::highlighting::ThemeSet, +pub struct SyntectSettings { + pub ps: syntect::parsing::SyntaxSet, + pub ts: syntect::highlighting::ThemeSet, } #[cfg(feature = "syntect")] -impl Default for Highlighter { +impl Default for SyntectSettings { fn default() -> Self { profiling::function_scope!(); Self { @@ -412,15 +476,33 @@ impl Default for Highlighter { } } +/// Highlight settings are memoized by reference address, rather than value +#[cfg(feature = "syntect")] +#[derive(Copy, Clone)] +struct HighlightSettings<'a>(&'a SyntectSettings); + +#[cfg(not(feature = "syntect"))] +#[derive(Copy, Clone)] +struct HighlightSettings<'a>(&'a ()); + +impl std::hash::Hash for HighlightSettings<'_> { + fn hash(&self, state: &mut H) { + std::ptr::hash(self.0, state); + } +} + +#[derive(Default)] +struct Highlighter; + impl Highlighter { fn highlight( - &self, font_id: egui::FontId, theme: &CodeTheme, code: &str, lang: &str, + settings: HighlightSettings<'_>, ) -> LayoutJob { - self.highlight_impl(theme, code, lang).unwrap_or_else(|| { + Self::highlight_impl(theme, code, lang, settings).unwrap_or_else(|| { // Fallback: LayoutJob::simple( code.into(), @@ -436,19 +518,25 @@ impl Highlighter { } #[cfg(feature = "syntect")] - fn highlight_impl(&self, theme: &CodeTheme, text: &str, language: &str) -> Option { + fn highlight_impl( + theme: &CodeTheme, + text: &str, + language: &str, + highlighter: HighlightSettings<'_>, + ) -> Option { profiling::function_scope!(); use syntect::easy::HighlightLines; use syntect::highlighting::FontStyle; use syntect::util::LinesWithEndings; - let syntax = self + let syntax = highlighter + .0 .ps .find_syntax_by_name(language) - .or_else(|| self.ps.find_syntax_by_extension(language))?; + .or_else(|| highlighter.0.ps.find_syntax_by_extension(language))?; let syn_theme = theme.syntect_theme.syntect_key_name(); - let mut h = HighlightLines::new(syntax, &self.ts.themes[syn_theme]); + let mut h = HighlightLines::new(syntax, &highlighter.0.ts.themes[syn_theme]); use egui::text::{LayoutSection, TextFormat}; @@ -458,7 +546,7 @@ impl Highlighter { }; for line in LinesWithEndings::from(text) { - for (style, range) in h.highlight_line(line, &self.ps).ok()? { + for (style, range) in h.highlight_line(line, &highlighter.0.ps).ok()? { let fg = style.foreground; let text_color = egui::Color32::from_rgb(fg.r, fg.g, fg.b); let italics = style.font_style.contains(FontStyle::ITALIC); @@ -505,18 +593,13 @@ fn as_byte_range(whole: &str, range: &str) -> std::ops::Range { // ---------------------------------------------------------------------------- -#[cfg(not(feature = "syntect"))] -#[derive(Default)] -struct Highlighter {} - #[cfg(not(feature = "syntect"))] impl Highlighter { - #[expect(clippy::unused_self)] fn highlight_impl( - &self, theme: &CodeTheme, mut text: &str, language: &str, + _settings: HighlightSettings<'_>, ) -> Option { profiling::function_scope!(); From 78a8de2e8f8d6da82680af4701cfa6e61cfe389c Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 25 Jun 2025 14:26:36 +0200 Subject: [PATCH 08/56] Respect `StyleModifier` in popup `Frame` style (#7265) This makes it possible to style the Popup's frame using a StyleModifier --- crates/egui/src/containers/popup.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 47e800d22..c40578bc6 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -573,10 +573,9 @@ impl<'a> Popup<'a> { area = area.default_width(width); } - let frame = frame.unwrap_or_else(|| Frame::popup(&ctx.style())); - let mut response = area.show(&ctx, |ui| { style.apply(ui.style_mut()); + let frame = frame.unwrap_or_else(|| Frame::popup(ui.style())); frame.show(ui, content).inner }); From ae8363ddb53675b1892c2f069e88c77469b5fceb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 27 Jun 2025 10:25:47 +0200 Subject: [PATCH 09/56] eframe web: only cosume copy/cut events if the canvas has focus (#7270) Previously eframe would "steal" these events (by calling `stop_propagation/prevent_default`) even when the eframe canvas did not have focus. --- crates/eframe/src/web/events.rs | 46 +++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 1782a6797..521d43941 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -311,13 +311,17 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { runner_ref.add_event_listener(target, "paste", |event: web_sys::ClipboardEvent, runner| { + if !runner.input.raw.focused { + return; // The eframe app is not interested + } + if let Some(data) = event.clipboard_data() { if let Ok(text) = data.get_data("text") { let text = text.replace("\r\n", "\n"); let mut should_stop_propagation = true; let mut should_prevent_default = true; - if !text.is_empty() && runner.input.raw.focused { + if !text.is_empty() { let egui_event = egui::Event::Paste(text); should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); @@ -340,17 +344,19 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul })?; runner_ref.add_event_listener(target, "cut", |event: web_sys::ClipboardEvent, runner| { - if runner.input.raw.focused { - runner.input.raw.events.push(egui::Event::Cut); - - // In Safari we are only allowed to write to the clipboard during the - // event callback, which is why we run the app logic here and now: - runner.logic(); - - // Make sure we paint the output of the above logic call asap: - runner.needs_repaint.repaint_asap(); + if !runner.input.raw.focused { + return; // The eframe app is not interested } + runner.input.raw.events.push(egui::Event::Cut); + + // In Safari we are only allowed to write to the clipboard during the + // event callback, which is why we run the app logic here and now: + runner.logic(); + + // Make sure we paint the output of the above logic call asap: + runner.needs_repaint.repaint_asap(); + // Use web options to tell if the web event should be propagated to parent elements based on the egui event. if (runner.web_options.should_stop_propagation)(&egui::Event::Cut) { event.stop_propagation(); @@ -362,17 +368,19 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul })?; runner_ref.add_event_listener(target, "copy", |event: web_sys::ClipboardEvent, runner| { - if runner.input.raw.focused { - runner.input.raw.events.push(egui::Event::Copy); - - // In Safari we are only allowed to write to the clipboard during the - // event callback, which is why we run the app logic here and now: - runner.logic(); - - // Make sure we paint the output of the above logic call asap: - runner.needs_repaint.repaint_asap(); + if !runner.input.raw.focused { + return; // The eframe app is not interested } + runner.input.raw.events.push(egui::Event::Copy); + + // In Safari we are only allowed to write to the clipboard during the + // event callback, which is why we run the app logic here and now: + runner.logic(); + + // Make sure we paint the output of the above logic call asap: + runner.needs_repaint.repaint_asap(); + // Use web options to tell if the web event should be propagated to parent elements based on the egui event. if (runner.web_options.should_stop_propagation)(&egui::Event::Copy) { event.stop_propagation(); From ab9f55ab0144462c1bc46a1fc49fe3b18f4d0fa7 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Fri, 27 Jun 2025 11:27:03 +0200 Subject: [PATCH 10/56] Fix crash in `egui_extras::FileLoader` after `forget_image` (#6995) This pull request modifies the `BytesLoader` implementation for `FileLoader` in `crates/egui_extras/src/loaders/file_loader.rs` to improve thread safety and handle unexpected states more gracefully. ### Changes to thread safety and state handling: * Updated the cache logic to check if the `uri` exists in the cache before inserting the result. If the `uri` is not found, a log message is added to indicate the loading was canceled. This change prevents overwriting cache entries unexpectedly. * Closes * [x] I have followed the instructions in the PR template --- crates/egui/src/context.rs | 1 + crates/egui_extras/src/loaders/file_loader.rs | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 27a3fcd9b..fb83a837a 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -3446,6 +3446,7 @@ impl Context { /// Release all memory and textures related to the given image URI. /// /// If you attempt to load the image again, it will be reloaded from scratch. + /// Also this cancels any ongoing loading of the image. pub fn forget_image(&self, uri: &str) { use load::BytesLoader as _; diff --git a/crates/egui_extras/src/loaders/file_loader.rs b/crates/egui_extras/src/loaders/file_loader.rs index 9feaebf56..d13134e21 100644 --- a/crates/egui_extras/src/loaders/file_loader.rs +++ b/crates/egui_extras/src/loaders/file_loader.rs @@ -95,10 +95,15 @@ impl BytesLoader for FileLoader { } Err(err) => Err(err.to_string()), }; - let prev = cache.lock().insert(uri.clone(), Poll::Ready(result)); - assert!(matches!(prev, Some(Poll::Pending)), "unexpected state"); - ctx.request_repaint(); - log::trace!("finished loading {uri:?}"); + let mut cache = cache.lock(); + if let std::collections::hash_map::Entry::Occupied(mut entry) = cache.entry(uri.clone()) { + let entry = entry.get_mut(); + *entry = Poll::Ready(result); + ctx.request_repaint(); + log::trace!("finished loading {uri:?}"); + } else { + log::trace!("cancelled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); + } } }) .expect("failed to spawn thread"); From c943720eedb98969339898ddbc84fd671e1de599 Mon Sep 17 00:00:00 2001 From: Lukas Rieger Date: Sun, 29 Jun 2025 13:30:39 +0200 Subject: [PATCH 11/56] Slider: move by at least the next increment when using fixed_decimals (#7066) fixes https://github.com/emilk/egui/issues/7065 --- crates/egui/src/widgets/slider.rs | 15 +++++- crates/egui_kittest/tests/regression_tests.rs | 49 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 017270fc7..8ad883491 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -736,7 +736,7 @@ impl Slider<'_> { let prev_value = self.get_value(); let prev_position = self.position_from_value(prev_value, position_range); let new_position = prev_position + ui_point_per_step * kb_step; - let new_value = match self.step { + let mut new_value = match self.step { Some(step) => prev_value + (kb_step as f64 * step), None if self.smart_aim => { let aim_radius = 0.49 * ui_point_per_step; // Chosen so we don't include `prev_value` in the search. @@ -747,6 +747,19 @@ impl Slider<'_> { } _ => self.value_from_position(new_position, position_range), }; + if let Some(max_decimals) = self.max_decimals { + // self.set_value rounds, so ensure we reach at the least the next breakpoint + // note: we give it a little bit of leeway due to floating point errors. (0.1 isn't representable in binary) + // 'set_value' will round it to the nearest value. + let min_increment = 1.0 / (10.0_f64.powi(max_decimals as i32)); + new_value = if new_value > prev_value { + f64::max(new_value, prev_value + min_increment * 1.001) + } else if new_value < prev_value { + f64::min(new_value, prev_value - min_increment * 1.001) + } else { + new_value + }; + } self.set_value(new_value); } diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index 5107799f5..5c7b4fc53 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -1,6 +1,8 @@ -use egui::accesskit::Role; +use egui::accesskit::{self, Role}; use egui::{Button, ComboBox, Image, Vec2, Widget as _}; -use egui_kittest::{kittest::Queryable as _, Harness, SnapshotResults}; +#[cfg(all(feature = "wgpu", feature = "snapshot"))] +use egui_kittest::SnapshotResults; +use egui_kittest::{kittest::Queryable as _, Harness}; #[test] pub fn focus_should_skip_over_disabled_buttons() { @@ -89,6 +91,7 @@ fn test_combobox() { harness.run(); + #[cfg(all(feature = "wgpu", feature = "snapshot"))] let mut results = SnapshotResults::new(); #[cfg(all(feature = "wgpu", feature = "snapshot"))] @@ -112,3 +115,45 @@ fn test_combobox() { // Popup should be closed now assert!(harness.query_by_label("Item 2").is_none()); } + +/// `https://github.com/emilk/egui/issues/7065` +#[test] +pub fn slider_should_move_with_fixed_decimals() { + let mut value: f32 = 1.0; + + let mut harness = Harness::new_ui(|ui| { + // Movement on arrow-key is relative to slider width; make the slider wide so the movement becomes small. + ui.spacing_mut().slider_width = 2000.0; + ui.add(egui::Slider::new(&mut value, 0.1..=10.0).fixed_decimals(2)); + }); + + harness.key_press(egui::Key::Tab); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.00".to_owned())); + + harness.key_press(egui::Key::ArrowRight); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.01".to_owned())); + + harness.key_press(egui::Key::ArrowRight); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.02".to_owned())); + + harness.key_press(egui::Key::ArrowLeft); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.01".to_owned())); + + harness.key_press(egui::Key::ArrowLeft); + harness.run(); + + let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); + assert_eq!(actual_slider.value(), Some("1.00".to_owned())); +} From 2525546fef620bcca1d77ade6db66e69486994d0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 30 Jun 2025 10:03:54 +0200 Subject: [PATCH 12/56] Simplify some bezier math --- crates/epaint/src/shapes/bezier_shape.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/epaint/src/shapes/bezier_shape.rs b/crates/epaint/src/shapes/bezier_shape.rs index 7d291c7fd..5823c3796 100644 --- a/crates/epaint/src/shapes/bezier_shape.rs +++ b/crates/epaint/src/shapes/bezier_shape.rs @@ -253,8 +253,8 @@ impl CubicBezierShape { if p > 0.0 { return None; } - let r = (-1.0 * (p / 3.0).powi(3)).sqrt(); - let theta = (-1.0 * q / (2.0 * r)).acos() / 3.0; + let r = (-(p / 3.0).powi(3)).sqrt(); + let theta = (-q / (2.0 * r)).acos() / 3.0; let t1 = 2.0 * r.cbrt() * theta.cos() + h; let t2 = 2.0 * r.cbrt() * (theta + 120.0 * std::f32::consts::PI / 180.0).cos() + h; From d770cd53a6827f9895fad3901256c5803f28f4e2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 30 Jun 2025 10:41:27 +0200 Subject: [PATCH 13/56] Add `Context::current_pass_index` (#7276) This can be used by developers who wants to add diagnostics when there is a multi-pass egui frame. --- crates/egui/src/context.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index fb83a837a..099f80090 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1539,6 +1539,9 @@ impl Context { /// The total number of completed passes (usually there is one pass per rendered frame). /// /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). + /// + /// If you instead want to know which pass index this is within the current frame, + /// use [`Self::current_pass_index`]. pub fn cumulative_pass_nr(&self) -> u64 { self.cumulative_pass_nr_for(self.viewport_id()) } @@ -1554,6 +1557,18 @@ impl Context { }) } + /// The index of the current pass in the current frame, starting at zero. + /// + /// Usually this is zero, but if something called [`Self::request_discard`] to do multi-pass layout, + /// then this will be incremented for each pass. + /// + /// This just reads the value of [`PlatformOutput::num_completed_passes`]. + /// + /// To know the total number of passes ever completed, use [`Self::cumulative_pass_nr`]. + pub fn current_pass_index(&self) -> usize { + self.output(|o| o.num_completed_passes) + } + /// Call this if there is need to repaint the UI, i.e. if you are showing an animation. /// /// If this is called at least once in a frame, then there will be another frame right after this. @@ -1726,7 +1741,7 @@ impl Context { /// This means the first pass will look glitchy, and ideally should not be shown to the user. /// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitches. /// - /// There is a limit to how many passes egui will perform, set by [`Options::max_passes`]. + /// There is a limit to how many passes egui will perform, set by [`Options::max_passes`] (default=2). /// Therefore, the request might be declined. /// /// You can check if the current pass will be discarded with [`Self::will_discard`]. From 8ba42f322df919a3b6dfad90fb9efa1092f214fc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 30 Jun 2025 13:29:56 +0200 Subject: [PATCH 14/56] Add `Context::cumulative_frame_nr` (#7278) --- crates/egui/src/context.rs | 107 ++++++++++++++++------ crates/egui_demo_app/src/backend_panel.rs | 4 + 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 099f80090..de0a7e2e4 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -339,6 +339,15 @@ impl RepaintCause { /// Per-viewport state related to repaint scheduling. struct ViewportRepaintInfo { /// Monotonically increasing counter. + /// + /// Incremented at the end of [`Context::run`]. + /// This can be smaller than [`Self::cumulative_pass_nr`], + /// but never larger. + cumulative_frame_nr: u64, + + /// Monotonically increasing counter, counting the number of passes. + /// This can be larger than [`Self::cumulative_frame_nr`], + /// but never smaller. cumulative_pass_nr: u64, /// The duration which the backend will poll for new events @@ -369,6 +378,7 @@ struct ViewportRepaintInfo { impl Default for ViewportRepaintInfo { fn default() -> Self { Self { + cumulative_frame_nr: 0, cumulative_pass_nr: 0, // We haven't scheduled a repaint yet. @@ -864,6 +874,7 @@ impl Context { } else { viewport.num_multipass_in_row = 0; } + viewport.repaint.cumulative_frame_nr += 1; }); output @@ -1536,6 +1547,28 @@ impl Context { } } + /// The total number of completed frames. + /// + /// Starts at zero, and is incremented once at the end of each call to [`Self::run`]. + /// + /// This is always smaller or equal to [`Self::cumulative_pass_nr`]. + pub fn cumulative_frame_nr(&self) -> u64 { + self.cumulative_frame_nr_for(self.viewport_id()) + } + + /// The total number of completed frames. + /// + /// Starts at zero, and is incremented once at the end of each call to [`Self::run`]. + /// + /// This is always smaller or equal to [`Self::cumulative_pass_nr_for`]. + pub fn cumulative_frame_nr_for(&self, id: ViewportId) -> u64 { + self.read(|ctx| { + ctx.viewports + .get(&id) + .map_or(0, |v| v.repaint.cumulative_frame_nr) + }) + } + /// The total number of completed passes (usually there is one pass per rendered frame). /// /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). @@ -2998,36 +3031,54 @@ impl Context { pub fn inspection_ui(&self, ui: &mut Ui) { use crate::containers::CollapsingHeader; - ui.label(format!("Is using pointer: {}", self.is_using_pointer())) - .on_hover_text( - "Is egui currently using the pointer actively (e.g. dragging a slider)?", - ); - ui.label(format!("Wants pointer input: {}", self.wants_pointer_input())) - .on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window)."); - ui.label(format!( - "Wants keyboard input: {}", - self.wants_keyboard_input() - )) - .on_hover_text("Is egui currently listening for text input?"); - ui.label(format!( - "Keyboard focus widget: {}", - self.memory(|m| m.focused()) - .as_ref() - .map(Id::short_debug_format) - .unwrap_or_default() - )) - .on_hover_text("Is egui currently listening for text input?"); + crate::Grid::new("egui-inspection-grid") + .num_columns(2) + .striped(true) + .show(ui, |ui| { + ui.label("Total ui frames:"); + ui.monospace(ui.ctx().cumulative_frame_nr().to_string()); + ui.end_row(); - let pointer_pos = self - .pointer_hover_pos() - .map_or_else(String::new, |pos| format!("{pos:?}")); - ui.label(format!("Pointer pos: {pointer_pos}")); + ui.label("Total ui passes:"); + ui.monospace(ui.ctx().cumulative_pass_nr().to_string()); + ui.end_row(); - let top_layer = self - .pointer_hover_pos() - .and_then(|pos| self.layer_id_at(pos)) - .map_or_else(String::new, |layer| layer.short_debug_format()); - ui.label(format!("Top layer under mouse: {top_layer}")); + ui.label("Is using pointer") + .on_hover_text("Is egui currently using the pointer actively (e.g. dragging a slider)?"); + ui.monospace(self.is_using_pointer().to_string()); + ui.end_row(); + + ui.label("Wants pointer input") + .on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window)."); + ui.monospace(self.wants_pointer_input().to_string()); + ui.end_row(); + + ui.label("Wants keyboard input").on_hover_text("Is egui currently listening for text input?"); + ui.monospace(self.wants_keyboard_input().to_string()); + ui.end_row(); + + ui.label("Keyboard focus widget").on_hover_text("Is egui currently listening for text input?"); + ui.monospace(self.memory(|m| m.focused()) + .as_ref() + .map(Id::short_debug_format) + .unwrap_or_default()); + ui.end_row(); + + let pointer_pos = self + .pointer_hover_pos() + .map_or_else(String::new, |pos| format!("{pos:?}")); + ui.label("Pointer pos"); + ui.monospace(pointer_pos); + ui.end_row(); + + let top_layer = self + .pointer_hover_pos() + .and_then(|pos| self.layer_id_at(pos)) + .map_or_else(String::new, |layer| layer.short_debug_format()); + ui.label("Top layer under mouse"); + ui.monospace(top_layer); + ui.end_row(); + }); ui.add_space(16.0); diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 1fb0c6553..e98d5179d 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -146,6 +146,10 @@ impl BackendPanel { // builds to keep the noise down in the official demo. if cfg!(debug_assertions) { ui.collapsing("More…", |ui| { + ui.horizontal(|ui| { + ui.label("Total ui frames:"); + ui.monospace(ui.ctx().cumulative_frame_nr().to_string()); + }); ui.horizontal(|ui| { ui.label("Total ui passes:"); ui.monospace(ui.ctx().cumulative_pass_nr().to_string()); From 962c8e26a82766567dcc1eadcc25d8eefa323177 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 30 Jun 2025 13:43:27 +0200 Subject: [PATCH 15/56] Update MSRV to 1.85 (#7279) --- .github/workflows/deploy_web_demo.yml | 2 +- .github/workflows/preview_build.yml | 2 +- .github/workflows/rust.yml | 14 +++++++------- Cargo.toml | 3 ++- clippy.toml | 2 +- crates/egui/src/lib.rs | 2 +- crates/egui_demo_lib/src/demo/code_example.rs | 1 + examples/confirm_exit/Cargo.toml | 2 +- examples/custom_3d_glow/Cargo.toml | 2 +- examples/custom_font/Cargo.toml | 2 +- examples/custom_font_style/Cargo.toml | 2 +- examples/custom_keypad/Cargo.toml | 2 +- examples/custom_style/Cargo.toml | 2 +- examples/custom_window_frame/Cargo.toml | 2 +- examples/external_eventloop/Cargo.toml | 2 +- examples/external_eventloop_async/Cargo.toml | 2 +- examples/file_dialog/Cargo.toml | 2 +- examples/hello_android/Cargo.toml | 2 +- examples/hello_world/Cargo.toml | 2 +- examples/hello_world_par/Cargo.toml | 2 +- examples/hello_world_simple/Cargo.toml | 2 +- examples/images/Cargo.toml | 2 +- examples/keyboard_events/Cargo.toml | 2 +- examples/multiple_viewports/Cargo.toml | 2 +- examples/puffin_profiler/Cargo.toml | 2 +- examples/screenshot/Cargo.toml | 2 +- examples/serial_windows/Cargo.toml | 2 +- examples/user_attention/Cargo.toml | 2 +- rust-toolchain | 2 +- scripts/check.sh | 2 +- scripts/clippy_wasm/clippy.toml | 2 +- tests/test_egui_extras_compilation/Cargo.toml | 2 +- tests/test_inline_glow_paint/Cargo.toml | 2 +- tests/test_size_pass/Cargo.toml | 2 +- tests/test_ui_stack/Cargo.toml | 2 +- tests/test_viewports/Cargo.toml | 2 +- 36 files changed, 43 insertions(+), 41 deletions(-) diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index 8b7833366..278549e1e 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -39,7 +39,7 @@ jobs: with: profile: minimal target: wasm32-unknown-unknown - toolchain: 1.84.0 + toolchain: 1.85.0 override: true - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 8550cbeed..437108ab6 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.84.0 + toolchain: 1.85.0 targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d603051d5..493cc2fcf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.84.0 + toolchain: 1.85.0 - name: Install packages (Linux) if: runner.os == 'Linux' @@ -83,7 +83,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.84.0 + toolchain: 1.85.0 targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev @@ -155,7 +155,7 @@ jobs: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v2 with: - rust-version: "1.84.0" + rust-version: "1.85.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -170,7 +170,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.84.0 + toolchain: 1.85.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -191,7 +191,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.84.0 + toolchain: 1.85.0 targets: aarch64-apple-ios - name: Set up cargo cache @@ -210,7 +210,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.84.0 + toolchain: 1.85.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 @@ -234,7 +234,7 @@ jobs: lfs: true - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.84.0 + toolchain: 1.85.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index bf5b27d30..a5a480da7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ [workspace.package] edition = "2021" license = "MIT OR Apache-2.0" -rust-version = "1.84" +rust-version = "1.85" version = "0.31.1" @@ -193,6 +193,7 @@ large_stack_frames = "warn" large_types_passed_by_value = "warn" let_unit_value = "warn" linkedlist = "warn" +literal_string_with_formatting_args = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" manual_assert = "warn" diff --git a/clippy.toml b/clippy.toml index 24a804037..0f40ef38d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.84" +msrv = "1.85" allow-unwrap-in-tests = true diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 4ff7db230..fa390dbc7 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -3,7 +3,7 @@ //! Try the live web demo: . Read more about egui at . //! //! `egui` is in heavy development, with each new version having breaking changes. -//! You need to have rust 1.84.0 or later to use `egui`. +//! You need to have rust 1.85.0 or later to use `egui`. //! //! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template) //! which uses [`eframe`](https://docs.rs/eframe). diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 89a8cdafa..52cafad0c 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -62,6 +62,7 @@ impl CodeExample { } ui.end_row(); + #[expect(clippy::literal_string_with_formatting_args)] show_code(ui, r#"ui.label(format!("{name} is {age}"));"#); ui.label(format!("{name} is {age}")); ui.end_row(); diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index f20af6faf..2e5e1c085 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 98824d3a4..fa11a8b5e 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index da29460c7..dc42990bb 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index d7db7450a..ff9bde8ce 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["tami5 "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index a866ae4d9..1b4c5ca4e 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Varphone Wong "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index e6571fa04..596306782 100644 --- a/examples/custom_style/Cargo.toml +++ b/examples/custom_style/Cargo.toml @@ -3,7 +3,7 @@ name = "custom_style" version = "0.1.0" license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index b98ea27d2..21c8e8ef0 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/external_eventloop/Cargo.toml b/examples/external_eventloop/Cargo.toml index 301f30251..884791f3a 100644 --- a/examples/external_eventloop/Cargo.toml +++ b/examples/external_eventloop/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Will Brown "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/external_eventloop_async/Cargo.toml b/examples/external_eventloop_async/Cargo.toml index 399ff7c39..e7a0913bc 100644 --- a/examples/external_eventloop_async/Cargo.toml +++ b/examples/external_eventloop_async/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Will Brown "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 816686979..290ea3c66 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index b893f35e9..b8b763622 100644 --- a/examples/hello_android/Cargo.toml +++ b/examples/hello_android/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false # `unsafe_code` is required for `#[no_mangle]`, disable workspace lints to workaround lint error. diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 0ad5ff844..37803d3aa 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index dd980888b..38c0d2b53 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Maxim Osipenko "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 57f0269e9..1c4e30b98 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index d605e25e0..c3317b38b 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jan Procházka "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 00d1d591f..9ca900a28 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jose Palazon "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 995baa431..84aca3c85 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index df39fd456..041b13de1 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [package.metadata.cargo-machete] diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 0288328fa..2e775eb7d 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index f89af84c0..b2d9899e0 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index ff1a7538b..7d5ce61a9 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["TicClick "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/rust-toolchain b/rust-toolchain index 6c3ca5854..71db6497f 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,6 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.84.0" +channel = "1.85.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/check.sh b/scripts/check.sh index f232dfbb6..0207df1cc 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -9,7 +9,7 @@ set -x # Checks all tests, lints etc. # Basically does what the CI does. -# cargo +1.84.0 install --quiet typos-cli +# cargo +1.85.0 install --quiet typos-cli export RUSTFLAGS="-D warnings" export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454 diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index 17d2a8cd6..e91b18abc 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -6,7 +6,7 @@ # ----------------------------------------------------------------------------- # Section identical to the root clippy.toml: -msrv = "1.84" +msrv = "1.85" allow-unwrap-in-tests = true diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index 0f5e8f50a..102130cf8 100644 --- a/tests/test_egui_extras_compilation/Cargo.toml +++ b/tests/test_egui_extras_compilation/Cargo.toml @@ -3,7 +3,7 @@ name = "test_egui_extras_compilation" version = "0.1.0" license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index 7187c599a..d26cb864c 100644 --- a/tests/test_inline_glow_paint/Cargo.toml +++ b/tests/test_inline_glow_paint/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml index 280a40c1b..760571a2d 100644 --- a/tests/test_size_pass/Cargo.toml +++ b/tests/test_size_pass/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/tests/test_ui_stack/Cargo.toml b/tests/test_ui_stack/Cargo.toml index e4dd35f4d..43fd45420 100644 --- a/tests/test_ui_stack/Cargo.toml +++ b/tests/test_ui_stack/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Antoine Beyeler "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] diff --git a/tests/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml index e8c8bc754..33b7829b3 100644 --- a/tests/test_viewports/Cargo.toml +++ b/tests/test_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["konkitoman"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.84" +rust-version = "1.85" publish = false [lints] From b2995dcb835d4f5dab973cca46609e47966beb3f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 30 Jun 2025 14:01:57 +0200 Subject: [PATCH 16/56] Use Rust edition 2024 (#7280) --- Cargo.toml | 3 +-- crates/ecolor/src/cint_impl.rs | 2 +- crates/ecolor/src/color32.rs | 2 +- crates/ecolor/src/hsva.rs | 2 +- crates/ecolor/src/hsva_gamma.rs | 2 +- crates/eframe/README.md | 2 +- crates/eframe/src/epi.rs | 8 ++++-- crates/eframe/src/native/app_icon.rs | 6 +++-- crates/eframe/src/native/file_storage.rs | 4 +-- crates/eframe/src/native/glow_integration.rs | 10 ++++--- crates/eframe/src/native/run.rs | 3 +-- crates/eframe/src/native/wgpu_integration.rs | 2 +- crates/eframe/src/web/app_runner.rs | 4 +-- crates/eframe/src/web/events.rs | 9 +++---- crates/eframe/src/web/input.rs | 2 +- crates/eframe/src/web/web_logger.rs | 13 +++++++--- crates/eframe/src/web/web_painter_wgpu.rs | 2 +- crates/eframe/src/web/web_runner.rs | 4 +-- crates/egui-wgpu/src/capture.rs | 7 +++-- crates/egui-wgpu/src/renderer.rs | 14 +++++++--- crates/egui-wgpu/src/winit.rs | 12 ++++++--- crates/egui-winit/src/clipboard.rs | 4 ++- crates/egui/src/animation_manager.rs | 2 +- crates/egui/src/callstack.rs | 13 +++++++--- crates/egui/src/containers/area.rs | 4 +-- .../egui/src/containers/collapsing_header.rs | 6 ++--- crates/egui/src/containers/combo_box.rs | 6 ++--- crates/egui/src/containers/frame.rs | 7 ++--- crates/egui/src/containers/menu.rs | 2 +- crates/egui/src/containers/panel.rs | 4 +-- crates/egui/src/containers/popup.rs | 4 +-- crates/egui/src/containers/resize.rs | 4 +-- crates/egui/src/containers/scene.rs | 4 +-- crates/egui/src/containers/scroll_area.rs | 4 +-- crates/egui/src/containers/window.rs | 2 +- crates/egui/src/context.rs | 26 ++++++++++++------- crates/egui/src/data/input.rs | 2 +- crates/egui/src/debug_text.rs | 2 +- crates/egui/src/grid.rs | 4 +-- crates/egui/src/hit_test.rs | 4 +-- crates/egui/src/input_state/mod.rs | 6 ++--- crates/egui/src/input_state/touch_state.rs | 8 +++--- crates/egui/src/interaction.rs | 2 +- crates/egui/src/introspection.rs | 4 +-- crates/egui/src/layers.rs | 4 +-- crates/egui/src/layout.rs | 2 +- crates/egui/src/lib.rs | 15 +++++------ crates/egui/src/load.rs | 2 +- crates/egui/src/load/bytes_loader.rs | 4 +-- crates/egui/src/memory/mod.rs | 13 ++++++---- crates/egui/src/memory/theme.rs | 6 +---- crates/egui/src/menu.rs | 9 +++---- crates/egui/src/os.rs | 3 ++- crates/egui/src/painter.rs | 4 +-- crates/egui/src/pass_state.rs | 4 +-- crates/egui/src/placer.rs | 2 +- crates/egui/src/response.rs | 5 ++-- crates/egui/src/style.rs | 8 +++--- .../egui/src/text_selection/accesskit_text.rs | 2 +- .../egui/src/text_selection/cursor_range.rs | 4 +-- .../text_selection/label_text_selection.rs | 8 +++--- .../src/text_selection/text_cursor_state.rs | 4 +-- crates/egui/src/text_selection/visuals.rs | 2 +- crates/egui/src/ui.rs | 16 ++++++------ crates/egui/src/ui_builder.rs | 2 +- crates/egui/src/widget_text.rs | 2 +- crates/egui/src/widgets/checkbox.rs | 4 +-- crates/egui/src/widgets/color_picker.rs | 7 ++--- crates/egui/src/widgets/drag_value.rs | 4 +-- crates/egui/src/widgets/hyperlink.rs | 4 +-- crates/egui/src/widgets/image.rs | 9 ++++--- crates/egui/src/widgets/image_button.rs | 4 +-- crates/egui/src/widgets/label.rs | 4 +-- crates/egui/src/widgets/mod.rs | 4 +-- crates/egui/src/widgets/progress_bar.rs | 4 +-- crates/egui/src/widgets/radio_button.rs | 4 +-- crates/egui/src/widgets/separator.rs | 2 +- crates/egui/src/widgets/slider.rs | 12 +++------ crates/egui/src/widgets/spinner.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 12 ++++----- crates/egui/src/widgets/text_edit/state.rs | 2 +- .../egui/src/widgets/text_edit/text_buffer.rs | 2 +- .../egui_demo_app/src/apps/fractal_clock.rs | 2 +- crates/egui_demo_app/src/apps/image_viewer.rs | 6 ++--- crates/egui_demo_app/src/frame_history.rs | 2 +- crates/egui_demo_app/src/main.rs | 11 ++++++-- crates/egui_demo_app/tests/test_demo_app.rs | 4 +-- crates/egui_demo_lib/benches/benchmark.rs | 4 +-- .../egui_demo_lib/src/demo/dancing_strings.rs | 3 ++- .../src/demo/demo_app_windows.rs | 4 +-- .../egui_demo_lib/src/demo/drag_and_drop.rs | 2 +- .../src/demo/misc_demo_window.rs | 4 +-- crates/egui_demo_lib/src/demo/modals.rs | 4 +-- crates/egui_demo_lib/src/demo/multi_touch.rs | 3 ++- crates/egui_demo_lib/src/demo/paint_bezier.rs | 6 ++--- crates/egui_demo_lib/src/demo/painting.rs | 2 +- crates/egui_demo_lib/src/demo/popups.rs | 6 ++--- crates/egui_demo_lib/src/demo/scrolling.rs | 4 +-- crates/egui_demo_lib/src/demo/sliders.rs | 2 +- crates/egui_demo_lib/src/demo/table_demo.rs | 4 ++- .../src/demo/tests/layout_test.rs | 2 +- .../src/demo/tests/tessellation_test.rs | 3 ++- crates/egui_demo_lib/src/demo/text_edit.rs | 4 +-- crates/egui_demo_lib/src/demo/undo_redo.rs | 2 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- .../src/easy_mark/easy_mark_viewer.rs | 4 +-- crates/egui_demo_lib/src/rendering_test.rs | 8 +++--- crates/egui_extras/src/layout.rs | 2 +- .../egui_extras/src/loaders/ehttp_loader.rs | 4 +-- crates/egui_extras/src/loaders/gif_loader.rs | 3 +-- .../egui_extras/src/loaders/image_loader.rs | 3 +-- crates/egui_extras/src/loaders/svg_loader.rs | 4 +-- crates/egui_extras/src/loaders/webp_loader.rs | 7 +++-- crates/egui_extras/src/strip.rs | 2 +- crates/egui_extras/src/syntax_highlighting.rs | 2 +- crates/egui_extras/src/table.rs | 4 +-- crates/egui_glow/src/lib.rs | 8 ++---- crates/egui_glow/src/painter.rs | 4 ++- crates/egui_glow/src/shader_version.rs | 6 +---- crates/egui_glow/src/winit.rs | 2 +- crates/egui_kittest/src/node.rs | 4 +-- crates/egui_kittest/src/snapshot.rs | 16 +++++++----- crates/egui_kittest/src/wgpu.rs | 2 +- crates/egui_kittest/tests/accesskit.rs | 2 +- crates/egui_kittest/tests/menu.rs | 2 +- crates/egui_kittest/tests/regression_tests.rs | 2 +- crates/egui_kittest/tests/tests.rs | 2 +- crates/emath/src/align.rs | 2 +- crates/emath/src/easing.rs | 6 +---- crates/emath/src/lib.rs | 8 ++---- crates/emath/src/pos2.rs | 2 +- crates/emath/src/rect.rs | 2 +- crates/emath/src/rect_transform.rs | 2 +- crates/emath/src/vec2.rs | 6 +---- crates/epaint/benches/benchmark.rs | 6 ++--- crates/epaint/src/image.rs | 2 +- crates/epaint/src/lib.rs | 2 +- crates/epaint/src/margin.rs | 2 +- crates/epaint/src/margin_f32.rs | 2 +- crates/epaint/src/mesh.rs | 2 +- crates/epaint/src/shadow.rs | 3 ++- crates/epaint/src/shape_transform.rs | 4 +-- crates/epaint/src/shapes/rect_shape.rs | 3 ++- crates/epaint/src/shapes/shape.rs | 7 ++--- crates/epaint/src/stroke.rs | 2 +- crates/epaint/src/tessellator.rs | 18 ++++++------- crates/epaint/src/text/font.rs | 4 +-- crates/epaint/src/text/fonts.rs | 9 ++++--- crates/epaint/src/text/text_layout.rs | 4 +-- crates/epaint/src/text/text_layout_types.rs | 2 +- crates/epaint/src/texture_atlas.rs | 2 +- crates/epaint/src/texture_handle.rs | 4 +-- examples/confirm_exit/Cargo.toml | 2 +- examples/custom_3d_glow/Cargo.toml | 2 +- examples/custom_font/Cargo.toml | 2 +- examples/custom_font_style/Cargo.toml | 2 +- examples/custom_keypad/Cargo.toml | 2 +- examples/custom_keypad/src/keypad.rs | 2 +- examples/custom_style/Cargo.toml | 2 +- examples/custom_style/src/main.rs | 2 +- examples/custom_window_frame/Cargo.toml | 2 +- examples/custom_window_frame/src/main.rs | 2 +- examples/external_eventloop/Cargo.toml | 2 +- examples/external_eventloop/src/main.rs | 2 +- examples/external_eventloop_async/Cargo.toml | 2 +- examples/external_eventloop_async/src/app.rs | 2 +- examples/file_dialog/Cargo.toml | 2 +- examples/hello_android/Cargo.toml | 2 +- examples/hello_android/src/lib.rs | 2 +- examples/hello_world/Cargo.toml | 2 +- examples/hello_world_par/Cargo.toml | 2 +- examples/hello_world_simple/Cargo.toml | 2 +- examples/images/Cargo.toml | 2 +- examples/keyboard_events/Cargo.toml | 2 +- examples/multiple_viewports/Cargo.toml | 2 +- examples/multiple_viewports/src/main.rs | 2 +- examples/puffin_profiler/Cargo.toml | 2 +- examples/puffin_profiler/src/main.rs | 10 +++++-- examples/screenshot/Cargo.toml | 2 +- examples/serial_windows/Cargo.toml | 2 +- examples/user_attention/Cargo.toml | 2 +- examples/user_attention/src/main.rs | 2 +- tests/egui_tests/tests/regression_tests.rs | 4 +-- tests/egui_tests/tests/test_widgets.rs | 8 +++--- tests/test_egui_extras_compilation/Cargo.toml | 2 +- tests/test_inline_glow_paint/Cargo.toml | 2 +- tests/test_size_pass/Cargo.toml | 2 +- tests/test_ui_stack/Cargo.toml | 2 +- tests/test_viewports/Cargo.toml | 2 +- tests/test_viewports/src/main.rs | 2 +- 190 files changed, 427 insertions(+), 388 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5a480da7..b1f350b3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ members = [ ] [workspace.package] -edition = "2021" +edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.85" version = "0.31.1" @@ -171,7 +171,6 @@ fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" -if_let_mutex = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" diff --git a/crates/ecolor/src/cint_impl.rs b/crates/ecolor/src/cint_impl.rs index 5d34e5e36..3d9c9d2a6 100644 --- a/crates/ecolor/src/cint_impl.rs +++ b/crates/ecolor/src/cint_impl.rs @@ -1,4 +1,4 @@ -use super::{linear_f32_from_linear_u8, linear_u8_from_linear_f32, Color32, Hsva, HsvaGamma, Rgba}; +use super::{Color32, Hsva, HsvaGamma, Rgba, linear_f32_from_linear_u8, linear_u8_from_linear_f32}; use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha}; // ---- Color32 ---- diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 55e3f232e..eaa1ce2c3 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -1,4 +1,4 @@ -use crate::{fast_round, linear_f32_from_linear_u8, Rgba}; +use crate::{Rgba, fast_round, linear_f32_from_linear_u8}; /// This format is used for space-efficient color representation (32 bits). /// diff --git a/crates/ecolor/src/hsva.rs b/crates/ecolor/src/hsva.rs index 8388e4139..97aed1b59 100644 --- a/crates/ecolor/src/hsva.rs +++ b/crates/ecolor/src/hsva.rs @@ -1,5 +1,5 @@ use crate::{ - gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_u8_from_linear_f32, Color32, Rgba, + Color32, Rgba, gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_u8_from_linear_f32, }; /// Hue, saturation, value, alpha. All in the range [0, 1]. diff --git a/crates/ecolor/src/hsva_gamma.rs b/crates/ecolor/src/hsva_gamma.rs index 67e167677..19e31f337 100644 --- a/crates/ecolor/src/hsva_gamma.rs +++ b/crates/ecolor/src/hsva_gamma.rs @@ -1,4 +1,4 @@ -use crate::{gamma_from_linear, linear_from_gamma, Color32, Hsva, Rgba}; +use crate::{Color32, Hsva, Rgba, gamma_from_linear, linear_from_gamma}; /// Like Hsva but with the `v` value (brightness) being gamma corrected /// so that it is somewhat perceptually even. diff --git a/crates/eframe/README.md b/crates/eframe/README.md index 3d787c572..9dbf42caf 100644 --- a/crates/eframe/README.md +++ b/crates/eframe/README.md @@ -24,7 +24,7 @@ To use on Linux, first run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev ``` -You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info. +You need to either use `edition = "2024"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info. You can opt-in to the using [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index eb0087b12..3e6023270 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -574,7 +574,9 @@ impl Default for Renderer { fn default() -> Self { #[cfg(not(feature = "glow"))] #[cfg(not(feature = "wgpu"))] - compile_error!("eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"); + compile_error!( + "eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'" + ); #[cfg(feature = "glow")] #[cfg(not(feature = "wgpu"))] @@ -617,7 +619,9 @@ impl std::str::FromStr for Renderer { #[cfg(feature = "wgpu")] "wgpu" => Ok(Self::Wgpu), - _ => Err(format!("eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled.")) + _ => Err(format!( + "eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled." + )), } } } diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index ed240021b..b94d8f83b 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -225,7 +225,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS }; // TODO(madsmtm): Move this into `objc2-app-kit` - extern "C" { + unsafe extern "C" { static NSApp: Option<&'static NSApplication>; } @@ -240,7 +240,9 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS use objc2_app_kit::{NSBitmapImageRep, NSDeviceRGBColorSpace}; use objc2_foundation::NSSize; - log::trace!("NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel"); + log::trace!( + "NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel" + ); let Some(image_rep) = NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel( NSBitmapImageRep::alloc(), [image.as_raw().as_ptr().cast_mut()].as_mut_ptr(), diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 5a3b1af32..82b666c29 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -52,10 +52,10 @@ fn roaming_appdata() -> Option { use windows_sys::Win32::Foundation::S_OK; use windows_sys::Win32::System::Com::CoTaskMemFree; use windows_sys::Win32::UI::Shell::{ - FOLDERID_RoamingAppData, SHGetKnownFolderPath, KF_FLAG_DONT_VERIFY, + FOLDERID_RoamingAppData, KF_FLAG_DONT_VERIFY, SHGetKnownFolderPath, }; - extern "C" { + unsafe extern "C" { fn wcslen(buf: *const u16) -> usize; } let mut path_raw = ptr::null_mut(); diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 15ade4a38..c83a6f14b 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -32,13 +32,13 @@ use egui::{ use egui_winit::accesskit_winit; use crate::{ - native::epi_integration::EpiIntegration, App, AppCreator, CreationContext, NativeOptions, - Result, Storage, + App, AppCreator, CreationContext, NativeOptions, Result, Storage, + native::epi_integration::EpiIntegration, }; use super::{ epi_integration, event_loop_context, - winit_integration::{create_egui_context, EventResult, UserEvent, WinitApp}, + winit_integration::{EventResult, UserEvent, WinitApp, create_egui_context}, }; // ---------------------------------------------------------------------------- @@ -1015,7 +1015,9 @@ impl GlutinWindowContext { let gl_context = match gl_context_result { Ok(it) => it, Err(err) => { - log::warn!("Failed to create context using default context attributes {context_attributes:?} due to error: {err}"); + log::warn!( + "Failed to create context using default context attributes {context_attributes:?} due to error: {err}" + ); log::debug!( "Retrying with fallback context attributes: {fallback_context_attributes:?}" ); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 8edfdbe2e..9c31aefe6 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -10,9 +10,8 @@ use ahash::HashMap; use super::winit_integration::{UserEvent, WinitApp}; use crate::{ - epi, + Result, epi, native::{event_loop_context, winit_integration::EventResult}, - Result, }; // ---------------------------------------------------------------------------- diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 96e93300e..f387779d7 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -25,8 +25,8 @@ use egui_winit::accesskit_winit; use winit_integration::UserEvent; use crate::{ - native::{epi_integration::EpiIntegration, winit_integration::EventResult}, App, AppCreator, CreationContext, NativeOptions, Result, Storage, + native::{epi_integration::EpiIntegration, winit_integration::EventResult}, }; use super::{epi_integration, event_loop_context, winit_integration, winit_integration::WinitApp}; diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 675c57bec..9f2d2beba 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -1,8 +1,8 @@ use egui::{TexturesDelta, UserData, ViewportCommand}; -use crate::{epi, App}; +use crate::{App, epi}; -use super::{now_sec, text_agent::TextAgent, web_painter::WebPainter as _, NeedRepaint}; +use super::{NeedRepaint, now_sec, text_agent::TextAgent, web_painter::WebPainter as _}; pub struct AppRunner { #[allow(dead_code, clippy::allow_attributes)] diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 521d43941..2bffdb780 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -1,11 +1,10 @@ use crate::web::string_from_js_value; use super::{ - button_from_mouse_event, location_hash, modifiers_from_kb_event, modifiers_from_mouse_event, - modifiers_from_wheel_event, native_pixels_per_point, pos_from_mouse_event, - prefers_color_scheme_dark, primary_touch_pos, push_touches, text_from_keyboard_event, - theme_from_dark_mode, translate_key, AppRunner, Closure, JsCast as _, JsValue, WebRunner, - DEBUG_RESIZE, + AppRunner, Closure, DEBUG_RESIZE, JsCast as _, JsValue, WebRunner, button_from_mouse_event, + location_hash, modifiers_from_kb_event, modifiers_from_mouse_event, modifiers_from_wheel_event, + native_pixels_per_point, pos_from_mouse_event, prefers_color_scheme_dark, primary_touch_pos, + push_touches, text_from_keyboard_event, theme_from_dark_mode, translate_key, }; use web_sys::{Document, EventTarget, ShadowRoot}; diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index eecf7c1f5..c27897090 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -1,4 +1,4 @@ -use super::{canvas_content_rect, AppRunner}; +use super::{AppRunner, canvas_content_rect}; pub fn pos_from_mouse_event( canvas: &web_sys::HtmlCanvasElement, diff --git a/crates/eframe/src/web/web_logger.rs b/crates/eframe/src/web/web_logger.rs index fe0eaaebe..01c347b7e 100644 --- a/crates/eframe/src/web/web_logger.rs +++ b/crates/eframe/src/web/web_logger.rs @@ -126,12 +126,17 @@ fn shorten_file_path(file_path: &str) -> &str { #[test] fn test_shorten_file_path() { for (before, after) in [ - ("/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs", "tokio-1.24.1/src/runtime/runtime.rs"), + ( + "/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs", + "tokio-1.24.1/src/runtime/runtime.rs", + ), ("crates/rerun/src/main.rs", "rerun/src/main.rs"), - ("/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs", "core/src/ops/function.rs"), + ( + "/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs", + "core/src/ops/function.rs", + ), ("/weird/path/file.rs", "/weird/path/file.rs"), - ] - { + ] { assert_eq!(shorten_file_path(before), after); } } diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 3cfc53f1c..debc6c5d1 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use super::web_painter::WebPainter; use crate::WebOptions; use egui::{Event, UserData, ViewportId}; -use egui_wgpu::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState}; +use egui_wgpu::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}; use egui_wgpu::{RenderState, SurfaceErrorAction}; use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 099be7aeb..c9449ab99 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -2,12 +2,12 @@ use std::{cell::RefCell, rc::Rc}; use wasm_bindgen::prelude::*; -use crate::{epi, App}; +use crate::{App, epi}; use super::{ + AppRunner, PanicHandler, events::{self, ResizeObserverContext}, text_agent::TextAgent, - AppRunner, PanicHandler, }; /// This is how `eframe` runs your web application diff --git a/crates/egui-wgpu/src/capture.rs b/crates/egui-wgpu/src/capture.rs index 38595bb6f..d47a828b4 100644 --- a/crates/egui-wgpu/src/capture.rs +++ b/crates/egui-wgpu/src/capture.rs @@ -1,6 +1,6 @@ use egui::{UserData, ViewportId}; use epaint::ColorImage; -use std::sync::{mpsc, Arc}; +use std::sync::{Arc, mpsc}; use wgpu::{BindGroupLayout, MultisampleState, StoreOp}; /// A texture and a buffer for reading the rendered frame back to the cpu. @@ -196,7 +196,10 @@ impl CaptureState { wgpu::TextureFormat::Rgba8Unorm => [0, 1, 2, 3], wgpu::TextureFormat::Bgra8Unorm => [2, 1, 0, 3], _ => { - log::error!("Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {:?}", format); + log::error!( + "Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {:?}", + format + ); return; } }; diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 7badefda4..73df09e43 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, num::NonZeroU64, ops::Range}; use ahash::HashMap; -use epaint::{emath::NumExt as _, PaintCallbackInfo, Primitive, Vertex}; +use epaint::{PaintCallbackInfo, Primitive, Vertex, emath::NumExt as _}; use wgpu::util::DeviceExt as _; @@ -909,7 +909,11 @@ impl Renderer { ); let Some(mut index_buffer_staging) = index_buffer_staging else { - panic!("Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)", self.index_buffer.buffer.size(), self.index_buffer.capacity); + panic!( + "Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)", + self.index_buffer.buffer.size(), + self.index_buffer.capacity + ); }; let mut index_offset = 0; @@ -948,7 +952,11 @@ impl Renderer { ); let Some(mut vertex_buffer_staging) = vertex_buffer_staging else { - panic!("Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)", self.vertex_buffer.buffer.size(), self.vertex_buffer.capacity); + panic!( + "Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)", + self.vertex_buffer.buffer.size(), + self.vertex_buffer.capacity + ); }; let mut vertex_offset = 0; diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index e0eafee71..cd02b59d8 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -1,8 +1,8 @@ #![allow(clippy::missing_errors_doc)] #![allow(clippy::undocumented_unsafe_blocks)] -use crate::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState}; -use crate::{renderer, RenderState, SurfaceErrorAction, WgpuConfiguration}; +use crate::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}; +use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer}; use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet}; use std::{num::NonZeroU32, sync::Arc}; @@ -220,7 +220,9 @@ impl Painter { } else if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) { wgpu::CompositeAlphaMode::PostMultiplied } else { - log::warn!("Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency."); + log::warn!( + "Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency." + ); wgpu::CompositeAlphaMode::Auto } } else { @@ -344,7 +346,9 @@ impl Painter { height_in_pixels, ); } else { - log::warn!("Ignoring window resize notification with no surface created via Painter::set_window()"); + log::warn!( + "Ignoring window resize notification with no surface created via Painter::set_window()" + ); } } diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index a0221294e..cec4b43c2 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -123,7 +123,9 @@ impl Clipboard { return; } - log::error!("Copying images is not supported. Enable the 'clipboard' feature of `egui-winit` to enable it."); + log::error!( + "Copying images is not supported. Enable the 'clipboard' feature of `egui-winit` to enable it." + ); _ = image; } } diff --git a/crates/egui/src/animation_manager.rs b/crates/egui/src/animation_manager.rs index 15002141f..50f97e992 100644 --- a/crates/egui/src/animation_manager.rs +++ b/crates/egui/src/animation_manager.rs @@ -1,6 +1,6 @@ use crate::{ - emath::{remap_clamp, NumExt as _}, Id, IdMap, InputState, + emath::{NumExt as _, remap_clamp}, }; #[derive(Clone, Default)] diff --git a/crates/egui/src/callstack.rs b/crates/egui/src/callstack.rs index 03eeaf5fc..3fe0a7a59 100644 --- a/crates/egui/src/callstack.rs +++ b/crates/egui/src/callstack.rs @@ -204,12 +204,17 @@ fn shorten_source_file_path(path: &std::path::Path) -> String { #[test] fn test_shorten_path() { for (before, after) in [ - ("/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs", "tokio-1.24.1/src/runtime/runtime.rs"), + ( + "/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs", + "tokio-1.24.1/src/runtime/runtime.rs", + ), ("crates/rerun/src/main.rs", "rerun/src/main.rs"), - ("/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs", "core/src/ops/function.rs"), + ( + "/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs", + "core/src/ops/function.rs", + ), ("/weird/path/file.rs", "/weird/path/file.rs"), - ] - { + ] { use std::str::FromStr as _; let before = std::path::PathBuf::from_str(before).unwrap(); assert_eq!(shorten_source_file_path(&before), after); diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 0d21e8bdd..d40df8358 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -5,8 +5,8 @@ use emath::GuiRounding as _; use crate::{ - emath, pos2, Align2, Context, Id, InnerResponse, LayerId, Layout, NumExt as _, Order, Pos2, - Rect, Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState, + Align2, Context, Id, InnerResponse, LayerId, Layout, NumExt as _, Order, Pos2, Rect, Response, + Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState, emath, pos2, }; /// State of an [`Area`] that is persisted between frames. diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 66e024f0b..3afb77682 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -1,9 +1,9 @@ use std::hash::Hash; use crate::{ - emath, epaint, pos2, remap, remap_clamp, vec2, Context, Id, InnerResponse, NumExt as _, Rect, - Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, - WidgetInfo, WidgetText, WidgetType, + Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle, + TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetText, WidgetType, + emath, epaint, pos2, remap, remap_clamp, vec2, }; use emath::GuiRounding as _; use epaint::{Shape, StrokeKind}; diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 3ae8344ba..fc5f33905 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -1,9 +1,9 @@ use epaint::Shape; use crate::{ - epaint, style::StyleModifier, style::WidgetVisuals, vec2, Align2, Context, Id, InnerResponse, - NumExt as _, Painter, Popup, PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, - TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType, + Align2, Context, Id, InnerResponse, NumExt as _, Painter, Popup, PopupCloseBehavior, Rect, + Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo, + WidgetText, WidgetType, epaint, style::StyleModifier, style::WidgetVisuals, vec2, }; #[expect(unused_imports)] // Documentation diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 1fdbbca92..d4b677693 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -1,8 +1,8 @@ //! Frame container use crate::{ - epaint, layers::ShapeIdx, InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind, - UiStackInfo, + InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind, UiStackInfo, epaint, + layers::ShapeIdx, }; use epaint::{Color32, CornerRadius, Margin, MarginF32, Rect, Shadow, Shape, Stroke}; @@ -143,7 +143,8 @@ pub struct Frame { #[test] fn frame_size() { assert_eq!( - std::mem::size_of::(), 32, + std::mem::size_of::(), + 32, "Frame changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." ); assert!( diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index 4fe06477d..d07d3ab6c 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -3,7 +3,7 @@ use crate::{ Button, Color32, Context, Frame, Id, InnerResponse, IntoAtoms, Layout, Popup, PopupCloseBehavior, Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget as _, }; -use emath::{vec2, Align, RectAlign, Vec2}; +use emath::{Align, RectAlign, Vec2, vec2}; use epaint::Stroke; /// Apply a menu style to the [`Style`]. diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index db0e8a7f9..2869f598c 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -18,8 +18,8 @@ use emath::GuiRounding as _; use crate::{ - lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, - Rangef, Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, + Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef, + Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, lerp, vec2, }; fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 { diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index c40578bc6..434747cda 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -1,10 +1,10 @@ -use crate::containers::menu::{menu_style, MenuConfig, MenuState}; +use crate::containers::menu::{MenuConfig, MenuState, menu_style}; use crate::style::StyleModifier; use crate::{ Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response, Sense, Ui, UiKind, UiStackInfo, }; -use emath::{vec2, Align, Pos2, Rect, RectAlign, Vec2}; +use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2}; use std::iter::once; /// What should we anchor the popup to? diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index fe3861646..f7522e020 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -1,6 +1,6 @@ use crate::{ - pos2, vec2, Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense, - Shape, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, + Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense, Shape, Ui, + UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2, }; #[derive(Clone, Copy, Debug)] diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index aaca37291..4c8c9f64b 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -3,8 +3,8 @@ use core::f32; use emath::{GuiRounding as _, Pos2}; use crate::{ - emath::TSTransform, InnerResponse, LayerId, PointerButton, Rangef, Rect, Response, Sense, Ui, - UiBuilder, Vec2, + InnerResponse, LayerId, PointerButton, Rangef, Rect, Response, Sense, Ui, UiBuilder, Vec2, + emath::TSTransform, }; /// Creates a transformation that fits a given scene rectangle into the available screen size. diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 9e4d60d3b..45599ac8c 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -3,8 +3,8 @@ use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; use crate::{ - emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, Context, CursorIcon, Id, - NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, + Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind, + UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, }; #[derive(Clone, Copy, Debug)] diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index b34332ea1..e93b046e5 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -9,7 +9,7 @@ use crate::collapsing_header::CollapsingState; use crate::*; use super::scroll_area::{ScrollBarVisibility, ScrollSource}; -use super::{area, resize, Area, Frame, Resize, ScrollArea}; +use super::{Area, Frame, Resize, ScrollArea, area, resize}; /// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default). /// diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index de0a7e2e4..54ae49036 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -4,16 +4,23 @@ use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration use emath::{GuiRounding as _, OrderedFloat}; use epaint::{ + ClippedPrimitive, ClippedShape, Color32, ImageData, ImageDelta, Pos2, Rect, StrokeKind, + TessellationOptions, TextureAtlas, TextureId, Vec2, emath::{self, TSTransform}, mutex::RwLock, stats::PaintStats, tessellator, text::{FontInsert, FontPriority, Fonts}, - vec2, ClippedPrimitive, ClippedShape, Color32, ImageData, ImageDelta, Pos2, Rect, StrokeKind, - TessellationOptions, TextureAtlas, TextureId, Vec2, + vec2, }; use crate::{ + Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, + ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, + ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, + ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, + ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, + Widget as _, WidgetRect, WidgetText, animation_manager::AnimationManager, containers::{self, area::AreaState}, data::output::PlatformOutput, @@ -29,12 +36,6 @@ use crate::{ resize, response, scroll_area, util::IdTypeMap, viewport::ViewportClass, - Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, - ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, - ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, - ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, - ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, - Widget as _, WidgetRect, WidgetText, }; #[cfg(feature = "accesskit")] @@ -860,7 +861,10 @@ impl Context { if max_passes <= output.platform_output.num_completed_passes { #[cfg(feature = "log")] - log::debug!("Ignoring call request_discard, because max_passes={max_passes}. Requested from {:?}", output.platform_output.request_discard_reasons); + log::debug!( + "Ignoring call request_discard, because max_passes={max_passes}. Requested from {:?}", + output.platform_output.request_discard_reasons + ); break; } @@ -2353,7 +2357,9 @@ impl Context { // If you see this message, it means we've been paying the cost of multi-pass for multiple frames in a row. // This is likely a bug. `request_discard` should only be called in rare situations, when some layout changes. - let mut warning = format!("egui PERF WARNING: request_discard has been called {num_multipass_in_row} frames in a row"); + let mut warning = format!( + "egui PERF WARNING: request_discard has been called {num_multipass_in_row} frames in a row" + ); self.viewport(|vp| { for reason in &vp.output.request_discard_reasons { warning += &format!("\n {reason}"); diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 1e2580678..05b93616d 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -3,8 +3,8 @@ use epaint::ColorImage; use crate::{ - emath::{Pos2, Rect, Vec2}, Key, Theme, ViewportId, ViewportIdMap, + emath::{Pos2, Rect, Vec2}, }; /// What the integrations provides to egui at the start of each frame. diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index bb9487bd3..f487e795f 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -6,7 +6,7 @@ //! to get callbacks on certain events ([`Context::on_begin_pass`], [`Context::on_end_pass`]). use crate::{ - text, Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText, + Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText, text, }; /// Register this plugin on the given egui context, diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index dbc22a09d..31f9e7a71 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -1,8 +1,8 @@ use emath::GuiRounding as _; use crate::{ - vec2, Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style, - Ui, UiBuilder, Vec2, + Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style, Ui, + UiBuilder, Vec2, vec2, }; #[cfg(debug_assertions)] diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index 2f0edcfaf..f253a1dfe 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -2,7 +2,7 @@ use ahash::HashMap; use emath::TSTransform; -use crate::{ahash, emath, id::IdSet, LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects}; +use crate::{LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects, ahash, emath, id::IdSet}; /// Result of a hit-test against [`WidgetRects`]. /// @@ -466,7 +466,7 @@ fn should_prioritize_hits_on_back(back: Rect, front: Rect) -> bool { #[cfg(test)] mod tests { - use emath::{pos2, vec2, Rect}; + use emath::{Rect, pos2, vec2}; use crate::{Id, Sense}; diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 7fd073167..d87bd5669 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -1,11 +1,11 @@ mod touch_state; use crate::data::input::{ - Event, EventFilter, KeyboardShortcut, Modifiers, MouseWheelUnit, PointerButton, RawInput, - TouchDeviceId, ViewportInfo, NUM_POINTER_BUTTONS, + Event, EventFilter, KeyboardShortcut, Modifiers, MouseWheelUnit, NUM_POINTER_BUTTONS, + PointerButton, RawInput, TouchDeviceId, ViewportInfo, }; use crate::{ - emath::{vec2, NumExt as _, Pos2, Rect, Vec2}, + emath::{NumExt as _, Pos2, Rect, Vec2, vec2}, util::History, }; use std::{ diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index b4c789a8e..102b91fff 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -1,9 +1,9 @@ use std::{collections::BTreeMap, fmt::Debug}; use crate::{ - data::input::TouchDeviceId, - emath::{normalized_angle, Pos2, Vec2}, Event, RawInput, TouchId, TouchPhase, + data::input::TouchDeviceId, + emath::{Pos2, Vec2, normalized_angle}, }; /// All you probably need to know about a multi-touch gesture. @@ -174,7 +174,7 @@ impl TouchState { if added_or_removed_touches { // Adding or removing fingers makes the average values "jump". We better forget // about the previous values, and don't create delta information for this frame: - if let Some(ref mut state) = &mut self.gesture_state { + if let Some(state) = &mut self.gesture_state { state.previous = None; } } @@ -224,7 +224,7 @@ impl TouchState { fn update_gesture(&mut self, time: f64, pointer_pos: Option) { if let Some(dyn_state) = self.calc_dynamic_state() { - if let Some(ref mut state) = &mut self.gesture_state { + if let Some(state) = &mut self.gesture_state { // updating an ongoing gesture state.previous = Some(state.current); state.current = dyn_state; diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index b6b63e19d..9cd76b3a0 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -1,6 +1,6 @@ //! How mouse and touch interzcts with widgets. -use crate::{hit_test, id, input_state, memory, Id, InputState, Key, WidgetRects}; +use crate::{Id, InputState, Key, WidgetRects, hit_test, id, input_state, memory}; use self::{hit_test::WidgetHits, id::IdSet, input_state::PointerEvent, memory::InteractionState}; diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index 8826eca79..fa8caa3c8 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -1,7 +1,7 @@ //! Showing UI:s for egui/epaint types. use crate::{ - epaint, memory, pos2, remap_clamp, vec2, Color32, CursorIcon, FontFamily, FontId, Label, Mesh, - NumExt as _, Rect, Response, Sense, Shape, Slider, TextStyle, TextWrapMode, Ui, Widget, + Color32, CursorIcon, FontFamily, FontId, Label, Mesh, NumExt as _, Rect, Response, Sense, + Shape, Slider, TextStyle, TextWrapMode, Ui, Widget, epaint, memory, pos2, remap_clamp, vec2, }; pub fn font_family_ui(ui: &mut Ui, font_family: &mut FontFamily) { diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 81a60812e..927ffc36b 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -1,8 +1,8 @@ //! Handles paint layers, i.e. how things //! are sometimes painted behind or in front of other things. -use crate::{ahash, epaint, Id, IdMap, Rect}; -use epaint::{emath::TSTransform, ClippedShape, Shape}; +use crate::{Id, IdMap, Rect, ahash, epaint}; +use epaint::{ClippedShape, Shape, emath::TSTransform}; /// Different layer categories #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index a0665246b..3064d0d87 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -1,8 +1,8 @@ use emath::GuiRounding as _; use crate::{ - emath::{pos2, vec2, Align2, NumExt as _, Pos2, Rect, Vec2}, Align, + emath::{Align2, NumExt as _, Pos2, Rect, Vec2, pos2, vec2}, }; const INFINITY: f32 = f32::INFINITY; diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index fa390dbc7..b4daa9326 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -463,22 +463,21 @@ pub use epaint::emath; pub use ecolor::hex_color; pub use ecolor::{Color32, Rgba}; pub use emath::{ - lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, RectAlign, - Vec2, Vec2b, + Align, Align2, NumExt, Pos2, Rangef, Rect, RectAlign, Vec2, Vec2b, lerp, pos2, remap, + remap_clamp, vec2, }; pub use epaint::{ - mutex, + ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback, + PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, - ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback, - PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, }; pub mod text { pub use crate::text_selection::CCursorRange; pub use epaint::text::{ - cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, - LayoutSection, TextFormat, TextWrapping, TAB_SIZE, + FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TAB_SIZE, + TextFormat, TextWrapping, cursor::CCursor, }; } @@ -487,12 +486,12 @@ pub use self::{ containers::*, context::{Context, RepaintCause, RequestRepaintInfo}, data::{ + Key, UserData, input::*, output::{ self, CursorIcon, FullOutput, OpenUrl, OutputCommand, PlatformOutput, UserAttentionType, WidgetInfo, }, - Key, UserData, }, drag_and_drop::DragAndDrop, epaint::text::TextWrapMode, diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 9c9df5a9c..43aa1785e 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -65,7 +65,7 @@ use std::{ use ahash::HashMap; use emath::{Float as _, OrderedFloat}; -use epaint::{mutex::Mutex, textures::TextureOptions, ColorImage, TextureHandle, TextureId, Vec2}; +use epaint::{ColorImage, TextureHandle, TextureId, Vec2, mutex::Mutex, textures::TextureOptions}; use crate::Context; diff --git a/crates/egui/src/load/bytes_loader.rs b/crates/egui/src/load/bytes_loader.rs index 5f3e0d3bc..9f0c60356 100644 --- a/crates/egui/src/load/bytes_loader.rs +++ b/crates/egui/src/load/bytes_loader.rs @@ -1,6 +1,6 @@ use super::{ - generate_loader_id, Bytes, BytesLoadResult, BytesLoader, BytesPoll, Context, Cow, HashMap, - LoadError, Mutex, + Bytes, BytesLoadResult, BytesLoader, BytesPoll, Context, Cow, HashMap, LoadError, Mutex, + generate_loader_id, }; /// Maps URI:s to [`Bytes`], e.g. found with `include_bytes!`. diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 9bf482b83..53d9172b0 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -6,8 +6,8 @@ use ahash::{HashMap, HashSet}; use epaint::emath::TSTransform; use crate::{ - area, vec2, EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2, - ViewportId, ViewportIdMap, ViewportIdSet, + EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2, ViewportId, + ViewportIdMap, ViewportIdSet, area, vec2, }; mod theme; @@ -377,8 +377,8 @@ impl Options { reduce_texture_memory, } = self; - use crate::containers::CollapsingHeader; use crate::Widget as _; + use crate::containers::CollapsingHeader; CollapsingHeader::new("⚙ Options") .default_open(false) @@ -1250,8 +1250,11 @@ impl Areas { /// /// The two layers must have the same [`LayerId::order`]. pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) { - debug_assert_eq!(parent.order, child.order, - "DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui", parent.order, child.order); + debug_assert_eq!( + parent.order, child.order, + "DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui", + parent.order, child.order + ); self.sublayers.entry(parent).or_default().insert(child); diff --git a/crates/egui/src/memory/theme.rs b/crates/egui/src/memory/theme.rs index 4a63ecd56..dd4d3d6f9 100644 --- a/crates/egui/src/memory/theme.rs +++ b/crates/egui/src/memory/theme.rs @@ -30,11 +30,7 @@ impl Theme { /// Chooses between [`Self::Dark`] or [`Self::Light`] based on a boolean value. pub fn from_dark_mode(dark_mode: bool) -> Self { - if dark_mode { - Self::Dark - } else { - Self::Light - } + if dark_mode { Self::Dark } else { Self::Light } } } diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 9863f3941..99bbd450b 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -17,14 +17,13 @@ //! ``` use super::{ - style::WidgetVisuals, Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response, - Sense, TextStyle, Ui, Vec2, + Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response, Sense, TextStyle, Ui, + Vec2, style::WidgetVisuals, }; use crate::{ - epaint, vec2, - widgets::{Button, ImageButton}, Align2, Area, Color32, Frame, Key, LayerId, Layout, NumExt as _, Order, Stroke, Style, - TextWrapMode, UiKind, WidgetText, + TextWrapMode, UiKind, WidgetText, epaint, vec2, + widgets::{Button, ImageButton}, }; use epaint::mutex::RwLock; use std::sync::Arc; diff --git a/crates/egui/src/os.rs b/crates/egui/src/os.rs index 283e33863..a9b4a874c 100644 --- a/crates/egui/src/os.rs +++ b/crates/egui/src/os.rs @@ -71,7 +71,8 @@ impl OperatingSystem { #[cfg(feature = "log")] log::warn!( "egui: Failed to guess operating system from User-Agent {:?}. Please file an issue at https://github.com/emilk/egui/issues", - user_agent); + user_agent + ); Self::Unknown } diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index c17fbb272..373142f61 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -2,14 +2,14 @@ use std::sync::Arc; use emath::GuiRounding as _; use epaint::{ - text::{Fonts, Galley, LayoutJob}, CircleShape, ClippedShape, CornerRadius, PathStroke, RectShape, Shape, Stroke, StrokeKind, + text::{Fonts, Galley, LayoutJob}, }; use crate::{ + Color32, Context, FontId, emath::{Align2, Pos2, Rangef, Rect, Vec2}, layers::{LayerId, PaintList, ShapeIdx}, - Color32, Context, FontId, }; /// Helper to paint shapes and text to a specific region on a specific layer. diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index 1f629253c..079bb4eb0 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -1,9 +1,9 @@ use ahash::HashMap; -use crate::{id::IdSet, style, Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects}; +use crate::{Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects, id::IdSet, style}; #[cfg(debug_assertions)] -use crate::{pos2, Align2, Color32, FontId, NumExt as _, Painter}; +use crate::{Align2, Color32, FontId, NumExt as _, Painter, pos2}; /// Reset at the start of each frame. #[derive(Clone, Debug, Default)] diff --git a/crates/egui/src/placer.rs b/crates/egui/src/placer.rs index 6ffe07ed6..6a5d31be0 100644 --- a/crates/egui/src/placer.rs +++ b/crates/egui/src/placer.rs @@ -1,4 +1,4 @@ -use crate::{grid, vec2, Layout, Painter, Pos2, Rect, Region, Vec2}; +use crate::{Layout, Painter, Pos2, Rect, Region, Vec2, grid, vec2}; #[cfg(debug_assertions)] use crate::{Align2, Color32, Stroke}; diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index b54cb10e0..d9dc61c36 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -1,9 +1,10 @@ use std::{any::Any, sync::Arc}; use crate::{ + Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense, Tooltip, Ui, + WidgetRect, WidgetText, emath::{Align, Pos2, Rect, Vec2}, - pass_state, Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense, Tooltip, - Ui, WidgetRect, WidgetText, + pass_state, }; // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 7b73edbb9..354269483 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -3,14 +3,14 @@ #![allow(clippy::if_same_then_else)] use emath::Align; -use epaint::{text::FontTweak, CornerRadius, Shadow, Stroke}; +use epaint::{CornerRadius, Shadow, Stroke, text::FontTweak}; use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc}; use crate::{ - ecolor::Color32, - emath::{pos2, vec2, Rangef, Rect, Vec2}, ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response, RichText, TextWrapMode, WidgetText, + ecolor::Color32, + emath::{Rangef, Rect, Vec2, pos2, vec2}, }; /// How to format numbers in e.g. a [`crate::DragValue`]. @@ -1557,8 +1557,8 @@ impl Default for Widgets { // ---------------------------------------------------------------------------- use crate::{ - widgets::{reset_button, DragValue, Slider, Widget}, Ui, + widgets::{DragValue, Slider, Widget, reset_button}, }; impl Style { diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index e04a54d18..b18995542 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -2,7 +2,7 @@ use emath::TSTransform; use crate::{Context, Galley, Id}; -use super::{text_cursor_state::is_word_char, CCursorRange}; +use super::{CCursorRange, text_cursor_state::is_word_char}; /// Update accesskit with the current text state. pub fn update_accesskit_for_text_widget( diff --git a/crates/egui/src/text_selection/cursor_range.rs b/crates/egui/src/text_selection/cursor_range.rs index 05351e0ac..10980c581 100644 --- a/crates/egui/src/text_selection/cursor_range.rs +++ b/crates/egui/src/text_selection/cursor_range.rs @@ -1,6 +1,6 @@ -use epaint::{text::cursor::CCursor, Galley}; +use epaint::{Galley, text::cursor::CCursor}; -use crate::{os::OperatingSystem, Event, Id, Key, Modifiers}; +use crate::{Event, Id, Key, Modifiers, os::OperatingSystem}; use super::text_cursor_state::{ccursor_next_word, ccursor_previous_word, slice_char_range}; diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index a315c2354..ffbc7ae30 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -3,14 +3,14 @@ use std::sync::Arc; use emath::TSTransform; use crate::{ - layers::ShapeIdx, text::CCursor, text_selection::CCursorRange, Context, CursorIcon, Event, - Galley, Id, LayerId, Pos2, Rect, Response, Ui, + Context, CursorIcon, Event, Galley, Id, LayerId, Pos2, Rect, Response, Ui, layers::ShapeIdx, + text::CCursor, text_selection::CCursorRange, }; use super::{ - text_cursor_state::cursor_rect, - visuals::{paint_text_selection, RowVertexIndices}, TextCursorState, + text_cursor_state::cursor_rect, + visuals::{RowVertexIndices, paint_text_selection}, }; /// Turn on to help debug this diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index d2158c6bd..2a02e4577 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -1,9 +1,9 @@ //! Text cursor changes/interaction, without modifying the text. -use epaint::text::{cursor::CCursor, Galley}; +use epaint::text::{Galley, cursor::CCursor}; use unicode_segmentation::UnicodeSegmentation as _; -use crate::{epaint, NumExt as _, Rect, Response, Ui}; +use crate::{NumExt as _, Rect, Response, Ui, epaint}; use super::CCursorRange; diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index deee5690b..e3054b19d 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{pos2, vec2, Galley, Painter, Rect, Ui, Visuals}; +use crate::{Galley, Painter, Rect, Ui, Visuals, pos2, vec2}; use super::CCursorRange; diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index bc2ed860d..0abd9c054 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -5,11 +5,15 @@ use emath::GuiRounding as _; use epaint::mutex::RwLock; use std::{any::Any, hash::Hash, sync::Arc}; -use crate::close_tag::ClosableTag; -use crate::containers::menu; #[cfg(debug_assertions)] use crate::Stroke; +use crate::close_tag::ClosableTag; +use crate::containers::menu; use crate::{ + Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtoms, + LayerId, Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText, + Sense, Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2, + WidgetRect, WidgetText, containers::{CollapsingHeader, CollapsingResponse, Frame}, ecolor::Hsva, emath, epaint, @@ -22,13 +26,9 @@ use crate::{ util::IdTypeMap, vec2, widgets, widgets::{ - color_picker, Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, - RadioButton, SelectableLabel, Separator, Spinner, TextEdit, Widget, + Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, RadioButton, + SelectableLabel, Separator, Spinner, TextEdit, Widget, color_picker, }, - Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtoms, - LayerId, Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText, - Sense, Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2, - WidgetRect, WidgetText, }; // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index 4aade7d64..fcb389fd9 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -1,8 +1,8 @@ use std::{hash::Hash, sync::Arc}; -use crate::close_tag::ClosableTag; #[expect(unused_imports)] // Used for doclinks use crate::Ui; +use crate::close_tag::ClosableTag; use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo}; /// Build a [`Ui`] as the child of another [`Ui`]. diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index d0edb6a26..75bf36d83 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -4,8 +4,8 @@ use std::fmt::Formatter; use std::{borrow::Cow, sync::Arc}; use crate::{ - text::{LayoutJob, TextWrapping}, Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals, + text::{LayoutJob, TextWrapping}, }; /// Text and optional style choices for it. diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs index f7498de5a..c90cca292 100644 --- a/crates/egui/src/widgets/checkbox.rs +++ b/crates/egui/src/widgets/checkbox.rs @@ -1,6 +1,6 @@ use crate::{ - epaint, pos2, Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, - Vec2, Widget, WidgetInfo, WidgetType, + Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget, + WidgetInfo, WidgetType, epaint, pos2, }; // TODO(emilk): allow checkbox without a text label diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index ebcd45b27..f8605beaf 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -2,12 +2,13 @@ use crate::util::fixed_cache::FixedCache; use crate::{ - epaint, lerp, remap_clamp, Context, DragValue, Id, Painter, Popup, PopupCloseBehavior, - Response, Sense, Ui, Widget as _, WidgetInfo, WidgetType, + Context, DragValue, Id, Painter, Popup, PopupCloseBehavior, Response, Sense, Ui, Widget as _, + WidgetInfo, WidgetType, epaint, lerp, remap_clamp, }; use epaint::{ + Mesh, Rect, Shape, Stroke, StrokeKind, Vec2, ecolor::{Color32, Hsva, HsvaGamma, Rgba}, - pos2, vec2, Mesh, Rect, Shape, Stroke, StrokeKind, Vec2, + pos2, vec2, }; fn contrast_color(color: impl Into) -> Color32 { diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index a9d971916..9515726c2 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -3,8 +3,8 @@ use std::{cmp::Ordering, ops::RangeInclusive}; use crate::{ - emath, text, Button, CursorIcon, Id, Key, Modifiers, NumExt as _, Response, RichText, Sense, - TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, MINUS_CHAR_STR, + Button, CursorIcon, Id, Key, MINUS_CHAR_STR, Modifiers, NumExt as _, Response, RichText, Sense, + TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, emath, text, }; // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/widgets/hyperlink.rs b/crates/egui/src/widgets/hyperlink.rs index 3e5ff88dc..989643304 100644 --- a/crates/egui/src/widgets/hyperlink.rs +++ b/crates/egui/src/widgets/hyperlink.rs @@ -1,6 +1,6 @@ use crate::{ - epaint, text_selection, CursorIcon, Label, Response, Sense, Stroke, Ui, Widget, WidgetInfo, - WidgetText, WidgetType, + CursorIcon, Label, Response, Sense, Stroke, Ui, Widget, WidgetInfo, WidgetText, WidgetType, + epaint, text_selection, }; use self::text_selection::LabelSelectionState; diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 07b08e53c..0ec4e9c90 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -2,14 +2,15 @@ use std::{borrow::Cow, slice::Iter, sync::Arc, time::Duration}; use emath::{Align, Float as _, GuiRounding as _, NumExt as _, Rot2}; use epaint::{ - text::{LayoutJob, TextFormat, TextWrapping}, RectShape, + text::{LayoutJob, TextFormat, TextWrapping}, }; use crate::{ - load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll}, - pos2, Color32, Context, CornerRadius, Id, Mesh, Painter, Rect, Response, Sense, Shape, Spinner, + Color32, Context, CornerRadius, Id, Mesh, Painter, Rect, Response, Sense, Shape, Spinner, TextStyle, TextureOptions, Ui, Vec2, Widget, WidgetInfo, WidgetType, + load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll}, + pos2, }; /// A widget which displays an image. @@ -499,7 +500,7 @@ impl ImageSize { let point_size = match fit { ImageFit::Original { scale } => { - return SizeHint::Scale((pixels_per_point * scale).ord()) + return SizeHint::Scale((pixels_per_point * scale).ord()); } ImageFit::Fraction(fract) => available_size * fract, ImageFit::Exact(size) => size, diff --git a/crates/egui/src/widgets/image_button.rs b/crates/egui/src/widgets/image_button.rs index 7962e5a42..a765a745a 100644 --- a/crates/egui/src/widgets/image_button.rs +++ b/crates/egui/src/widgets/image_button.rs @@ -1,6 +1,6 @@ use crate::{ - widgets, Color32, CornerRadius, Image, Rect, Response, Sense, Ui, Vec2, Widget, WidgetInfo, - WidgetType, + Color32, CornerRadius, Image, Rect, Response, Sense, Ui, Vec2, Widget, WidgetInfo, WidgetType, + widgets, }; /// A clickable image within a frame. diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 9f3606d12..aa229adff 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - epaint, pos2, text_selection::LabelSelectionState, Align, Direction, FontSelection, Galley, - Pos2, Response, Sense, Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, + Align, Direction, FontSelection, Galley, Pos2, Response, Sense, Stroke, TextWrapMode, Ui, + Widget, WidgetInfo, WidgetText, WidgetType, epaint, pos2, text_selection::LabelSelectionState, }; /// Static text. diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index a4a40ec66..d303b181b 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -4,7 +4,7 @@ //! * `ui.add(Label::new("Text").text_color(color::red));` //! * `if ui.add(Button::new("Click me")).clicked() { … }` -use crate::{epaint, Response, Ui}; +use crate::{Response, Ui, epaint}; mod button; mod checkbox; @@ -28,8 +28,8 @@ pub use self::{ drag_value::DragValue, hyperlink::{Hyperlink, Link}, image::{ - decode_animated_image_uri, has_gif_magic_header, has_webp_header, paint_texture_at, FrameDurations, Image, ImageFit, ImageOptions, ImageSize, ImageSource, + decode_animated_image_uri, has_gif_magic_header, has_webp_header, paint_texture_at, }, image_button::ImageButton, label::Label, diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index 6739c0e2e..bba6be8ef 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -1,6 +1,6 @@ use crate::{ - lerp, vec2, Color32, CornerRadius, NumExt as _, Pos2, Rect, Response, Rgba, Sense, Shape, - Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, + Color32, CornerRadius, NumExt as _, Pos2, Rect, Response, Rgba, Sense, Shape, Stroke, + TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, lerp, vec2, }; enum ProgressBarText { diff --git a/crates/egui/src/widgets/radio_button.rs b/crates/egui/src/widgets/radio_button.rs index 53dda399f..8b1f7cc4b 100644 --- a/crates/egui/src/widgets/radio_button.rs +++ b/crates/egui/src/widgets/radio_button.rs @@ -1,6 +1,6 @@ use crate::{ - epaint, Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Ui, Vec2, Widget, - WidgetInfo, WidgetType, + Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Ui, Vec2, Widget, + WidgetInfo, WidgetType, epaint, }; /// One out of several alternatives, either selected or not. diff --git a/crates/egui/src/widgets/separator.rs b/crates/egui/src/widgets/separator.rs index d018cfa4d..6fdd03b96 100644 --- a/crates/egui/src/widgets/separator.rs +++ b/crates/egui/src/widgets/separator.rs @@ -1,4 +1,4 @@ -use crate::{vec2, Response, Sense, Ui, Vec2, Widget}; +use crate::{Response, Sense, Ui, Vec2, Widget, vec2}; /// A visual separator. A horizontal or vertical line (depending on [`crate::Layout`]). /// diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 8ad883491..777c2eed1 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -3,9 +3,9 @@ use std::ops::RangeInclusive; use crate::{ - emath, epaint, lerp, pos2, remap, remap_clamp, style, style::HandleShape, vec2, Color32, - DragValue, EventFilter, Key, Label, NumExt as _, Pos2, Rangef, Rect, Response, Sense, - TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, MINUS_CHAR_STR, + Color32, DragValue, EventFilter, Key, Label, MINUS_CHAR_STR, NumExt as _, Pos2, Rangef, Rect, + Response, Sense, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, emath, + epaint, lerp, pos2, remap, remap_clamp, style, style::HandleShape, vec2, }; use super::drag_value::clamp_value_to_range; @@ -134,11 +134,7 @@ impl<'a> Slider<'a> { value.to_f64() }); - if Num::INTEGRAL { - slf.integer() - } else { - slf - } + if Num::INTEGRAL { slf.integer() } else { slf } } pub fn from_get_set( diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index abb4b27bc..573517dcb 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -1,4 +1,4 @@ -use epaint::{emath::lerp, vec2, Color32, Pos2, Rect, Shape, Stroke}; +use epaint::{Color32, Pos2, Rect, Shape, Stroke, emath::lerp, vec2}; use crate::{Response, Sense, Ui, Widget, WidgetInfo, WidgetType}; diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index e21c512a9..d5a006564 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -2,19 +2,19 @@ use std::sync::Arc; use emath::{Rect, TSTransform}; use epaint::{ - text::{cursor::CCursor, Galley, LayoutJob}, StrokeKind, + text::{Galley, LayoutJob, cursor::CCursor}, }; use crate::{ - epaint, + Align, Align2, Color32, Context, CursorIcon, Event, EventFilter, FontSelection, Id, ImeEvent, + Key, KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, Shape, TextBuffer, + TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetWithState, epaint, os::OperatingSystem, output::OutputEvent, response, text_selection, - text_selection::{text_cursor_state::cursor_rect, visuals::paint_text_selection, CCursorRange}, - vec2, Align, Align2, Color32, Context, CursorIcon, Event, EventFilter, FontSelection, Id, - ImeEvent, Key, KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, Shape, - TextBuffer, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetWithState, + text_selection::{CCursorRange, text_cursor_state::cursor_rect, visuals::paint_text_selection}, + vec2, }; use super::{TextEditOutput, TextEditState}; diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index 0051ea8e7..11304e700 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use crate::mutex::Mutex; use crate::{ - text_selection::{CCursorRange, TextCursorState}, Context, Id, + text_selection::{CCursorRange, TextCursorState}, }; pub type TextEditUndoer = crate::util::undoer::Undoer<(CCursorRange, String)>; diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index ebf33b097..a67dc1b38 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -1,8 +1,8 @@ use std::{borrow::Cow, ops::Range}; use epaint::{ - text::{cursor::CCursor, TAB_SIZE}, Galley, + text::{TAB_SIZE, cursor::CCursor}, }; use crate::{ diff --git a/crates/egui_demo_app/src/apps/fractal_clock.rs b/crates/egui_demo_app/src/apps/fractal_clock.rs index 3eeddd7be..50d9ae5ef 100644 --- a/crates/egui_demo_app/src/apps/fractal_clock.rs +++ b/crates/egui_demo_app/src/apps/fractal_clock.rs @@ -1,8 +1,8 @@ use egui::{ + Color32, Painter, Pos2, Rect, Shape, Stroke, Ui, Vec2, containers::{CollapsingHeader, Frame}, emath, pos2, widgets::Slider, - Color32, Painter, Pos2, Rect, Shape, Stroke, Ui, Vec2, }; use std::f32::consts::TAU; diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index 326718a23..75ff27190 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -1,9 +1,9 @@ -use egui::emath::Rot2; -use egui::panel::Side; -use egui::panel::TopBottomSide; use egui::ImageFit; use egui::Slider; use egui::Vec2; +use egui::emath::Rot2; +use egui::panel::Side; +use egui::panel::TopBottomSide; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ImageViewer { diff --git a/crates/egui_demo_app/src/frame_history.rs b/crates/egui_demo_app/src/frame_history.rs index 231ba4a4f..0b34f858a 100644 --- a/crates/egui_demo_app/src/frame_history.rs +++ b/crates/egui_demo_app/src/frame_history.rs @@ -53,7 +53,7 @@ impl FrameHistory { } fn graph(&self, ui: &mut egui::Ui) -> egui::Response { - use egui::{emath, epaint, pos2, vec2, Pos2, Rect, Sense, Shape, Stroke, TextStyle}; + use egui::{Pos2, Rect, Sense, Shape, Stroke, TextStyle, emath, epaint, pos2, vec2}; ui.label("egui CPU usage history"); diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index cf391ee99..099d16f59 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -16,7 +16,9 @@ fn main() -> eframe::Result { start_puffin_server(); #[cfg(not(feature = "puffin"))] - panic!("Unknown argument: {arg} - you need to enable the 'puffin' feature to use this."); + panic!( + "Unknown argument: {arg} - you need to enable the 'puffin' feature to use this." + ); } _ => { @@ -39,7 +41,12 @@ fn main() -> eframe::Result { rust_log += &format!(",{loud_crate}=warn"); } } - std::env::set_var("RUST_LOG", rust_log); + + // SAFETY: we call this from the main thread without any other threads running. + #[expect(unsafe_code)] + unsafe { + std::env::set_var("RUST_LOG", rust_log); + } } env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). diff --git a/crates/egui_demo_app/tests/test_demo_app.rs b/crates/egui_demo_app/tests/test_demo_app.rs index 65afff10f..961990b90 100644 --- a/crates/egui_demo_app/tests/test_demo_app.rs +++ b/crates/egui_demo_app/tests/test_demo_app.rs @@ -1,8 +1,8 @@ -use egui::accesskit::Role; use egui::Vec2; +use egui::accesskit::Role; use egui_demo_app::{Anchor, WrapApp}; -use egui_kittest::kittest::Queryable as _; use egui_kittest::SnapshotResults; +use egui_kittest::kittest::Queryable as _; #[test] fn test_demo_app() { diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index b511f0de8..e0c86f0db 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -1,6 +1,6 @@ use std::fmt::Write as _; -use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use egui::epaint::TextShape; use egui::load::SizedTexture; @@ -174,7 +174,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut locked_fonts = fonts.lock(); c.bench_function("text_layout_uncached", |b| { b.iter(|| { - use egui::epaint::text::{layout, LayoutJob}; + use egui::epaint::text::{LayoutJob, layout}; let job = LayoutJob::simple( LOREM_IPSUM_LONG.to_owned(), diff --git a/crates/egui_demo_lib/src/demo/dancing_strings.rs b/crates/egui_demo_lib/src/demo/dancing_strings.rs index dd480181d..cc4b578c0 100644 --- a/crates/egui_demo_lib/src/demo/dancing_strings.rs +++ b/crates/egui_demo_lib/src/demo/dancing_strings.rs @@ -1,8 +1,9 @@ use egui::{ + Color32, Context, Pos2, Rect, Ui, containers::{Frame, Window}, emath, epaint, epaint::PathStroke, - hex_color, lerp, pos2, remap, vec2, Color32, Context, Pos2, Rect, Ui, + hex_color, lerp, pos2, remap, vec2, }; #[derive(Default)] diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 6e9a92ef4..0e3a0d2c2 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -1,9 +1,9 @@ use std::collections::BTreeSet; use super::About; -use crate::is_mobile; use crate::Demo; use crate::View as _; +use crate::is_mobile; use egui::containers::menu; use egui::style::StyleModifier; use egui::{Context, Modifiers, ScrollArea, Ui}; @@ -370,7 +370,7 @@ fn file_menu_button(ui: &mut Ui) { #[cfg(test)] mod tests { - use crate::{demo::demo_app_windows::DemoGroups, Demo as _}; + use crate::{Demo as _, demo::demo_app_windows::DemoGroups}; use egui_kittest::kittest::{NodeT as _, Queryable as _}; use egui_kittest::{Harness, SnapshotOptions, SnapshotResults}; diff --git a/crates/egui_demo_lib/src/demo/drag_and_drop.rs b/crates/egui_demo_lib/src/demo/drag_and_drop.rs index 7fa3f01bb..d483ced3f 100644 --- a/crates/egui_demo_lib/src/demo/drag_and_drop.rs +++ b/crates/egui_demo_lib/src/demo/drag_and_drop.rs @@ -1,4 +1,4 @@ -use egui::{vec2, Color32, Context, Frame, Id, Ui, Window}; +use egui::{Color32, Context, Frame, Id, Ui, Window, vec2}; #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 99d85aeeb..5af27d5e4 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -1,8 +1,8 @@ use super::{Demo, View}; use egui::{ - vec2, Align, Align2, Checkbox, CollapsingHeader, Color32, ComboBox, Context, FontId, Resize, - RichText, Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, + Align, Align2, Checkbox, CollapsingHeader, Color32, ComboBox, Context, FontId, Resize, + RichText, Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, vec2, }; /// Showcase some ui code diff --git a/crates/egui_demo_lib/src/demo/modals.rs b/crates/egui_demo_lib/src/demo/modals.rs index 5fb1548e3..0aefbce82 100644 --- a/crates/egui_demo_lib/src/demo/modals.rs +++ b/crates/egui_demo_lib/src/demo/modals.rs @@ -162,10 +162,10 @@ impl crate::View for Modals { #[cfg(test)] mod tests { - use crate::demo::modals::Modals; use crate::Demo as _; - use egui::accesskit::Role; + use crate::demo::modals::Modals; use egui::Key; + use egui::accesskit::Role; use egui_kittest::kittest::Queryable as _; use egui_kittest::{Harness, SnapshotResults}; diff --git a/crates/egui_demo_lib/src/demo/multi_touch.rs b/crates/egui_demo_lib/src/demo/multi_touch.rs index b6580c5f8..0c2d98202 100644 --- a/crates/egui_demo_lib/src/demo/multi_touch.rs +++ b/crates/egui_demo_lib/src/demo/multi_touch.rs @@ -1,6 +1,7 @@ use egui::{ + Color32, Frame, Pos2, Rect, Sense, Stroke, Vec2, emath::{RectTransform, Rot2}, - vec2, Color32, Frame, Pos2, Rect, Sense, Stroke, Vec2, + vec2, }; pub struct MultiTouch { diff --git a/crates/egui_demo_lib/src/demo/paint_bezier.rs b/crates/egui_demo_lib/src/demo/paint_bezier.rs index df85e4377..7017560a5 100644 --- a/crates/egui_demo_lib/src/demo/paint_bezier.rs +++ b/crates/egui_demo_lib/src/demo/paint_bezier.rs @@ -1,8 +1,8 @@ use egui::{ - emath, + Color32, Context, Frame, Grid, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, Ui, Vec2, + Widget as _, Window, emath, epaint::{self, CubicBezierShape, PathShape, QuadraticBezierShape}, - pos2, Color32, Context, Frame, Grid, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, Ui, Vec2, - Widget as _, Window, + pos2, }; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui_demo_lib/src/demo/painting.rs b/crates/egui_demo_lib/src/demo/painting.rs index 8e1df7ae7..5a4942f68 100644 --- a/crates/egui_demo_lib/src/demo/painting.rs +++ b/crates/egui_demo_lib/src/demo/painting.rs @@ -1,4 +1,4 @@ -use egui::{emath, vec2, Color32, Context, Frame, Pos2, Rect, Sense, Stroke, Ui, Window}; +use egui::{Color32, Context, Frame, Pos2, Rect, Sense, Stroke, Ui, Window, emath, vec2}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] diff --git a/crates/egui_demo_lib/src/demo/popups.rs b/crates/egui_demo_lib/src/demo/popups.rs index f9815a6bf..6f7152c2b 100644 --- a/crates/egui_demo_lib/src/demo/popups.rs +++ b/crates/egui_demo_lib/src/demo/popups.rs @@ -1,9 +1,9 @@ use crate::rust_view_ui; -use egui::color_picker::{color_picker_color32, Alpha}; +use egui::color_picker::{Alpha, color_picker_color32}; use egui::containers::menu::{MenuConfig, SubMenuButton}; use egui::{ - include_image, Align, Align2, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior, - RectAlign, RichText, Tooltip, Ui, UiBuilder, + Align, Align2, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior, RectAlign, RichText, + Tooltip, Ui, UiBuilder, include_image, }; /// Showcase [`Popup`]. diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index dab3c4f5a..3e4660d3d 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -1,6 +1,6 @@ use egui::{ - pos2, scroll_area::ScrollBarVisibility, Align, Align2, Color32, DragValue, NumExt as _, Rect, - ScrollArea, Sense, Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget as _, + Align, Align2, Color32, DragValue, NumExt as _, Rect, ScrollArea, Sense, Slider, TextStyle, + TextWrapMode, Ui, Vec2, Widget as _, pos2, scroll_area::ScrollBarVisibility, }; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui_demo_lib/src/demo/sliders.rs b/crates/egui_demo_lib/src/demo/sliders.rs index ef8bdb0cd..6b512e1ea 100644 --- a/crates/egui_demo_lib/src/demo/sliders.rs +++ b/crates/egui_demo_lib/src/demo/sliders.rs @@ -1,4 +1,4 @@ -use egui::{style::HandleShape, Slider, SliderClamping, SliderOrientation, Ui}; +use egui::{Slider, SliderClamping, SliderOrientation, Ui, style::HandleShape}; /// Showcase sliders #[derive(PartialEq)] diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 17e19d01d..5a55569b7 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -330,7 +330,9 @@ fn expanding_content(ui: &mut egui::Ui) { } fn long_text(row_index: usize) -> String { - format!("Row {row_index} has some long text that you may want to clip, or it will take up too much horizontal space!") + format!( + "Row {row_index} has some long text that you may want to clip, or it will take up too much horizontal space!" + ) } fn thick_row(row_index: usize) -> bool { diff --git a/crates/egui_demo_lib/src/demo/tests/layout_test.rs b/crates/egui_demo_lib/src/demo/tests/layout_test.rs index f58369121..f63eff0a9 100644 --- a/crates/egui_demo_lib/src/demo/tests/layout_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/layout_test.rs @@ -1,4 +1,4 @@ -use egui::{vec2, Align, Direction, Layout, Resize, Slider, Ui}; +use egui::{Align, Direction, Layout, Resize, Slider, Ui, vec2}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] diff --git a/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs b/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs index 20e8d21bf..c1a325a5f 100644 --- a/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs @@ -1,7 +1,8 @@ use egui::{ + Color32, Pos2, Rect, Sense, StrokeKind, Vec2, emath::{GuiRounding as _, TSTransform}, epaint::{self, RectShape}, - vec2, Color32, Pos2, Rect, Sense, StrokeKind, Vec2, + vec2, }; #[derive(Clone, Debug, PartialEq)] diff --git a/crates/egui_demo_lib/src/demo/text_edit.rs b/crates/egui_demo_lib/src/demo/text_edit.rs index 4ef34a51e..a36ad6837 100644 --- a/crates/egui_demo_lib/src/demo/text_edit.rs +++ b/crates/egui_demo_lib/src/demo/text_edit.rs @@ -113,9 +113,9 @@ impl crate::View for TextEditDemo { #[cfg(test)] mod tests { - use egui::{accesskit, CentralPanel, Key, Modifiers}; - use egui_kittest::kittest::Queryable as _; + use egui::{CentralPanel, Key, Modifiers, accesskit}; use egui_kittest::Harness; + use egui_kittest::kittest::Queryable as _; #[test] pub fn should_type() { diff --git a/crates/egui_demo_lib/src/demo/undo_redo.rs b/crates/egui_demo_lib/src/demo/undo_redo.rs index 525e26c6f..04610031c 100644 --- a/crates/egui_demo_lib/src/demo/undo_redo.rs +++ b/crates/egui_demo_lib/src/demo/undo_redo.rs @@ -1,4 +1,4 @@ -use egui::{util::undoer::Undoer, Button}; +use egui::{Button, util::undoer::Undoer}; #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 292e5f0aa..d17385a68 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -1,5 +1,5 @@ use egui::{ - text::CCursorRange, Key, KeyboardShortcut, Modifiers, ScrollArea, TextBuffer, TextEdit, Ui, + Key, KeyboardShortcut, Modifiers, ScrollArea, TextBuffer, TextEdit, Ui, text::CCursorRange, }; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs index 17d3858f7..13ff7da03 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs @@ -1,7 +1,7 @@ use super::easy_mark_parser as easy_mark; use egui::{ - vec2, Align, Align2, Hyperlink, Layout, Response, RichText, Sense, Separator, Shape, TextStyle, - Ui, + Align, Align2, Hyperlink, Layout, Response, RichText, Sense, Separator, Shape, TextStyle, Ui, + vec2, }; /// Parse and display a VERY simple and small subset of Markdown. diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index 32e51a82a..e14bfca07 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use egui::{ - emath::GuiRounding as _, epaint, lerp, pos2, vec2, widgets::color_picker::show_color, Align2, - Color32, FontId, Image, Mesh, Pos2, Rect, Response, Rgba, RichText, Sense, Shape, Stroke, - TextureHandle, TextureOptions, Ui, Vec2, + Align2, Color32, FontId, Image, Mesh, Pos2, Rect, Response, Rgba, RichText, Sense, Shape, + Stroke, TextureHandle, TextureOptions, Ui, Vec2, emath::GuiRounding as _, epaint, lerp, pos2, + vec2, widgets::color_picker::show_color, }; const GRADIENT_SIZE: Vec2 = vec2(256.0, 18.0); @@ -722,8 +722,8 @@ fn mul_color_gamma(left: Color32, right: Color32) -> Color32 { #[cfg(test)] mod tests { use crate::ColorTest; - use egui_kittest::kittest::Queryable as _; use egui_kittest::SnapshotResults; + use egui_kittest::kittest::Queryable as _; #[test] pub fn rendering_test() { diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index b1c79d0a6..d9210187b 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -1,4 +1,4 @@ -use egui::{emath::GuiRounding as _, Id, Pos2, Rect, Response, Sense, Ui, UiBuilder}; +use egui::{Id, Pos2, Rect, Response, Sense, Ui, UiBuilder, emath::GuiRounding as _}; #[derive(Clone, Copy)] pub(crate) enum CellSize { diff --git a/crates/egui_extras/src/loaders/ehttp_loader.rs b/crates/egui_extras/src/loaders/ehttp_loader.rs index 22785eedb..abe5d96f1 100644 --- a/crates/egui_extras/src/loaders/ehttp_loader.rs +++ b/crates/egui_extras/src/loaders/ehttp_loader.rs @@ -19,13 +19,13 @@ impl File { return Err(format!( "failed to load {uri:?}: {} {} {response_text}", response.status, response.status_text - )) + )); } None => { return Err(format!( "failed to load {uri:?}: {} {}", response.status, response.status_text - )) + )); } } } diff --git a/crates/egui_extras/src/loaders/gif_loader.rs b/crates/egui_extras/src/loaders/gif_loader.rs index a92cbc33e..9f0786cfb 100644 --- a/crates/egui_extras/src/loaders/gif_loader.rs +++ b/crates/egui_extras/src/loaders/gif_loader.rs @@ -1,9 +1,8 @@ use ahash::HashMap; use egui::{ - decode_animated_image_uri, has_gif_magic_header, + ColorImage, FrameDurations, Id, decode_animated_image_uri, has_gif_magic_header, load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, mutex::Mutex, - ColorImage, FrameDurations, Id, }; use image::AnimationDecoder as _; use std::{io::Cursor, mem::size_of, sync::Arc, time::Duration}; diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 7f472dcc4..18e1e483b 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -1,9 +1,8 @@ use ahash::HashMap; use egui::{ - decode_animated_image_uri, + ColorImage, decode_animated_image_uri, load::{Bytes, BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, mutex::Mutex, - ColorImage, }; use image::ImageFormat; use std::{mem::size_of, path::Path, sync::Arc, task::Poll}; diff --git a/crates/egui_extras/src/loaders/svg_loader.rs b/crates/egui_extras/src/loaders/svg_loader.rs index fab70151f..4b778ff9e 100644 --- a/crates/egui_extras/src/loaders/svg_loader.rs +++ b/crates/egui_extras/src/loaders/svg_loader.rs @@ -1,17 +1,17 @@ use std::{ mem::size_of, sync::{ - atomic::{AtomicU64, Ordering::Relaxed}, Arc, + atomic::{AtomicU64, Ordering::Relaxed}, }, }; use ahash::HashMap; use egui::{ + ColorImage, load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, mutex::Mutex, - ColorImage, }; struct Entry { diff --git a/crates/egui_extras/src/loaders/webp_loader.rs b/crates/egui_extras/src/loaders/webp_loader.rs index ef1a5d527..f0dc32ae4 100644 --- a/crates/egui_extras/src/loaders/webp_loader.rs +++ b/crates/egui_extras/src/loaders/webp_loader.rs @@ -1,11 +1,10 @@ use ahash::HashMap; use egui::{ - decode_animated_image_uri, has_webp_header, + ColorImage, FrameDurations, Id, decode_animated_image_uri, has_webp_header, load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, mutex::Mutex, - ColorImage, FrameDurations, Id, }; -use image::{codecs::webp::WebPDecoder, AnimationDecoder as _, ColorType, ImageDecoder as _, Rgba}; +use image::{AnimationDecoder as _, ColorType, ImageDecoder as _, Rgba, codecs::webp::WebPDecoder}; use std::{io::Cursor, mem::size_of, sync::Arc, time::Duration}; #[derive(Clone)] @@ -55,7 +54,7 @@ impl WebP { unreachable => { return Err(format!( "Unreachable WebP color type, expected Rgb8/Rgba8, got {unreachable:?}" - )) + )); } }; diff --git a/crates/egui_extras/src/strip.rs b/crates/egui_extras/src/strip.rs index 00fc65774..da64b4f9e 100644 --- a/crates/egui_extras/src/strip.rs +++ b/crates/egui_extras/src/strip.rs @@ -1,7 +1,7 @@ use crate::{ + Size, layout::{CellDirection, CellSize, StripLayout, StripLayoutFlags}, sizing::Sizing, - Size, }; use egui::{Response, Ui}; diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 2476e783b..894f2cc24 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -5,8 +5,8 @@ #![allow(clippy::mem_forget)] // False positive from enum_map macro -use egui::text::LayoutJob; use egui::TextStyle; +use egui::text::LayoutJob; /// View some code with syntax highlighting and selection. pub fn code_view_ui( diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 2d64f2570..9d77c60c8 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -4,13 +4,13 @@ //! Takes all available height, so if you want something below the table, put it in a strip. use egui::{ - scroll_area::{ScrollAreaOutput, ScrollBarVisibility, ScrollSource}, Align, Id, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b, + scroll_area::{ScrollAreaOutput, ScrollBarVisibility, ScrollSource}, }; use crate::{ - layout::{CellDirection, CellSize, StripLayoutFlags}, StripLayout, + layout::{CellDirection, CellSize, StripLayoutFlags}, }; // -----------------------------------------------------------------=---------- diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index aaea1d0b9..174f02226 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -62,12 +62,8 @@ macro_rules! check_for_gl_error { /// ``` #[macro_export] macro_rules! check_for_gl_error_even_in_release { - ($gl: expr) => {{ - $crate::check_for_gl_error_impl($gl, file!(), line!(), "") - }}; - ($gl: expr, $context: literal) => {{ - $crate::check_for_gl_error_impl($gl, file!(), line!(), $context) - }}; + ($gl: expr) => {{ $crate::check_for_gl_error_impl($gl, file!(), line!(), "") }}; + ($gl: expr, $context: literal) => {{ $crate::check_for_gl_error_impl($gl, file!(), line!(), $context) }}; } #[doc(hidden)] diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 0646b560d..a5e7b3c1f 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -445,7 +445,9 @@ impl Painter { if let Some(callback) = callback.callback.downcast_ref::() { (callback.f)(info, self); } else { - log::warn!("Warning: Unsupported render callback. Expected egui_glow::CallbackFn"); + log::warn!( + "Warning: Unsupported render callback. Expected egui_glow::CallbackFn" + ); } check_for_gl_error!(&self.gl, "callback"); diff --git a/crates/egui_glow/src/shader_version.rs b/crates/egui_glow/src/shader_version.rs index 249cda369..b655c567c 100644 --- a/crates/egui_glow/src/shader_version.rs +++ b/crates/egui_glow/src/shader_version.rs @@ -47,11 +47,7 @@ impl ShaderVersion { .try_into() .unwrap(); if es { - if maj >= 3 { - Self::Es300 - } else { - Self::Es100 - } + if maj >= 3 { Self::Es300 } else { Self::Es100 } } else if maj > 1 || (maj == 1 && min >= 40) { Self::Gl140 } else { diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index c4569015a..2fe15dcd0 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -1,8 +1,8 @@ use ahash::HashSet; use egui::{ViewportId, ViewportOutput}; pub use egui_winit; -use egui_winit::winit; pub use egui_winit::EventResponse; +use egui_winit::winit; use crate::shader_version::ShaderVersion; diff --git a/crates/egui_kittest/src/node.rs b/crates/egui_kittest/src/node.rs index 384a5dbd4..51a0cc3a0 100644 --- a/crates/egui_kittest/src/node.rs +++ b/crates/egui_kittest/src/node.rs @@ -1,7 +1,7 @@ use egui::accesskit::ActionRequest; use egui::mutex::Mutex; -use egui::{accesskit, Modifiers, PointerButton, Pos2}; -use kittest::{debug_fmt_node, AccessKitNode, NodeT}; +use egui::{Modifiers, PointerButton, Pos2, accesskit}; +use kittest::{AccessKitNode, NodeT, debug_fmt_node}; use std::fmt::{Debug, Formatter}; pub(crate) enum EventType { diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index c85a09070..3c7dc265a 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -128,11 +128,17 @@ impl Display for SnapshotError { write!(f, "Missing snapshot: {path:?}. {HOW_TO_UPDATE_SCREENSHOTS}") } err => { - write!(f, "Error reading snapshot: {err:?}\nAt: {path:?}. {HOW_TO_UPDATE_SCREENSHOTS}") + write!( + f, + "Error reading snapshot: {err:?}\nAt: {path:?}. {HOW_TO_UPDATE_SCREENSHOTS}" + ) } }, err => { - write!(f, "Error decoding snapshot: {err:?}\nAt: {path:?}. Make sure git-lfs is setup correctly. Read the instructions here: https://github.com/emilk/egui/blob/main/CONTRIBUTING.md#making-a-pr") + write!( + f, + "Error decoding snapshot: {err:?}\nAt: {path:?}. Make sure git-lfs is setup correctly. Read the instructions here: https://github.com/emilk/egui/blob/main/CONTRIBUTING.md#making-a-pr" + ) } } } @@ -555,11 +561,7 @@ impl SnapshotResults { /// Convert this into a `Result<(), Self>`. #[expect(clippy::missing_errors_doc)] pub fn into_result(self) -> Result<(), Self> { - if self.has_errors() { - Err(self) - } else { - Ok(()) - } + if self.has_errors() { Err(self) } else { Ok(()) } } pub fn into_inner(mut self) -> Vec { diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index efd954245..e0fef2901 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -2,7 +2,7 @@ use std::iter::once; use std::sync::Arc; use egui::TexturesDelta; -use egui_wgpu::{wgpu, RenderState, ScreenDescriptor, WgpuSetup}; +use egui_wgpu::{RenderState, ScreenDescriptor, WgpuSetup, wgpu}; use image::RgbaImage; use crate::texture_to_image::texture_to_image; diff --git a/crates/egui_kittest/tests/accesskit.rs b/crates/egui_kittest/tests/accesskit.rs index 02afddefc..3f1f33ba9 100644 --- a/crates/egui_kittest/tests/accesskit.rs +++ b/crates/egui_kittest/tests/accesskit.rs @@ -1,8 +1,8 @@ //! Tests the accesskit accessibility output of egui. use egui::{ - accesskit::{NodeId, Role, TreeUpdate}, CentralPanel, Context, RawInput, Window, + accesskit::{NodeId, Role, TreeUpdate}, }; /// Baseline test that asserts there are no spurious nodes in the diff --git a/crates/egui_kittest/tests/menu.rs b/crates/egui_kittest/tests/menu.rs index 48f348a21..00470bd5c 100644 --- a/crates/egui_kittest/tests/menu.rs +++ b/crates/egui_kittest/tests/menu.rs @@ -1,5 +1,5 @@ use egui::containers::menu::{Bar, MenuConfig, SubMenuButton}; -use egui::{include_image, PopupCloseBehavior, Ui}; +use egui::{PopupCloseBehavior, Ui, include_image}; use egui_kittest::{Harness, SnapshotResults}; use kittest::Queryable as _; diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index 5c7b4fc53..0cae152bf 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -2,7 +2,7 @@ use egui::accesskit::{self, Role}; use egui::{Button, ComboBox, Image, Vec2, Widget as _}; #[cfg(all(feature = "wgpu", feature = "snapshot"))] use egui_kittest::SnapshotResults; -use egui_kittest::{kittest::Queryable as _, Harness}; +use egui_kittest::{Harness, kittest::Queryable as _}; #[test] pub fn focus_should_skip_over_disabled_buttons() { diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index 8c92f4314..b4e49642f 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -1,4 +1,4 @@ -use egui::{include_image, Modifiers, Vec2}; +use egui::{Modifiers, Vec2, include_image}; use egui_kittest::Harness; use kittest::Queryable as _; diff --git a/crates/emath/src/align.rs b/crates/emath/src/align.rs index b1b56755e..89b28d4af 100644 --- a/crates/emath/src/align.rs +++ b/crates/emath/src/align.rs @@ -1,6 +1,6 @@ //! One- and two-dimensional alignment ([`Align::Center`], [`Align2::LEFT_TOP`] etc). -use crate::{pos2, vec2, Pos2, Rangef, Rect, Vec2}; +use crate::{Pos2, Rangef, Rect, Vec2, pos2, vec2}; /// left/center/right or top/center/bottom alignment for e.g. anchors and layouts. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] diff --git a/crates/emath/src/easing.rs b/crates/emath/src/easing.rs index 352c451c2..95fc7250d 100644 --- a/crates/emath/src/easing.rs +++ b/crates/emath/src/easing.rs @@ -137,11 +137,7 @@ pub fn exponential_in(t: f32) -> f32 { /// There is a small discontinuity at 1. #[inline] pub fn exponential_out(t: f32) -> f32 { - if t == 1. { - t - } else { - 1. - powf(2.0, -10. * t) - } + if t == 1. { t } else { 1. - powf(2.0, -10. * t) } } /// diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 337fa2045..2d9dfb2f1 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -44,7 +44,7 @@ mod vec2b; pub use self::{ align::{Align, Align2}, - gui_rounding::{GuiRounding, GUI_ROUNDING}, + gui_rounding::{GUI_ROUNDING, GuiRounding}, history::History, numeric::*, ordered_float::*, @@ -182,11 +182,7 @@ where ); let t = (x - *from.start()) / (*from.end() - *from.start()); // Ensure no numerical inaccuracies sneak in: - if T::ONE <= t { - *to.end() - } else { - lerp(to, t) - } + if T::ONE <= t { *to.end() } else { lerp(to, t) } } } diff --git a/crates/emath/src/pos2.rs b/crates/emath/src/pos2.rs index 62590b10f..fc26686b3 100644 --- a/crates/emath/src/pos2.rs +++ b/crates/emath/src/pos2.rs @@ -3,7 +3,7 @@ use std::{ ops::{Add, AddAssign, MulAssign, Sub, SubAssign}, }; -use crate::{lerp, Div, Mul, Vec2}; +use crate::{Div, Mul, Vec2, lerp}; /// A position on screen. /// diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 00bed04f0..dc63315b6 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{lerp, pos2, vec2, Div, Mul, Pos2, Rangef, Rot2, Vec2}; +use crate::{Div, Mul, Pos2, Rangef, Rot2, Vec2, lerp, pos2, vec2}; /// A rectangular region of space. /// diff --git a/crates/emath/src/rect_transform.rs b/crates/emath/src/rect_transform.rs index da9382790..3539efe75 100644 --- a/crates/emath/src/rect_transform.rs +++ b/crates/emath/src/rect_transform.rs @@ -1,4 +1,4 @@ -use crate::{pos2, remap, remap_clamp, Pos2, Rect, Vec2}; +use crate::{Pos2, Rect, Vec2, pos2, remap, remap_clamp}; /// Linearly transforms positions from one [`Rect`] to another. /// diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 4771343e8..f79359df9 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -170,11 +170,7 @@ impl Vec2 { #[inline(always)] pub fn normalized(self) -> Self { let len = self.length(); - if len <= 0.0 { - self - } else { - self / len - } + if len <= 0.0 { self } else { self / len } } /// Checks if `self` has length `1.0` up to a precision of `1e-6`. diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 14f4d2fa7..676e1d0fd 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -1,8 +1,8 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use epaint::{ - pos2, tessellator::Path, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, - TessellationOptions, Tessellator, TextureAtlas, Vec2, + ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions, + Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, }; #[global_allocator] diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index b6183e7a1..8fcef2df7 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -1,6 +1,6 @@ use emath::Vec2; -use crate::{textures::TextureOptions, Color32}; +use crate::{Color32, textures::TextureOptions}; use std::sync::Arc; /// An image stored in RAM. diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 633aa6689..264966809 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -73,7 +73,7 @@ pub use self::{ pub type Rounding = CornerRadius; pub use ecolor::{Color32, Hsva, HsvaGamma, Rgba}; -pub use emath::{pos2, vec2, Pos2, Rect, Vec2}; +pub use emath::{Pos2, Rect, Vec2, pos2, vec2}; #[deprecated = "Use the ahash crate directly."] pub use ahash; diff --git a/crates/epaint/src/margin.rs b/crates/epaint/src/margin.rs index 417cc5568..e6f6d2287 100644 --- a/crates/epaint/src/margin.rs +++ b/crates/epaint/src/margin.rs @@ -1,4 +1,4 @@ -use emath::{vec2, Rect, Vec2}; +use emath::{Rect, Vec2, vec2}; /// A value for all four sides of a rectangle, /// often used to express padding or spacing. diff --git a/crates/epaint/src/margin_f32.rs b/crates/epaint/src/margin_f32.rs index fd88611d0..22bc8b3d4 100644 --- a/crates/epaint/src/margin_f32.rs +++ b/crates/epaint/src/margin_f32.rs @@ -1,4 +1,4 @@ -use emath::{vec2, Rect, Vec2}; +use emath::{Rect, Vec2, vec2}; use crate::Margin; diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index 60be2935c..fbff16ba2 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -1,4 +1,4 @@ -use crate::{emath, Color32, TextureId, WHITE_UV}; +use crate::{Color32, TextureId, WHITE_UV, emath}; use emath::{Pos2, Rect, Rot2, TSTransform, Vec2}; /// The 2D vertex type. diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index ee010ee99..ace5ab90a 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -29,7 +29,8 @@ pub struct Shadow { #[test] fn shadow_size() { assert_eq!( - std::mem::size_of::(), 8, + std::mem::size_of::(), + 8, "Shadow changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." ); } diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 57de14969..4b68d5964 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - color, CircleShape, Color32, ColorMode, CubicBezierShape, EllipseShape, Mesh, PathShape, - QuadraticBezierShape, RectShape, Shape, TextShape, + CircleShape, Color32, ColorMode, CubicBezierShape, EllipseShape, Mesh, PathShape, + QuadraticBezierShape, RectShape, Shape, TextShape, color, }; /// Remember to handle [`Color32::PLACEHOLDER`] specially! diff --git a/crates/epaint/src/shapes/rect_shape.rs b/crates/epaint/src/shapes/rect_shape.rs index ead5b7af2..2e855d369 100644 --- a/crates/epaint/src/shapes/rect_shape.rs +++ b/crates/epaint/src/shapes/rect_shape.rs @@ -59,7 +59,8 @@ pub struct RectShape { #[test] fn rect_shape_size() { assert_eq!( - std::mem::size_of::(), 48, + std::mem::size_of::(), + 48, "RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." ); assert!( diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index bc16e43d5..080f1794b 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -2,12 +2,12 @@ use std::sync::Arc; -use emath::{pos2, Align2, Pos2, Rangef, Rect, TSTransform, Vec2}; +use emath::{Align2, Pos2, Rangef, Rect, TSTransform, Vec2, pos2}; use crate::{ + Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId, stroke::PathStroke, text::{FontId, Fonts, Galley}, - Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId, }; use super::{ @@ -73,7 +73,8 @@ pub enum Shape { #[test] fn shape_size() { assert_eq!( - std::mem::size_of::(), 64, + std::mem::size_of::(), + 64, "Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." ); assert!( diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index 50f4f678b..473376f40 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -4,7 +4,7 @@ use std::{fmt::Debug, sync::Arc}; use emath::GuiRounding as _; -use super::{emath, Color32, ColorMode, Pos2, Rect}; +use super::{Color32, ColorMode, Pos2, Rect, emath}; /// Describes the width and color of a line. /// diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index b083ea452..4b1b23ba0 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -5,13 +5,13 @@ #![allow(clippy::identity_op)] -use emath::{pos2, remap, vec2, GuiRounding as _, NumExt as _, Pos2, Rect, Rot2, Vec2}; +use emath::{GuiRounding as _, NumExt as _, Pos2, Rect, Rot2, Vec2, pos2, remap, vec2}; use crate::{ - color::ColorMode, emath, stroke::PathStroke, texture_atlas::PreparedDisc, CircleShape, - ClippedPrimitive, ClippedShape, Color32, CornerRadiusF32, CubicBezierShape, EllipseShape, Mesh, - PathShape, Primitive, QuadraticBezierShape, RectShape, Shape, Stroke, StrokeKind, TextShape, - TextureId, Vertex, WHITE_UV, + CircleShape, ClippedPrimitive, ClippedShape, Color32, CornerRadiusF32, CubicBezierShape, + EllipseShape, Mesh, PathShape, Primitive, QuadraticBezierShape, RectShape, Shape, Stroke, + StrokeKind, TextShape, TextureId, Vertex, WHITE_UV, color::ColorMode, emath, + stroke::PathStroke, texture_atlas::PreparedDisc, }; // ---------------------------------------------------------------------------- @@ -28,7 +28,7 @@ mod precomputed_vertices { // println!("];") // } - use emath::{vec2, Vec2}; + use emath::{Vec2, vec2}; pub const CIRCLE_8: [Vec2; 9] = [ vec2(1.000000, 0.000000), @@ -340,7 +340,7 @@ impl Path { } pub fn add_circle(&mut self, center: Pos2, radius: f32) { - use precomputed_vertices::{CIRCLE_128, CIRCLE_16, CIRCLE_32, CIRCLE_64, CIRCLE_8}; + use precomputed_vertices::{CIRCLE_8, CIRCLE_16, CIRCLE_32, CIRCLE_64, CIRCLE_128}; // These cutoffs are based on a high-dpi display. TODO(emilk): use pixels_per_point here? // same cutoffs as in add_circle_quadrant @@ -535,7 +535,7 @@ impl Path { pub mod path { //! Helpers for constructing paths use crate::CornerRadiusF32; - use emath::{pos2, Pos2, Rect}; + use emath::{Pos2, Rect, pos2}; /// overwrites existing points pub fn rounded_rectangle(path: &mut Vec, rect: Rect, cr: CornerRadiusF32) { @@ -602,7 +602,7 @@ pub mod path { // - quadrant 3: right top // * angle 4 * TAU / 4 = right pub fn add_circle_quadrant(path: &mut Vec, center: Pos2, radius: f32, quadrant: f32) { - use super::precomputed_vertices::{CIRCLE_128, CIRCLE_16, CIRCLE_32, CIRCLE_64, CIRCLE_8}; + use super::precomputed_vertices::{CIRCLE_8, CIRCLE_16, CIRCLE_32, CIRCLE_64, CIRCLE_128}; // These cutoffs are based on a high-dpi display. TODO(emilk): use pixels_per_point here? // same cutoffs as in add_circle diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index b79bea2f2..72fffaad0 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -1,12 +1,12 @@ use std::collections::BTreeMap; use std::sync::Arc; -use emath::{vec2, GuiRounding as _, Vec2}; +use emath::{GuiRounding as _, Vec2, vec2}; use crate::{ + TextureAtlas, mutex::{Mutex, RwLock}, text::FontTweak, - TextureAtlas, }; // ---------------------------------------------------------------------------- diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index e65514215..6e90b5666 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -1,12 +1,12 @@ use std::{collections::BTreeMap, sync::Arc}; use crate::{ + TextureAtlas, mutex::{Mutex, MutexGuard}, text::{ - font::{Font, FontImpl}, Galley, LayoutJob, LayoutSection, + font::{Font, FontImpl}, }, - TextureAtlas, }; use emath::{NumExt as _, OrderedFloat}; @@ -680,7 +680,8 @@ impl FontsImpl { /// Get the right font implementation from size and [`FontFamily`]. pub fn font(&mut self, font_id: &FontId) -> &mut Font { - let FontId { mut size, family } = font_id; + let FontId { size, family } = font_id; + let mut size = *size; size = size.at_least(0.1).at_most(2048.0); self.sized_family @@ -1050,7 +1051,7 @@ mod tests { use core::f32; use super::*; - use crate::{text::TextFormat, Stroke}; + use crate::{Stroke, text::TextFormat}; use ecolor::Color32; use emath::Align; diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 63dfc3893..3777d860c 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1,8 +1,8 @@ use std::sync::Arc; -use emath::{pos2, vec2, Align, GuiRounding as _, NumExt as _, Pos2, Rect, Vec2}; +use emath::{Align, GuiRounding as _, NumExt as _, Pos2, Rect, Vec2, pos2, vec2}; -use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex}; +use crate::{Color32, Mesh, Stroke, Vertex, stroke::PathStroke, text::font::Font}; use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals}; diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index f7e11911b..016bfe104 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -9,7 +9,7 @@ use super::{ font::UvRect, }; use crate::{Color32, FontId, Mesh, Stroke}; -use emath::{pos2, vec2, Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2}; +use emath::{Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2, pos2, vec2}; /// Describes the task of laying out text. /// diff --git a/crates/epaint/src/texture_atlas.rs b/crates/epaint/src/texture_atlas.rs index 790540224..8e174e544 100644 --- a/crates/epaint/src/texture_atlas.rs +++ b/crates/epaint/src/texture_atlas.rs @@ -1,4 +1,4 @@ -use emath::{remap_clamp, Rect}; +use emath::{Rect, remap_clamp}; use crate::{FontImage, ImageDelta}; diff --git a/crates/epaint/src/texture_handle.rs b/crates/epaint/src/texture_handle.rs index 315437750..919b7405f 100644 --- a/crates/epaint/src/texture_handle.rs +++ b/crates/epaint/src/texture_handle.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - emath::NumExt as _, mutex::RwLock, textures::TextureOptions, ImageData, ImageDelta, TextureId, - TextureManager, + ImageData, ImageDelta, TextureId, TextureManager, emath::NumExt as _, mutex::RwLock, + textures::TextureOptions, }; /// Used to paint images. diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 2e5e1c085..d7de7866c 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -3,7 +3,7 @@ name = "confirm_exit" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index fa11a8b5e..1d78565ec 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -3,7 +3,7 @@ name = "custom_3d_glow" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index dc42990bb..86ce17bfb 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -3,7 +3,7 @@ name = "custom_font" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index ff9bde8ce..b3cd82d2a 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -3,7 +3,7 @@ name = "custom_font_style" version = "0.1.0" authors = ["tami5 "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index 1b4c5ca4e..de66772da 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -3,7 +3,7 @@ name = "custom_keypad" version = "0.1.0" authors = ["Varphone Wong "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/custom_keypad/src/keypad.rs b/examples/custom_keypad/src/keypad.rs index 1934ff602..ddc675461 100644 --- a/examples/custom_keypad/src/keypad.rs +++ b/examples/custom_keypad/src/keypad.rs @@ -1,4 +1,4 @@ -use eframe::egui::{self, pos2, vec2, Button, Ui, Vec2}; +use eframe::egui::{self, Button, Ui, Vec2, pos2, vec2}; #[derive(Clone, Copy, Debug, Default, PartialEq)] enum Transition { diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index 596306782..baae7bfc1 100644 --- a/examples/custom_style/Cargo.toml +++ b/examples/custom_style/Cargo.toml @@ -2,7 +2,7 @@ name = "custom_style" version = "0.1.0" license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/custom_style/src/main.rs b/examples/custom_style/src/main.rs index 1e78bea0f..58f7e1d3a 100644 --- a/examples/custom_style/src/main.rs +++ b/examples/custom_style/src/main.rs @@ -2,7 +2,7 @@ #![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui::{ - self, global_theme_preference_buttons, style::Selection, Color32, Stroke, Style, Theme, + self, Color32, Stroke, Style, Theme, global_theme_preference_buttons, style::Selection, }; use egui_demo_lib::{View as _, WidgetGallery}; diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index 21c8e8ef0..74f07237e 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -3,7 +3,7 @@ name = "custom_window_frame" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index bf347848f..bc7ddcb82 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -75,7 +75,7 @@ fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOn } fn title_bar_ui(ui: &mut egui::Ui, title_bar_rect: eframe::epaint::Rect, title: &str) { - use egui::{vec2, Align2, FontId, Id, PointerButton, Sense, UiBuilder}; + use egui::{Align2, FontId, Id, PointerButton, Sense, UiBuilder, vec2}; let painter = ui.painter(); diff --git a/examples/external_eventloop/Cargo.toml b/examples/external_eventloop/Cargo.toml index 884791f3a..e14852930 100644 --- a/examples/external_eventloop/Cargo.toml +++ b/examples/external_eventloop/Cargo.toml @@ -3,7 +3,7 @@ name = "external_eventloop" version = "0.1.0" authors = ["Will Brown "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/external_eventloop/src/main.rs b/examples/external_eventloop/src/main.rs index 178c2865f..d72f6914a 100644 --- a/examples/external_eventloop/src/main.rs +++ b/examples/external_eventloop/src/main.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![allow(rustdoc::missing_crate_level_docs)] // it's an example -use eframe::{egui, UserEvent}; +use eframe::{UserEvent, egui}; use std::{cell::Cell, rc::Rc}; use winit::event_loop::{ControlFlow, EventLoop}; diff --git a/examples/external_eventloop_async/Cargo.toml b/examples/external_eventloop_async/Cargo.toml index e7a0913bc..5305bdbb3 100644 --- a/examples/external_eventloop_async/Cargo.toml +++ b/examples/external_eventloop_async/Cargo.toml @@ -3,7 +3,7 @@ name = "external_eventloop_async" version = "0.1.0" authors = ["Will Brown "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/external_eventloop_async/src/app.rs b/examples/external_eventloop_async/src/app.rs index de3326b19..a7b3e0efe 100644 --- a/examples/external_eventloop_async/src/app.rs +++ b/examples/external_eventloop_async/src/app.rs @@ -1,4 +1,4 @@ -use eframe::{egui, EframePumpStatus, UserEvent}; +use eframe::{EframePumpStatus, UserEvent, egui}; use std::{cell::Cell, io, os::fd::AsRawFd as _, rc::Rc, time::Duration}; use tokio::task::LocalSet; use winit::event_loop::{ControlFlow, EventLoop}; diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 290ea3c66..bf437a396 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -3,7 +3,7 @@ name = "file_dialog" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index b8b763622..6e077c74f 100644 --- a/examples/hello_android/Cargo.toml +++ b/examples/hello_android/Cargo.toml @@ -3,7 +3,7 @@ name = "hello_android" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/hello_android/src/lib.rs b/examples/hello_android/src/lib.rs index c91c0e754..c138b97e1 100644 --- a/examples/hello_android/src/lib.rs +++ b/examples/hello_android/src/lib.rs @@ -1,6 +1,6 @@ #![doc = include_str!("../README.md")] -use eframe::{egui, CreationContext}; +use eframe::{CreationContext, egui}; #[cfg(target_os = "android")] #[no_mangle] diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 37803d3aa..4a5779b8c 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -3,7 +3,7 @@ name = "hello_world" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 38c0d2b53..ee3cb7c84 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -3,7 +3,7 @@ name = "hello_world_par" version = "0.1.0" authors = ["Maxim Osipenko "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 1c4e30b98..d5ce91625 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -3,7 +3,7 @@ name = "hello_world_simple" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index c3317b38b..01b63625c 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -3,7 +3,7 @@ name = "images" version = "0.1.0" authors = ["Jan Procházka "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 9ca900a28..2ae7b0731 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -3,7 +3,7 @@ name = "keyboard_events" version = "0.1.0" authors = ["Jose Palazon "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 84aca3c85..a672ea0db 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -3,7 +3,7 @@ name = "multiple_viewports" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/multiple_viewports/src/main.rs b/examples/multiple_viewports/src/main.rs index a8adab2c7..919c24a3a 100644 --- a/examples/multiple_viewports/src/main.rs +++ b/examples/multiple_viewports/src/main.rs @@ -2,8 +2,8 @@ #![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::{ - atomic::{AtomicBool, Ordering}, Arc, + atomic::{AtomicBool, Ordering}, }; use eframe::egui; diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 041b13de1..536f6ae72 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -3,7 +3,7 @@ name = "puffin_profiler" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index b75a20e00..1386e4884 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -2,15 +2,21 @@ #![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::{ - atomic::{AtomicBool, Ordering}, Arc, + atomic::{AtomicBool, Ordering}, }; use eframe::egui; fn main() -> eframe::Result { let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned()); - std::env::set_var("RUST_LOG", rust_log); + + // SAFETY: we call this from the main thread without any other threads running. + #[expect(unsafe_code)] + unsafe { + std::env::set_var("RUST_LOG", rust_log); + }; + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). start_puffin_server(); // NOTE: you may only want to call this if the users specifies some flag or clicks a button! diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 2e775eb7d..d633159cc 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -6,7 +6,7 @@ authors = [ "Andreas Faber "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 7d5ce61a9..24d362e8c 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -3,7 +3,7 @@ name = "user_attention" version = "0.1.0" authors = ["TicClick "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 3d171c279..6851b736e 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![allow(rustdoc::missing_crate_level_docs)] // it's an example -use eframe::{egui, CreationContext, NativeOptions}; +use eframe::{CreationContext, NativeOptions, egui}; use egui::{Button, CentralPanel, Context, UserAttentionType}; use std::time::{Duration, SystemTime}; diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs index d67759427..9a33c9359 100644 --- a/tests/egui_tests/tests/regression_tests.rs +++ b/tests/egui_tests/tests/regression_tests.rs @@ -1,6 +1,6 @@ -use egui::{include_image, Image}; -use egui_kittest::kittest::Queryable as _; +use egui::{Image, include_image}; use egui_kittest::Harness; +use egui_kittest::kittest::Queryable as _; #[test] fn image_button_should_have_alt_text() { diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs index e7082d472..4ec317521 100644 --- a/tests/egui_tests/tests/test_widgets.rs +++ b/tests/egui_tests/tests/test_widgets.rs @@ -1,10 +1,10 @@ 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 _, + 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 _, include_image, }; -use egui_kittest::kittest::{by, Queryable as _}; +use egui_kittest::kittest::{Queryable as _, by}; use egui_kittest::{Harness, Node, SnapshotResult, SnapshotResults}; #[test] diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index 102130cf8..6058a1865 100644 --- a/tests/test_egui_extras_compilation/Cargo.toml +++ b/tests/test_egui_extras_compilation/Cargo.toml @@ -2,7 +2,7 @@ name = "test_egui_extras_compilation" version = "0.1.0" license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index d26cb864c..5171700ea 100644 --- a/tests/test_inline_glow_paint/Cargo.toml +++ b/tests/test_inline_glow_paint/Cargo.toml @@ -3,7 +3,7 @@ name = "test_inline_glow_paint" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml index 760571a2d..6dbdfe1c0 100644 --- a/tests/test_size_pass/Cargo.toml +++ b/tests/test_size_pass/Cargo.toml @@ -3,7 +3,7 @@ name = "test_size_pass" version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/tests/test_ui_stack/Cargo.toml b/tests/test_ui_stack/Cargo.toml index 43fd45420..a826324a0 100644 --- a/tests/test_ui_stack/Cargo.toml +++ b/tests/test_ui_stack/Cargo.toml @@ -3,7 +3,7 @@ name = "test_ui_stack" version = "0.1.0" authors = ["Antoine Beyeler "] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/tests/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml index 33b7829b3..9a64b485d 100644 --- a/tests/test_viewports/Cargo.toml +++ b/tests/test_viewports/Cargo.toml @@ -3,7 +3,7 @@ name = "test_viewports" version = "0.1.0" authors = ["konkitoman"] license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" rust-version = "1.85" publish = false diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index 3f9964c94..9b7876323 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -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; From 9e021f78da204159f89d0eb03958d24355fb9410 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 1 Jul 2025 14:05:53 +0200 Subject: [PATCH 17/56] Change `ui.disable()` to modify opacity (#7282) * Closes https://github.com/emilk/egui/pull/6765 Branched off of https://github.com/emilk/egui/pull/6765 by @tye-exe. I needed to branch off to update snapshot images --------- Co-authored-by: tye-exe Co-authored-by: Tye <131195812+tye-exe@users.noreply.github.com> --- crates/egui/src/painter.rs | 1 + crates/egui/src/style.rs | 28 +++++++++++++++++-- crates/egui/src/ui.rs | 6 ++-- .../tests/snapshots/easymarkeditor.png | 4 +-- .../tests/snapshots/demos/Frame.png | 4 +-- .../tests/snapshots/demos/Grid Test.png | 4 +-- .../snapshots/demos/Manual Layout Test.png | 4 +-- .../tests/snapshots/demos/Sliders.png | 4 +-- .../snapshots/demos/Tessellation Test.png | 4 +-- .../tests/snapshots/demos/Undo Redo.png | 4 +-- .../tests/snapshots/demos/Window Options.png | 4 +-- .../snapshots/tessellation_test/Normal.png | 4 +-- .../tests/snapshots/visuals/button.png | 4 +-- .../tests/snapshots/visuals/button_image.png | 4 +-- .../visuals/button_image_shortcut.png | 4 +-- .../button_image_shortcut_selected.png | 4 +-- .../snapshots/visuals/checkbox_checked.png | 4 +-- .../tests/snapshots/visuals/drag_value.png | 4 +-- .../tests/snapshots/visuals/radio_checked.png | 4 +-- .../visuals/selectable_value_selected.png | 4 +-- .../tests/snapshots/visuals/slider.png | 4 +-- .../tests/snapshots/visuals/text_edit.png | 4 +-- 22 files changed, 67 insertions(+), 44 deletions(-) diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 373142f61..fe273970e 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -83,6 +83,7 @@ impl Painter { } /// If set, colors will be modified to look like this + #[deprecated = "Use `multiply_opacity` instead"] pub fn set_fade_to_color(&mut self, fade_to_color: Option) { self.fade_to_color = fade_to_color; } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 354269483..234d2190a 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1019,6 +1019,9 @@ pub struct Visuals { /// How to display numeric color values. pub numeric_color_space: NumericColorSpace, + + /// How much to modify the alpha of a disabled widget. + pub disabled_alpha: f32, } impl Visuals { @@ -1054,17 +1057,32 @@ impl Visuals { } /// When fading out things, we fade the colors towards this. - // TODO(emilk): replace with an alpha #[inline(always)] + #[deprecated = "Use disabled_alpha(). Fading is now handled by modifying the alpha channel."] pub fn fade_out_to_color(&self) -> Color32 { self.widgets.noninteractive.weak_bg_fill } - /// Returned a "grayed out" version of the given color. + /// Disabled widgets have their alpha modified by this. + #[inline(always)] + pub fn disabled_alpha(&self) -> f32 { + self.disabled_alpha + } + + /// Returns a "disabled" version of the given color. + /// + /// This function modifies the opcacity of the given color. + /// If this is undesirable use [`gray_out`](Self::gray_out). + #[inline(always)] + pub fn disable(&self, color: Color32) -> Color32 { + color.gamma_multiply(self.disabled_alpha()) + } + + /// Returns a "grayed out" version of the given color. #[doc(alias = "grey_out")] #[inline(always)] pub fn gray_out(&self, color: Color32) -> Color32 { - crate::ecolor::tint_color_towards(color, self.fade_out_to_color()) + crate::ecolor::tint_color_towards(color, self.widgets.noninteractive.weak_bg_fill) } } @@ -1384,6 +1402,7 @@ impl Visuals { image_loading_spinners: true, numeric_color_space: NumericColorSpace::GammaByte, + disabled_alpha: 0.5, } } @@ -2063,6 +2082,7 @@ impl Visuals { image_loading_spinners, numeric_color_space, + disabled_alpha, } = self; ui.collapsing("Background Colors", |ui| { @@ -2191,6 +2211,8 @@ impl Visuals { ui.label("Color picker type"); numeric_color_space.toggle_button_ui(ui); }); + + ui.add(Slider::new(disabled_alpha, 0.0..=1.0).text("Disabled element alpha")); }); ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals")); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 0abd9c054..3738e6e53 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -522,7 +522,7 @@ impl Ui { self.enabled = false; if self.is_visible() { self.painter - .set_fade_to_color(Some(self.visuals().fade_out_to_color())); + .multiply_opacity(self.visuals().disabled_alpha()); } } @@ -2963,8 +2963,8 @@ impl Ui { if is_anything_being_dragged && !can_accept_what_is_being_dragged { // When dragging something else, show that it can't be dropped here: - fill = self.visuals().gray_out(fill); - stroke.color = self.visuals().gray_out(stroke.color); + fill = self.visuals().disable(fill); + stroke.color = self.visuals().disable(stroke.color); } frame.frame.fill = fill; diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index 08f5fc98f..34cea1ecc 100644 --- a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png +++ b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8cf6d0b20f127f22d49daefed27fc2d0ca43d645fe1486cf7f6fcbb676bdec82 -size 179065 +oid sha256:2849afd01ec3dae797b15893e28908f6b037588b3712fb6dec556edb7b230b5d +size 179082 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index 64c6b76ec..41d3995db 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0b999914adab3d44c614bdf3b28abd268a4ff6162c5680b43035b3f71cb69bb -size 23999 +oid sha256:6d5f3129e34e22b15245212904e0a3537a0c7e70f1d35fd3e9c784af707038b5 +size 24018 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png index 394bea644..7dbb397fa 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c975c8b646425878b704f32198286010730746caf5d463ca8cbcfe539922816 -size 99087 +oid sha256:5d05c74583024825d82f1fe8dbeb2a793e366016e87a639f51d46945831de82a +size 99106 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png index 857cd2d6c..0e2bdbf80 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3110fab8444cb41dffe8b27277fa5dafd0d335aaf13dca511bcccc8b53fb25c8 -size 24046 +oid sha256:17f7065c47712f140e4a9fd9eed61a7118fe12cd79cf0745642a02921eaa596b +size 24065 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 92e94b78f..c26e7e4f6 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0e3eeca8abb4fba632cef4621d478fb66af1a0f13e099dda9a79420cc2b6301 -size 115320 +oid sha256:7e80bf8c79e6e431806c85385a0bd9262796efc0a1e74d431a1b896dde0b8651 +size 115338 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png index 723bb5995..6f3ca31d5 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f90d56d40004f61628e3f66cfac817c426cd18eb4b9c69ea1b3a6fe5e75e3f05 -size 70354 +oid sha256:16dc96246f011c6e9304409af7b4084f28e20cd813e44abca73834386e98b9b1 +size 70373 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png index 53d6c8a3d..b3dbb2ea1 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4d6a15094eee5d96a8af5c44ea9d0c962d650ee9b867344c86d1229e526dcb5 -size 12822 +oid sha256:26e4828e42f54da24d032f384f8829e42bcebaee072923f277db582f84302911 +size 12847 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png index a45e2be68..217419e00 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02abc0cbab97e572218f422f4b167957869d4e2b4b388355444c20148d998015 -size 35200 +oid sha256:4a4520aa68d6752992fd2f87090a317e6e5e24b5cdb5ee2e82daf07f9471ca80 +size 35251 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png index 677783cc5..9d19dbc9b 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b567d4038fd73986c80d2bd12197a6df037fde043545993fa9fe4160d0af446c -size 54829 +oid sha256:c33617dfde24071fa65aff2543f22883f5526152fb344997b1877aeb38df72fe +size 54848 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png index 8c8e9630c..364f7771f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button.png +++ b/tests/egui_tests/tests/snapshots/visuals/button.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99f64e581b97df6694cb7c85ee7728a955e3c1a851ab660e8b6091eee1885bbe -size 9719 +oid sha256:a573976aacbb629c88c285089fca28ba7998a0c28ecee9f783920d67929a1e2d +size 9735 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png index c71c2aeb0..c38571a6e 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d39ec25b91f5f5d68305d2cb7cc0285d715fe30ccbd66369efbe7327d1899b52 -size 10753 +oid sha256:9fbb9aca2006aeca555c138f1ebdb89409026f1bed48da74cd0fa03dcd8facbe +size 10746 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png index 42f8ff02a..7cb8c01f7 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46d86987ba895ead9b28efcc37e1b4374f34eedebac83d1db9eaa8e5a3202ee3 -size 13203 +oid sha256:f74f5ff20b842c1990c50d8a66ab5b34e248786f01b1592485620d31426ce5ae +size 13302 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png index 114baa35d..9115d6919 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9151f1c9d8a769ac2143a684cabf5d9ed1e453141fff555da245092003f1df1 -size 13563 +oid sha256:df84f3fce07a45a208f6169f0df701b7971fc7d467151870d56d90ce49a2c819 +size 13522 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index 40852f3c2..113839a2f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e03cf99a3d28f73d4a72c0e616dc54198663b94bf5cffda694cf4eb4dee01be8 -size 13445 +oid sha256:ec75c3fccec8d6a72b808aba593f8c289618b6f95db08eb3cdb20a255b9d986e +size 13450 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png index dbe3c13b6..56b5bb0e3 100644 --- a/tests/egui_tests/tests/snapshots/visuals/drag_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/drag_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e86a37c7b259a6bad61897545d927d75e8307916dc78d256e4d33c410fcd6876 -size 7306 +oid sha256:c7e66a490236b306ce03c504d29490cdadc3708a79e21e3b46d11df8eb22a26b +size 7309 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index a42ad5012..8e89197e1 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1a172cfadc91467529e5546e686673be73ba0071a55d55abc7a41fb1d07214d -size 11700 +oid sha256:895914fa37608ff68c5ae7fdd22d0363da26907c78d4980f6bf1ed19f7e5f388 +size 11697 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png index 81f995515..67d80fed3 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ef91dfedc74cae59099bce32b2e42cb04649e84442e8010282a9c1ff2a7f2c8 -size 12469 +oid sha256:0e0c4277eebadb0c350b5110d5ea7ff9292ab2b0231d6b36e9ada3aeefc7c198 +size 12510 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index 6c8348559..7e868c0e7 100644 --- a/tests/egui_tests/tests/snapshots/visuals/slider.png +++ b/tests/egui_tests/tests/snapshots/visuals/slider.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1892358a4552af3f529141d314cd18e4cf55a629d870798278a5470e3e0a8a94 -size 11030 +oid sha256:ec09e0e3432668c0d08bbba0aa8608c4eefba33d57f2335fdf105d144791406d +size 11036 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png index 5f2a64b8d..1e1e4d394 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7300a0b88d4fdb6c1e543bfaf50e8964b2f84aaaf8197267b671d0cf3c8da30a -size 7033 +oid sha256:9353e6d39d309e7a6e6c0a17be819809c2dbea8979e9e73b3c73b67b07124a36 +size 7031 From 737c61867bec6279717958c97647c967ebf2d2b6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 1 Jul 2025 15:13:16 +0200 Subject: [PATCH 18/56] Add `Visuals::text_edit_bg_color` (#7283) * Closes https://github.com/emilk/egui/issues/7263 --- crates/egui/src/style.rs | 42 +++++++++++++++++--- crates/egui/src/widgets/text_edit/builder.rs | 6 +-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 234d2190a..be1f6e739 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -11,6 +11,7 @@ use crate::{ WidgetText, ecolor::Color32, emath::{Rangef, Rect, Vec2, pos2, vec2}, + reset_button_with, }; /// How to format numbers in e.g. a [`crate::DragValue`]. @@ -952,6 +953,11 @@ pub struct Visuals { /// that needs to look different from other interactive stuff. pub extreme_bg_color: Color32, + /// The background color of [`crate::TextEdit`]. + /// + /// Defaults to [`Self::extreme_bg_color`]. + pub text_edit_bg_color: Option, + /// Background color behind code-styled monospaced labels. pub code_bg_color: Color32, @@ -1045,6 +1051,11 @@ impl Visuals { self.widgets.active.text_color() } + /// The background color of [`crate::TextEdit`]. + pub fn text_edit_bg_color(&self) -> Color32 { + self.text_edit_bg_color.unwrap_or(self.extreme_bg_color) + } + /// Window background color. #[inline(always)] pub fn window_fill(&self) -> Color32 { @@ -1357,6 +1368,7 @@ impl Visuals { hyperlink_color: Color32::from_rgb(90, 170, 255), faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background + text_edit_bg_color: None, // use `extreme_bg_color` by default code_bg_color: Color32::from_gray(64), warn_fg_color: Color32::from_rgb(255, 143, 0), // orange error_fg_color: Color32::from_rgb(255, 0, 0), // red @@ -1699,11 +1711,11 @@ impl Style { ui.end_row(); }); - ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles)); + ui.collapsing("🔠 Text styles", |ui| text_styles_ui(ui, text_styles)); ui.collapsing("📏 Spacing", |ui| spacing.ui(ui)); ui.collapsing("☝ Interaction", |ui| interaction.ui(ui)); ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui)); - ui.collapsing("🔄 Scroll Animation", |ui| scroll_animation.ui(ui)); + ui.collapsing("🔄 Scroll animation", |ui| scroll_animation.ui(ui)); #[cfg(debug_assertions)] ui.collapsing("🐛 Debug", |ui| debug.ui(ui)); @@ -2041,13 +2053,14 @@ impl WidgetVisuals { impl Visuals { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { - dark_mode: _, + dark_mode, override_text_color: _, widgets, selection, hyperlink_color, faint_bg_color, extreme_bg_color, + text_edit_bg_color, code_bg_color, warn_fg_color, error_fg_color, @@ -2085,7 +2098,7 @@ impl Visuals { disabled_alpha, } = self; - ui.collapsing("Background Colors", |ui| { + ui.collapsing("Background colors", |ui| { ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons"); ui_color(ui, window_fill, "Windows"); ui_color(ui, panel_fill, "Panels"); @@ -2094,6 +2107,13 @@ impl Visuals { ); ui_color(ui, extreme_bg_color, "Extreme") .on_hover_text("Background of plots and paintings"); + + ui_color( + ui, + text_edit_bg_color.get_or_insert(*extreme_bg_color), + "TextEdit", + ) + .on_hover_text("Background of TextEdit"); }); ui.collapsing("Text color", |ui| { @@ -2215,7 +2235,19 @@ impl Visuals { ui.add(Slider::new(disabled_alpha, 0.0..=1.0).text("Disabled element alpha")); }); - ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals")); + let dark_mode = *dark_mode; + ui.vertical_centered(|ui| { + reset_button_with( + ui, + self, + "Reset visuals", + if dark_mode { + Self::dark() + } else { + Self::light() + }, + ); + }); } } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index d5a006564..bcd65c7bf 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -63,7 +63,7 @@ type LayouterFn<'t> = &'t mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc { text: &'t mut dyn TextBuffer, @@ -207,7 +207,7 @@ impl<'t> TextEdit<'t> { self } - /// Set the background color of the [`TextEdit`]. The default is [`crate::Visuals::extreme_bg_color`]. + /// Set the background color of the [`TextEdit`]. The default is [`crate::Visuals::text_edit_bg_color`]. // TODO(bircni): remove this once #3284 is implemented #[inline] pub fn background_color(mut self, color: Color32) -> Self { @@ -428,7 +428,7 @@ impl TextEdit<'_> { let where_to_put_background = ui.painter().add(Shape::Noop); let background_color = self .background_color - .unwrap_or(ui.visuals().extreme_bg_color); + .unwrap_or_else(|| ui.visuals().text_edit_bg_color()); let output = self.show_content(ui); if frame { From 9d1dce51eb24bb15a57af519ef1dfefc64722935 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 1 Jul 2025 15:54:00 +0200 Subject: [PATCH 19/56] Extend .typos.toml to enforce american english (#7284) More or less the same list we use at Rerun --- .typos.toml | 128 ++++++++++++++++++ CONTRIBUTING.md | 2 +- crates/eframe/src/native/file_storage.rs | 2 +- crates/egui/src/viewport.rs | 2 +- crates/egui_extras/src/loaders/file_loader.rs | 4 +- scripts/update_snapshots_from_ci.sh | 2 +- 6 files changed, 134 insertions(+), 6 deletions(-) diff --git a/.typos.toml b/.typos.toml index b9d882beb..d4c716172 100644 --- a/.typos.toml +++ b/.typos.toml @@ -18,5 +18,133 @@ teselation = "tessellation" tessalation = "tessellation" tesselation = "tessellation" + +# Use the more common spelling +adaptor = "adapter" +adaptors = "adapters" + +# For consistency we prefer American English: +aeroplane = "airplane" +analogue = "analog" +analyse = "analyze" +appetiser = "appetizer" +arbour = "arbor" +ardour = "arbor" +armour = "armor" +artefact = "artifact" +authorise = "authorize" +behaviour = "behavior" +behavioural = "behavioral" +British = "American" +calibre = "caliber" +# cancelled = "canceled" # winit uses this :( +candour = "candor" +capitalise = "capitalize" +catalogue = "catalog" +centre = "center" +characterise = "characterize" +chequerboard = "checkerboard" +chequered = "checkered" +civilise = "civilize" +clamour = "clamor" +colonise = "colonize" +colour = "color" +coloured = "colored" +cosy = "cozy" +criticise = "criticize" +defence = "defense" +demeanour = "demeanor" +dialogue = "dialog" +distil = "distill" +doughnut = "donut" +dramatise = "dramatize" +draught = "draft" +emphasise = "emphasize" +endeavour = "endeavor" +enrol = "enroll" +epilogue = "epilog" +equalise = "equalize" +favour = "favor" +favourite = "favorite" +fibre = "fiber" +flavour = "flavor" +fulfil = "fufill" +gaol = "jail" +grey = "gray" +greys = "grays" +greyscale = "grayscale" +harbour = "habor" +honour = "honor" +humour = "humor" +instalment = "installment" +instil = "instill" +jewellery = "jewelry" +kerb = "curb" +labour = "labor" +litre = "liter" +lustre = "luster" +meagre = "meager" +metre = "meter" +mobilise = "mobilize" +monologue = "monolog" +naturalise = "naturalize" +neighbour = "neighbor" +neighbourhood = "neighborhood" +normalise = "normalize" +normalised = "normalized" +odour = "odor" +offence = "offense" +organise = "organize" +parlour = "parlor" +plough = "plow" +popularise = "popularize" +pretence = "pretense" +programme = "program" +prologue = "prolog" +rancour = "rancor" +realise = "realize" +recognise = "recognize" +recognised = "recognized" +rigour = "rigor" +rumour = "rumor" +sabre = "saber" +satirise = "satirize" +saviour = "savior" +savour = "savor" +sceptical = "skeptical" +sceptre = "scepter" +sepulchre = "sepulcher" +serialisation = "serialization" +serialise = "serialize" +serialised = "serialized" +skilful = "skillful" +sombre = "somber" +specialisation = "specialization" +specialise = "specialize" +specialised = "specialized" +splendour = "splendor" +standardise = "standardize" +sulphur = "sulfur" +symbolise = "symbolize" +theatre = "theater" +tonne = "ton" +travelogue = "travelog" +tumour = "tumor" +valour = "valor" +vaporise = "vaporize" +vigour = "vigor" + +# null-terminated is the name of the wikipedia article! +# https://en.wikipedia.org/wiki/Null-terminated_string +nullterminated = "null-terminated" +zeroterminated = "null-terminated" +zero-terminated = "null-terminated" + + [files] extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated + +[default] +extend-ignore-re = [ + "#\\[doc\\(alias = .*", # We suggest "grey" in some doc +] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ddedc378..110f92ddd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,7 +36,7 @@ There are snapshots test that might need to be updated. Run the tests with `UPDATE_SNAPSHOTS=true cargo test --workspace --all-features` to update all of them. If CI keeps complaining about snapshots (which could happen if you don't use macOS, snapshots in CI are currently rendered with macOS), you can instead run `./scripts/update_snapshots_from_ci.sh` to update your local snapshots from -the last CI run of your PR (which will download the `test_results` artefact). +the last CI run of your PR (which will download the `test_results` artifact). For more info about the tests see [egui_kittest](./crates/egui_kittest/README.md). Snapshots and other big files are stored with git lfs. See [Working with git lfs](#working-with-git-lfs) for more info. If you see an `InvalidSignature` error when running snapshot tests, it's probably a problem related to git-lfs. diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 82b666c29..fd502f1a8 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -72,7 +72,7 @@ fn roaming_appdata() -> Option { }; let path = if result == S_OK { - // SAFETY: SHGetKnownFolderPath indicated success and is supposed to allocate a nullterminated string for us. + // SAFETY: SHGetKnownFolderPath indicated success and is supposed to allocate a null-terminated string for us. let path_slice = unsafe { slice::from_raw_parts(path_raw, wcslen(path_raw)) }; Some(PathBuf::from(OsString::from_wide(path_slice))) } else { diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 9f5bb1e31..bf09fc845 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -438,7 +438,7 @@ impl ViewportBuilder { } /// macOS: Set to `true` to allow the window to be moved by dragging the background. - /// Enabling this feature can result in unexpected behaviour with draggable UI widgets such as sliders. + /// Enabling this feature can result in unexpected behavior with draggable UI widgets such as sliders. #[inline] pub fn with_movable_by_background(mut self, value: bool) -> Self { self.movable_by_window_background = Some(value); diff --git a/crates/egui_extras/src/loaders/file_loader.rs b/crates/egui_extras/src/loaders/file_loader.rs index d13134e21..001e988c2 100644 --- a/crates/egui_extras/src/loaders/file_loader.rs +++ b/crates/egui_extras/src/loaders/file_loader.rs @@ -100,9 +100,9 @@ impl BytesLoader for FileLoader { let entry = entry.get_mut(); *entry = Poll::Ready(result); ctx.request_repaint(); - log::trace!("finished loading {uri:?}"); + log::trace!("Finished loading {uri:?}"); } else { - log::trace!("cancelled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); + log::trace!("Canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); } } }) diff --git a/scripts/update_snapshots_from_ci.sh b/scripts/update_snapshots_from_ci.sh index c42ffb0fd..c15360365 100755 --- a/scripts/update_snapshots_from_ci.sh +++ b/scripts/update_snapshots_from_ci.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# This script searches for the last CI run with your branch name, downloads the test_results artefact +# This script searches for the last CI run with your branch name, downloads the test_results artifact # and replaces your existing snapshots with the new ones. # Make sure you have the gh cli installed and authenticated before running this script. # If prompted to select a default repo, choose the emilk/egui one From 0857527f1dc55492f02714751df1756467da56fa Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 1 Jul 2025 20:42:54 +0200 Subject: [PATCH 20/56] Add `Visuals::weak_text_alpha` and `weak_text_color` (#7285) * Closes https://github.com/emilk/egui/issues/7262 This also makes the default weak color slightly less weak in most cases. --- crates/egui/src/style.rs | 163 ++++++++++++------ .../tests/snapshots/easymarkeditor.png | 4 +- .../tests/snapshots/demos/Input Test.png | 4 +- .../tests/snapshots/demos/Panels.png | 4 +- .../tests/snapshots/demos/Scene.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 4 +- .../tests/snapshots/widget_gallery.png | 4 +- .../layout/button_image_shortcut.png | 4 +- .../visuals/button_image_shortcut.png | 4 +- .../button_image_shortcut_selected.png | 4 +- 10 files changed, 129 insertions(+), 70 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index be1f6e739..205aa5786 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -936,6 +936,17 @@ pub struct Visuals { /// it is disabled, non-interactive, hovered etc. pub override_text_color: Option, + /// How strong "weak" text is. + /// + /// Ignored if [`Self::weak_text_color`] is set. + pub weak_text_alpha: f32, + + /// Color of "weak" text. + /// + /// If `None`, the color is [`Self::text_color`] + /// multiplied by [`Self::weak_text_alpha`]. + pub weak_text_color: Option, + /// Visual styles of widgets pub widgets: Widgets, @@ -1043,7 +1054,8 @@ impl Visuals { } pub fn weak_text_color(&self) -> Color32 { - self.gray_out(self.text_color()) + self.weak_text_color + .unwrap_or_else(|| self.text_color().gamma_multiply(self.weak_text_alpha)) } #[inline(always)] @@ -1363,6 +1375,8 @@ impl Visuals { Self { dark_mode: true, override_text_color: None, + weak_text_alpha: 0.6, + weak_text_color: None, widgets: Widgets::default(), selection: Selection::default(), hyperlink_color: Color32::from_rgb(90, 170, 255), @@ -2055,6 +2069,8 @@ impl Visuals { let Self { dark_mode, override_text_color: _, + weak_text_alpha, + weak_text_color, widgets, selection, hyperlink_color, @@ -2098,49 +2114,108 @@ impl Visuals { disabled_alpha, } = self; - ui.collapsing("Background colors", |ui| { - ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons"); - ui_color(ui, window_fill, "Windows"); - ui_color(ui, panel_fill, "Panels"); - ui_color(ui, faint_bg_color, "Faint accent").on_hover_text( - "Used for faint accentuation of interactive things, like striped grids.", - ); - ui_color(ui, extreme_bg_color, "Extreme") - .on_hover_text("Background of plots and paintings"); + fn ui_optional_color( + ui: &mut Ui, + color: &mut Option, + default_value: Color32, + label: impl Into, + ) -> Response { + let label_response = ui.label(label); - ui_color( - ui, - text_edit_bg_color.get_or_insert(*extreme_bg_color), - "TextEdit", - ) - .on_hover_text("Background of TextEdit"); + ui.horizontal(|ui| { + let mut set = color.is_some(); + ui.checkbox(&mut set, ""); + if set { + let color = color.get_or_insert(default_value); + ui.color_edit_button_srgba(color); + } else { + *color = None; + }; + }); + + ui.end_row(); + + label_response + } + + ui.collapsing("Background colors", |ui| { + Grid::new("background_colors") + .num_columns(2) + .show(ui, |ui| { + fn ui_color( + ui: &mut Ui, + color: &mut Color32, + label: impl Into, + ) -> Response { + let label_response = ui.label(label); + ui.color_edit_button_srgba(color); + ui.end_row(); + label_response + } + + ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons"); + ui_color(ui, window_fill, "Windows"); + ui_color(ui, panel_fill, "Panels"); + ui_color(ui, faint_bg_color, "Faint accent").on_hover_text( + "Used for faint accentuation of interactive things, like striped grids.", + ); + ui_color(ui, extreme_bg_color, "Extreme") + .on_hover_text("Background of plots and paintings"); + + ui_optional_color(ui, text_edit_bg_color, *extreme_bg_color, "TextEdit") + .on_hover_text("Background of TextEdit"); + }); }); ui.collapsing("Text color", |ui| { - ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label"); - ui_text_color( - ui, - &mut widgets.inactive.fg_stroke.color, - "Unhovered button", - ); - ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button"); - ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button"); + fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into) { + ui.label(label.into().color(*color)); + ui.color_edit_button_srgba(color); + ui.end_row(); + } - ui_text_color(ui, warn_fg_color, RichText::new("Warnings")); - ui_text_color(ui, error_fg_color, RichText::new("Errors")); + Grid::new("text_color").num_columns(2).show(ui, |ui| { + ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label"); - ui_text_color(ui, hyperlink_color, "hyperlink_color"); + ui_text_color( + ui, + &mut widgets.inactive.fg_stroke.color, + "Unhovered button", + ); + ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button"); + ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button"); - ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui( - |ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("For monospaced inlined text "); - ui.code("like this"); - ui.label("."); + ui_text_color(ui, warn_fg_color, RichText::new("Warnings")); + ui_text_color(ui, error_fg_color, RichText::new("Errors")); + + ui_text_color(ui, hyperlink_color, "hyperlink_color"); + + ui.label(RichText::new("Code background").code()) + .on_hover_ui(|ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("For monospaced inlined text "); + ui.code("like this"); + ui.label("."); + }); }); - }, - ); + ui.color_edit_button_srgba(code_bg_color); + ui.end_row(); + + ui.label("Weak text alpha"); + ui.add_enabled( + weak_text_color.is_none(), + DragValue::new(weak_text_alpha).speed(0.01).range(0.0..=1.0), + ); + ui.end_row(); + + ui_optional_color( + ui, + weak_text_color, + widgets.noninteractive.text_color(), + "Weak text color", + ); + }); }); ui.collapsing("Text cursor", |ui| { @@ -2364,22 +2439,6 @@ fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive) -> im } } -fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into) -> Response { - ui.horizontal(|ui| { - ui.color_edit_button_srgba(color); - ui.label(label); - }) - .response -} - -fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into) -> Response { - ui.horizontal(|ui| { - ui.color_edit_button_srgba(color); - ui.label(label.into().color(*color)); - }) - .response -} - impl HandleShape { pub fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index 34cea1ecc..6a1d0290a 100644 --- a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png +++ b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2849afd01ec3dae797b15893e28908f6b037588b3712fb6dec556edb7b230b5d -size 179082 +oid sha256:f62d5375ff784e333e01a31b84d9caadf2dcbd2b19647a08977dab6550b48828 +size 179654 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png index 91548c427..aca535ad1 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fc9e2ec3253a30ac9649995b019b6b23d745dba07a327886f574a15c0e99e84 -size 50082 +oid sha256:e0a49139611dd5f4e97874e8f7b0e12b649da5f373ff7ee80a7ff678f7f8ecc7 +size 50321 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 9953ac6c1..e87c842a1 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df1e4a1e355100056713e751a8979d4201d0e4aab5513ba2f7a3e4852e1347dd -size 264340 +oid sha256:cfc5dd77728ee0b3d319c5851698305851b6713eb054a6eb5b618e9670f58ae5 +size 277018 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index ea8f9c857..f5bb0ffd1 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdff6256488f3a40c65a3d73c0635377bf661c57927bce4c853b2a5f3b33274e -size 35121 +oid sha256:fdf3535530c1abb1262383ff9a3f2a740ad2c62ccec33ec5fb435be11625d139 +size 35125 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 49b223e7d..f13fc54db 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a347875ef98ebbd606774e03baffdb317cb0246882db116fee1aa7685efbb88 -size 179653 +oid sha256:1bd15215f3ec1b365b8c51987f629d5653e4f40e84c34756aea0dc863af27c1e +size 179906 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png index bcb09fe26..ffb00ce22 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee129f0542f21e12f5aa3c2f9746e7cadd73441a04d580f57c12c1cdd40d8b07 -size 153136 +oid sha256:3f5a7397601cb718d5529842a428d2d328d4fe3d1a9cf1a3ca6d583d8525f75e +size 153190 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png index 7dbda11d9..f15fb0ce6 100644 --- a/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png +++ b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad14068e60fa678ee749925dd3713ee2b12a83ec1bca9c413bdeb9bc27d8ac20 -size 407795 +oid sha256:d59882afca42e766dddc36450a3331ca247a130e3796f99d0335ac370a7c3610 +size 425517 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png index 7cb8c01f7..4be868a30 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f74f5ff20b842c1990c50d8a66ab5b34e248786f01b1592485620d31426ce5ae -size 13302 +oid sha256:8ff776897760d300a4f26c10578be0d9afed7b4ae9f95f941914e641c2a10cb8 +size 13798 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png index 9115d6919..ffabcae40 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df84f3fce07a45a208f6169f0df701b7971fc7d467151870d56d90ce49a2c819 -size 13522 +oid sha256:9cd6a7f38c876cc345eae1a5e01f7668d4642b70181198fe0f09570815e47da8 +size 13489 From 22c6a9ae6991262bfa102b170769e071840f0965 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 2 Jul 2025 12:00:22 +0200 Subject: [PATCH 21/56] `egui_kittest`: Add `HarnessBuilder::theme` (#7289) Makes it ergonomic to snapshot test light vs dark mode --- crates/egui_kittest/src/builder.rs | 9 +++++++++ crates/egui_kittest/src/lib.rs | 2 ++ 2 files changed, 11 insertions(+) diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index 3b10ba37a..021019a89 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -7,6 +7,7 @@ use std::marker::PhantomData; pub struct HarnessBuilder { pub(crate) screen_rect: Rect, pub(crate) pixels_per_point: f32, + pub(crate) theme: egui::Theme, pub(crate) max_steps: u64, pub(crate) step_dt: f32, pub(crate) state: PhantomData, @@ -19,6 +20,7 @@ impl Default for HarnessBuilder { Self { screen_rect: Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)), pixels_per_point: 1.0, + theme: egui::Theme::Dark, state: PhantomData, renderer: Box::new(LazyRenderer::default()), max_steps: 4, @@ -45,6 +47,13 @@ impl HarnessBuilder { self } + /// Set the desired theme (dark or light). + #[inline] + pub fn with_theme(mut self, theme: egui::Theme) -> Self { + self.theme = theme; + self + } + /// Set the maximum number of steps to run when calling [`Harness::run`]. /// /// Default is 4. diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index ac67c8dad..01aef3266 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -86,6 +86,7 @@ impl<'a, State> Harness<'a, State> { let HarnessBuilder { screen_rect, pixels_per_point, + theme, max_steps, step_dt, state: _, @@ -93,6 +94,7 @@ impl<'a, State> Harness<'a, State> { wait_for_pending_images, } = builder; let ctx = ctx.unwrap_or_default(); + ctx.set_theme(theme); ctx.enable_accesskit(); // Disable cursor blinking so it doesn't interfere with snapshots ctx.all_styles_mut(|style| style.visuals.text_cursor.blink = false); From 8bedaf6e5b7d4a75ef1b6af34076164d44e44c00 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 2 Jul 2025 12:00:36 +0200 Subject: [PATCH 22/56] Add light-mode Widget Gallery screenshot test (#7288) Part of some work to improve text rendering in light mode (again!) --- .../egui_demo_lib/src/demo/widget_gallery.rs | 29 +++++++++++++------ .../snapshots/widget_gallery_dark_x1.png | 3 ++ ...gallery.png => widget_gallery_dark_x2.png} | 0 .../snapshots/widget_gallery_light_x1.png | 3 ++ .../snapshots/widget_gallery_light_x2.png | 3 ++ 5 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png rename crates/egui_demo_lib/tests/snapshots/{widget_gallery.png => widget_gallery_dark_x2.png} (100%) create mode 100644 crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png create mode 100644 crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 31f5d279a..214646d49 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -319,16 +319,27 @@ mod tests { date: Some(chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()), ..Default::default() }; - let mut harness = Harness::builder() - .with_pixels_per_point(2.0) - .with_size(Vec2::new(380.0, 550.0)) - .build_ui(|ui| { - egui_extras::install_image_loaders(ui.ctx()); - demo.ui(ui); - }); - harness.fit_contents(); + for pixels_per_point in [1, 2] { + for theme in [egui::Theme::Light, egui::Theme::Dark] { + let mut harness = Harness::builder() + .with_pixels_per_point(pixels_per_point as f32) + .with_theme(theme) + .with_size(Vec2::new(380.0, 550.0)) + .build_ui(|ui| { + egui_extras::install_image_loaders(ui.ctx()); + demo.ui(ui); + }); - harness.snapshot("widget_gallery"); + harness.fit_contents(); + + let theme_name = match theme { + egui::Theme::Light => "light", + egui::Theme::Dark => "dark", + }; + let image_name = format!("widget_gallery_{theme_name}_x{pixels_per_point}"); + harness.snapshot(&image_name); + } + } } } diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png new file mode 100644 index 000000000..d607894d4 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62df72fd7e2404c4aa482f09eff5103ee28e8afc42ee8c8c74307a246f64cda6 +size 64651 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x2.png similarity index 100% rename from crates/egui_demo_lib/tests/snapshots/widget_gallery.png rename to crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x2.png diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png new file mode 100644 index 000000000..02e801272 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2198a523fb986e90fa3a42f047499f5b1c791075e7c3822b45509d9880073966 +size 60272 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png new file mode 100644 index 000000000..40518afe0 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bb371a477f58c90ac72aed45a081f3177ea968f090e3739bdb5044ade29f4be +size 144295 From dc79998044d4401230a59e08effd79e58d809b42 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 2 Jul 2025 14:58:37 +0200 Subject: [PATCH 23/56] Improve text rendering in light mode (#7290) This changes how we convert glyph coverage to alpha (and ultimately a color), but only in light mode. This is a bit of a hack, because it doesn't fix dark-on-light text in _dark mode_ (if you have any), but for the common case this PR is a huge improvement. You can also tweak this yourself now using `Visuals::text_alpha_from_coverage` or from the UI (bottom of the image): ![image](https://github.com/user-attachments/assets/350210d4-c0bb-44b6-84cc-47c2e9d4b9f0) ## Before / After ![widget_gallery_light_x1](https://github.com/user-attachments/assets/21f5a2a0-6b4e-4985-b17f-cd1c7cc01b46) ![widget_gallery_light_x1](https://github.com/user-attachments/assets/5dfec04a-c81c-43ef-8d86-fc48ef7958f1) ## Black text Before/after If you think the text above looks too weak, it's only because of the default text color. Here's how it looks like with perfectly `#000000` black text: ![image](https://github.com/user-attachments/assets/56a4a4f3-c431-4991-b941-a566a4ae94ed) ![Screenshot 2025-07-02 at 13 59 30](https://github.com/user-attachments/assets/df5a91ad-0bb8-4a0f-81a2-50852e7556c1) --- crates/egui-wgpu/src/renderer.rs | 6 +- crates/egui/src/context.rs | 56 +++++++++++- crates/egui/src/memory/mod.rs | 6 +- crates/egui/src/style.rs | 46 +++++++++- .../snapshots/widget_gallery_light_x1.png | 4 +- .../snapshots/widget_gallery_light_x2.png | 4 +- crates/egui_glow/src/painter.rs | 2 +- crates/epaint/src/image.rs | 88 ++++++++++++++----- crates/epaint/src/lib.rs | 2 +- 9 files changed, 177 insertions(+), 37 deletions(-) diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 73df09e43..473c73028 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -571,7 +571,11 @@ impl Renderer { "Mismatch between texture size and texel count" ); profiling::scope!("font -> sRGBA"); - Cow::Owned(image.srgba_pixels(None).collect::>()) + Cow::Owned( + image + .srgba_pixels(Default::default()) + .collect::>(), + ) } }; let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice()); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 54ae49036..80bcf570f 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1876,6 +1876,16 @@ impl Context { } } + pub(crate) fn reset_font_atlas(&self) { + let pixels_per_point = self.pixels_per_point(); + let fonts = self.read(|ctx| { + ctx.fonts + .get(&pixels_per_point.into()) + .map(|current_fonts| current_fonts.lock().fonts.definitions().clone()) + }); + self.memory_mut(|mem| mem.new_font_definitions = fonts); + } + /// Tell `egui` which fonts to use. /// /// The default `egui` fonts only support latin and cyrillic alphabets, @@ -2011,10 +2021,19 @@ impl Context { /// You can use [`Ui::style_mut`] to change the style of a single [`Ui`]. pub fn set_style_of(&self, theme: Theme, style: impl Into>) { let style = style.into(); - self.options_mut(|opt| match theme { - Theme::Dark => opt.dark_style = style, - Theme::Light => opt.light_style = style, + let mut recreate_font_atlas = false; + self.options_mut(|opt| { + let dest = match theme { + Theme::Dark => &mut opt.dark_style, + Theme::Light => &mut opt.light_style, + }; + recreate_font_atlas = + dest.visuals.text_alpha_from_coverage != style.visuals.text_alpha_from_coverage; + *dest = style; }); + if recreate_font_atlas { + self.reset_font_atlas(); + } } /// The [`crate::Visuals`] used by all subsequent windows, panels etc. @@ -2411,7 +2430,28 @@ impl ContextImpl { } // Inform the backend of all textures that have been updated (including font atlas). - let textures_delta = self.tex_manager.0.write().take_delta(); + let textures_delta = { + // HACK to get much nicer looking text in light mode. + // This assumes all text is black-on-white in light mode, + // and white-on-black in dark mode, which is not necessarily true, + // but often close enough. + // Of course this fails for cases when there is black-on-white text in dark mode, + // and white-on-black text in light mode. + + let text_alpha_from_coverage = + self.memory.options.style().visuals.text_alpha_from_coverage; + + let mut textures_delta = self.tex_manager.0.write().take_delta(); + + for (_, delta) in &mut textures_delta.set { + if let ImageData::Font(font) = &mut delta.image { + delta.image = + ImageData::Color(font.to_color_image(text_alpha_from_coverage).into()); + } + } + + textures_delta + }; let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); @@ -3009,9 +3049,17 @@ impl Context { options.ui(ui); + let text_alpha_from_coverage_changed = + prev_options.style().visuals.text_alpha_from_coverage + != options.style().visuals.text_alpha_from_coverage; + if options != prev_options { self.options_mut(move |o| *o = options); } + + if text_alpha_from_coverage_changed { + ui.ctx().reset_font_atlas(); + } } fn fonts_tweak_ui(&self, ui: &mut Ui) { diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 53d9172b0..8f98de305 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -408,11 +408,11 @@ impl Options { .show(ui, |ui| { theme_preference.radio_buttons(ui); - std::sync::Arc::make_mut(match theme { + let style = std::sync::Arc::make_mut(match theme { Theme::Dark => dark_style, Theme::Light => light_style, - }) - .ui(ui); + }); + style.ui(ui); }); CollapsingHeader::new("✒ Painting") diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 205aa5786..364b3fffc 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -3,7 +3,7 @@ #![allow(clippy::if_same_then_else)] use emath::Align; -use epaint::{CornerRadius, Shadow, Stroke, text::FontTweak}; +use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak}; use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc}; use crate::{ @@ -921,6 +921,9 @@ pub struct Visuals { /// this is more to provide a convenient summary of the rest of the settings. pub dark_mode: bool, + /// ADVANCED: Controls how we render text. + pub text_alpha_from_coverage: AlphaFromCoverage, + /// Override default text color for all text. /// /// This is great for setting the color of text for any widget. @@ -1374,6 +1377,7 @@ impl Visuals { pub fn dark() -> Self { Self { dark_mode: true, + text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT, override_text_color: None, weak_text_alpha: 0.6, weak_text_color: None, @@ -1436,6 +1440,7 @@ impl Visuals { pub fn light() -> Self { Self { dark_mode: false, + text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT, widgets: Widgets::light(), selection: Selection::light(), hyperlink_color: Color32::from_rgb(0, 155, 255), @@ -2068,6 +2073,7 @@ impl Visuals { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { dark_mode, + text_alpha_from_coverage, override_text_color: _, weak_text_alpha, weak_text_color, @@ -2216,6 +2222,10 @@ impl Visuals { "Weak text color", ); }); + + ui.add_space(4.0); + + text_alpha_from_coverage_ui(ui, text_alpha_from_coverage); }); ui.collapsing("Text cursor", |ui| { @@ -2326,6 +2336,40 @@ impl Visuals { } } +fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) { + let mut dark_mode_special = + *text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq; + + ui.horizontal(|ui| { + ui.label("Text rendering:"); + + ui.checkbox(&mut dark_mode_special, "Dark-mode special"); + + if dark_mode_special { + *text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq; + } else { + let mut gamma = match text_alpha_from_coverage { + AlphaFromCoverage::Linear => 1.0, + AlphaFromCoverage::Gamma(gamma) => *gamma, + AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same + }; + + ui.add( + DragValue::new(&mut gamma) + .speed(0.01) + .range(0.1..=4.0) + .prefix("Gamma: "), + ); + + if gamma == 1.0 { + *text_alpha_from_coverage = AlphaFromCoverage::Linear; + } else { + *text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma); + } + } + }); +} + impl TextCursorStyle { fn ui(&mut self, ui: &mut Ui) { let Self { diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png index 02e801272..948766c97 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2198a523fb986e90fa3a42f047499f5b1c791075e7c3822b45509d9880073966 -size 60272 +oid sha256:34d85b6015112ea2733f7246f8daabfb9d983523e187339e4d26bfc1f3a3bba3 +size 59460 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png index 40518afe0..150365d5f 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bb371a477f58c90ac72aed45a081f3177ea968f090e3739bdb5044ade29f4be -size 144295 +oid sha256:4f51d75010cd1213daa6a1282d352655e64b69da7bca478011ea055a2e5349bc +size 146500 diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index a5e7b3c1f..a574cbb71 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -544,7 +544,7 @@ impl Painter { let data: Vec = { profiling::scope!("font -> sRGBA"); image - .srgba_pixels(None) + .srgba_pixels(Default::default()) .flat_map(|a| a.to_array()) .collect() }; diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index 8fcef2df7..6b40714cd 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -318,6 +318,59 @@ impl std::fmt::Debug for ColorImage { // ---------------------------------------------------------------------------- +/// How to convert font coverage values into alpha and color values. +// +// This whole thing is less than rigorous. +// Ideally we should do this in a shader instead, and use different computations +// for different text colors. +// See https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html for an in-depth analysis. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum AlphaFromCoverage { + /// `alpha = coverage`. + /// + /// Looks good for black-on-white text, i.e. light mode. + /// + /// Same as [`Self::Gamma`]`(1.0)`, but more efficient. + Linear, + + /// `alpha = coverage^gamma`. + Gamma(f32), + + /// `alpha = 2 * coverage - coverage^2` + /// + /// This looks good for white-on-black text, i.e. dark mode. + /// + /// Very similar to a gamma of 0.5, but produces sharper text. + /// See for a comparison to gamma=0.5. + #[default] + TwoCoverageMinusCoverageSq, +} + +impl AlphaFromCoverage { + /// A good-looking default for light mode (black-on-white text). + pub const LIGHT_MODE_DEFAULT: Self = Self::Linear; + + /// A good-looking default for dark mode (white-on-black text). + pub const DARK_MODE_DEFAULT: Self = Self::TwoCoverageMinusCoverageSq; + + /// Convert coverage to alpha. + #[inline(always)] + pub fn alpha_from_coverage(&self, coverage: f32) -> f32 { + match self { + Self::Linear => coverage, + Self::Gamma(gamma) => coverage.powf(*gamma), + Self::TwoCoverageMinusCoverageSq => 2.0 * coverage - coverage * coverage, + } + } + + #[inline(always)] + pub fn color_from_coverage(&self, coverage: f32) -> Color32 { + let alpha = self.alpha_from_coverage(coverage); + Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha)) + } +} + /// A single-channel image designed for the font texture. /// /// Each value represents "coverage", i.e. how much a texel is covered by a character. @@ -354,30 +407,21 @@ impl FontImage { } /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom. - /// - /// `gamma` should normally be set to `None`. - /// - /// If you are having problems with text looking skinny and pixelated, try using a low gamma, e.g. `0.4`. #[inline] - pub fn srgba_pixels(&self, gamma: Option) -> impl ExactSizeIterator + '_ { - // This whole function is less than rigorous. - // Ideally we should do this in a shader instead, and use different computations - // for different text colors. - // See https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html for an in-depth analysis. - self.pixels.iter().map(move |coverage| { - let alpha = if let Some(gamma) = gamma { - coverage.powf(gamma) - } else { - // alpha = coverage * coverage; // recommended by the article for WHITE text (using linear blending) + pub fn srgba_pixels( + &self, + alpha_from_coverage: AlphaFromCoverage, + ) -> impl ExactSizeIterator + '_ { + self.pixels + .iter() + .map(move |&coverage| alpha_from_coverage.color_from_coverage(coverage)) + } - // The following is recommended by the article for BLACK text (using linear blending). - // Very similar to a gamma of 0.5, but produces sharper text. - // In practice it works well for all text colors (better than a gamma of 0.5, for instance). - // See https://www.desmos.com/calculator/w0ndf5blmn for a visual comparison. - 2.0 * coverage - coverage * coverage - }; - Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha)) - }) + /// Convert this coverage image to a [`ColorImage`]. + pub fn to_color_image(&self, alpha_from_coverage: AlphaFromCoverage) -> ColorImage { + profiling::function_scope!(); + let pixels = self.srgba_pixels(alpha_from_coverage).collect(); + ColorImage::new(self.size, pixels) } /// Clone a sub-region as a new image. diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 264966809..7afc7b146 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -50,7 +50,7 @@ pub use self::{ color::ColorMode, corner_radius::CornerRadius, corner_radius_f32::CornerRadiusF32, - image::{ColorImage, FontImage, ImageData, ImageDelta}, + image::{AlphaFromCoverage, ColorImage, FontImage, ImageData, ImageDelta}, margin::Margin, margin_f32::*, mesh::{Mesh, Mesh16, Vertex}, From 1878874f7d4d4a8e59f4c881f842adec61b03112 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 2 Jul 2025 16:14:46 +0200 Subject: [PATCH 24/56] Free textures after submitting queue instead of before with wgpu renderer on Web (#7291) --- crates/eframe/src/web/web_painter_wgpu.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index debc6c5d1..735c94d73 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -279,13 +279,6 @@ impl WebPainter for WebPainterWgpu { Some((output_frame, capture_buffer)) }; - { - let mut renderer = render_state.renderer.write(); - for id in &textures_delta.free { - renderer.free_texture(id); - } - } - // Submit the commands: both the main buffer and user-defined ones. render_state .queue @@ -307,6 +300,16 @@ impl WebPainter for WebPainterWgpu { frame.present(); } + // Free textures marked for destruction **after** queue submit since they might still be used in the current frame. + // Calling `wgpu::Texture::destroy` on a texture that is still in use would invalidate the command buffer(s) it is used in. + // However, once we called `wgpu::Queue::submit`, it is up for wgpu to determine how long the underlying gpu resource has to live. + { + let mut renderer = render_state.renderer.write(); + for id in &textures_delta.free { + renderer.free_texture(id); + } + } + Ok(()) } From 40c69cd1ba7b5324b92db53dfbeee588b914c9a3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 3 Jul 2025 08:58:45 +0200 Subject: [PATCH 25/56] Respect and detect `prefers-color-scheme: no-preference` (#7293) I don't think this will make a difference in practice, but technically there are three preference states: * `dark` * `light` * `no-preference` Previously we would only check for `dark`, and if not set would assume `light`. Not we also check `light` and if we're neither `dark` or `light` we assume nothing. --- crates/eframe/src/web/events.rs | 27 ++++++++++++++----------- crates/eframe/src/web/mod.rs | 36 ++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 2bffdb780..168d6123a 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -3,8 +3,8 @@ use crate::web::string_from_js_value; use super::{ AppRunner, Closure, DEBUG_RESIZE, JsCast as _, JsValue, WebRunner, button_from_mouse_event, location_hash, modifiers_from_kb_event, modifiers_from_mouse_event, modifiers_from_wheel_event, - native_pixels_per_point, pos_from_mouse_event, prefers_color_scheme_dark, primary_touch_pos, - push_touches, text_from_keyboard_event, theme_from_dark_mode, translate_key, + native_pixels_per_point, pos_from_mouse_event, prefers_color_scheme, primary_touch_pos, + push_touches, text_from_keyboard_event, translate_key, }; use web_sys::{Document, EventTarget, ShadowRoot}; @@ -469,16 +469,19 @@ fn install_color_scheme_change_event( runner_ref: &WebRunner, window: &web_sys::Window, ) -> Result<(), JsValue> { - if let Some(media_query_list) = prefers_color_scheme_dark(window)? { - runner_ref.add_event_listener::( - &media_query_list, - "change", - |event, runner| { - let theme = theme_from_dark_mode(event.matches()); - runner.input.raw.system_theme = Some(theme); - runner.needs_repaint.repaint_asap(); - }, - )?; + for theme in [egui::Theme::Dark, egui::Theme::Light] { + if let Some(media_query_list) = prefers_color_scheme(window, theme)? { + runner_ref.add_event_listener::( + &media_query_list, + "change", + |_event, runner| { + if let Some(theme) = super::system_theme() { + runner.input.raw.system_theme = Some(theme); + runner.needs_repaint.repaint_asap(); + } + }, + )?; + } } Ok(()) diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 2bdd3af63..fdc9d2123 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -40,6 +40,7 @@ pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; pub use backend::*; +use egui::Theme; use wasm_bindgen::prelude::*; use web_sys::{Document, MediaQueryList, Node}; @@ -113,24 +114,31 @@ pub fn native_pixels_per_point() -> f32 { /// /// `None` means unknown. pub fn system_theme() -> Option { - let dark_mode = prefers_color_scheme_dark(&web_sys::window()?) - .ok()?? - .matches(); - Some(theme_from_dark_mode(dark_mode)) -} - -fn prefers_color_scheme_dark(window: &web_sys::Window) -> Result, JsValue> { - window.match_media("(prefers-color-scheme: dark)") -} - -fn theme_from_dark_mode(dark_mode: bool) -> egui::Theme { - if dark_mode { - egui::Theme::Dark + let window = web_sys::window()?; + if does_prefer_color_scheme(&window, Theme::Dark) == Some(true) { + Some(Theme::Dark) + } else if does_prefer_color_scheme(&window, Theme::Light) == Some(true) { + Some(Theme::Light) } else { - egui::Theme::Light + None } } +fn does_prefer_color_scheme(window: &web_sys::Window, theme: Theme) -> Option { + Some(prefers_color_scheme(window, theme).ok()??.matches()) +} + +fn prefers_color_scheme( + window: &web_sys::Window, + theme: Theme, +) -> Result, JsValue> { + let theme = match theme { + Theme::Dark => "dark", + Theme::Light => "light", + }; + window.match_media(format!("(prefers-color-scheme: {theme})").as_str()) +} + /// Returns the canvas in client coordinates. fn canvas_content_rect(canvas: &web_sys::HtmlCanvasElement) -> egui::Rect { let bounding_rect = canvas.get_bounding_client_rect(); From 378e22e6ec17b16236855459586c2f171d1dafb6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 3 Jul 2025 09:16:13 +0200 Subject: [PATCH 26/56] Improve the `ThemePreference` selection UI slightly --- crates/egui/src/memory/theme.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/memory/theme.rs b/crates/egui/src/memory/theme.rs index dd4d3d6f9..555edaedd 100644 --- a/crates/egui/src/memory/theme.rs +++ b/crates/egui/src/memory/theme.rs @@ -89,9 +89,32 @@ impl ThemePreference { /// Show radio-buttons to switch between light mode, dark mode and following the system theme. pub fn radio_buttons(&mut self, ui: &mut crate::Ui) { ui.horizontal(|ui| { - ui.selectable_value(self, Self::Light, "☀ Light"); - ui.selectable_value(self, Self::Dark, "🌙 Dark"); - ui.selectable_value(self, Self::System, "💻 System"); + let system_theme = ui.ctx().input(|i| i.raw.system_theme); + + ui.selectable_value(self, Self::System, "💻 System") + .on_hover_ui(|ui| { + ui.label("Follow the system theme preference."); + + ui.add_space(4.0); + + if let Some(system_theme) = system_theme { + ui.label(format!( + "The current system theme is: {}", + match system_theme { + Theme::Dark => "dark", + Theme::Light => "light", + } + )); + } else { + ui.label("The system theme is unknown."); + } + }); + + ui.selectable_value(self, Self::Dark, "🌙 Dark") + .on_hover_text("Use the dark mode theme"); + + ui.selectable_value(self, Self::Light, "☀ Light") + .on_hover_text("Use the light mode theme"); }); } } From 6d312cc4c79d2a21941af40c4496ed5b3adb78c4 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 3 Jul 2025 12:02:05 +0200 Subject: [PATCH 27/56] Add support for scrolling via accesskit / kittest (#7286) I need to scroll in a snapshot test in my app, and kittest had no utilities for this. Event::MouseWheel is error prone. This adds support for some accesskit scroll actions, and uses this in kittest to add helpers to scroll to a node / scroll the scroll area surrounding a node. The accesskit code says down/up/left/right `Scrolls by approximately one screen in a specific direction.`. Unfortunately it's difficult to get the size of a "screen" (I guess that would be the size of the containing scroll area)where I implemented the scrolling, so for now I've hardcoded it to 100px. I think scrolling a fixed amount is still better than not scrolling at all. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/context.rs | 47 +++++++++++++- crates/egui/src/input_state/mod.rs | 17 ++++++ crates/egui_kittest/src/lib.rs | 16 ++++- crates/egui_kittest/src/node.rs | 45 ++++++++++++++ .../tests/snapshots/test_scroll_initial.png | 3 + .../tests/snapshots/test_scroll_scrolled.png | 3 + crates/egui_kittest/tests/tests.rs | 61 ++++++++++++++++++- 7 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 crates/egui_kittest/tests/snapshots/test_scroll_initial.png create mode 100644 crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 80bcf570f..abb03b1c8 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1149,7 +1149,7 @@ impl Context { ID clashes happens when things like Windows or CollapsingHeaders share names,\n\ or when things like Plot and Grid:s aren't given unique id_salt:s.\n\n\ Sometimes the solution is to use ui.push_id.", - if below { "above" } else { "below" }) + if below { "above" } else { "below" }), ); } } @@ -1216,6 +1216,51 @@ impl Context { self.accesskit_node_builder(w.id, |builder| res.fill_accesskit_node_common(builder)); } + #[cfg(feature = "accesskit")] + self.write(|ctx| { + use crate::{Align, pass_state::ScrollTarget, style::ScrollAnimation}; + let viewport = ctx.viewport_for(ctx.viewport_id()); + + viewport + .input + .consume_accesskit_action_requests(res.id, |request| { + // TODO(lucasmerlin): Correctly handle the scroll unit: + // https://github.com/AccessKit/accesskit/blob/e639c0e0d8ccbfd9dff302d972fa06f9766d608e/common/src/lib.rs#L2621 + const DISTANCE: f32 = 100.0; + + match &request.action { + accesskit::Action::ScrollIntoView => { + viewport.this_pass.scroll_target = [ + Some(ScrollTarget::new( + res.rect.x_range(), + Some(Align::Center), + ScrollAnimation::none(), + )), + Some(ScrollTarget::new( + res.rect.y_range(), + Some(Align::Center), + ScrollAnimation::none(), + )), + ]; + } + accesskit::Action::ScrollDown => { + viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::UP; + } + accesskit::Action::ScrollUp => { + viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::DOWN; + } + accesskit::Action::ScrollLeft => { + viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::LEFT; + } + accesskit::Action::ScrollRight => { + viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::RIGHT; + } + _ => return false, + }; + true + }); + }); + res } diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index d87bd5669..fd3e78a21 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -824,6 +824,23 @@ impl InputState { }) } + #[cfg(feature = "accesskit")] + pub fn consume_accesskit_action_requests( + &mut self, + id: crate::Id, + mut consume: impl FnMut(&accesskit::ActionRequest) -> bool, + ) { + let accesskit_id = id.accesskit_id(); + self.events.retain(|event| { + if let Event::AccessKitActionRequest(request) = event { + if request.target == accesskit_id { + return !consume(request); + } + } + true + }); + } + #[cfg(feature = "accesskit")] pub fn has_accesskit_action_request(&self, id: crate::Id, action: accesskit::Action) -> bool { self.accesskit_action_requests(id, action).next().is_some() diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 01aef3266..6adefe53b 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -28,6 +28,7 @@ pub use builder::*; pub use node::*; pub use renderer::*; +use egui::style::ScrollAnimation; use egui::{Key, Modifiers, Pos2, Rect, RepaintCause, Vec2, ViewportId}; use kittest::Queryable; @@ -55,6 +56,10 @@ impl Display for ExceededMaxStepsError { /// The [Harness] has a optional generic state that can be used to pass data to the app / ui closure. /// In _most cases_ it should be fine to just store the state in the closure itself. /// The state functions are useful if you need to access the state after the harness has been created. +/// +/// Some egui style options are changed from the defaults: +/// - The cursor blinking is disabled +/// - The scroll animation is disabled pub struct Harness<'a, State = ()> { pub ctx: egui::Context, input: egui::RawInput, @@ -96,8 +101,12 @@ impl<'a, State> Harness<'a, State> { let ctx = ctx.unwrap_or_default(); ctx.set_theme(theme); ctx.enable_accesskit(); - // Disable cursor blinking so it doesn't interfere with snapshots - ctx.all_styles_mut(|style| style.visuals.text_cursor.blink = false); + ctx.all_styles_mut(|style| { + // Disable cursor blinking so it doesn't interfere with snapshots + style.visuals.text_cursor.blink = false; + style.scroll_animation = ScrollAnimation::none(); + style.animation_time = 0.0; + }); let mut input = egui::RawInput { screen_rect: Some(screen_rect), ..Default::default() @@ -564,7 +573,8 @@ impl<'a, State> Harness<'a, State> { .expect("Missing root viewport") } - fn root(&self) -> Node<'_> { + /// The root node of the test harness. + pub fn root(&self) -> Node<'_> { Node { accesskit_node: self.kittest.root(), queue: &self.queued_events, diff --git a/crates/egui_kittest/src/node.rs b/crates/egui_kittest/src/node.rs index 51a0cc3a0..94940ffff 100644 --- a/crates/egui_kittest/src/node.rs +++ b/crates/egui_kittest/src/node.rs @@ -159,4 +159,49 @@ impl Node<'_> { pub fn is_focused(&self) -> bool { self.accesskit_node.is_focused() } + + /// Scroll the node into view. + pub fn scroll_to_me(&self) { + self.event(egui::Event::AccessKitActionRequest(ActionRequest { + action: accesskit::Action::ScrollIntoView, + target: self.accesskit_node.id(), + data: None, + })); + } + + /// Scroll the [`egui::ScrollArea`] containing this node down (100px). + pub fn scroll_down(&self) { + self.event(egui::Event::AccessKitActionRequest(ActionRequest { + action: accesskit::Action::ScrollDown, + target: self.accesskit_node.id(), + data: None, + })); + } + + /// Scroll the [`egui::ScrollArea`] containing this node up (100px). + pub fn scroll_up(&self) { + self.event(egui::Event::AccessKitActionRequest(ActionRequest { + action: accesskit::Action::ScrollUp, + target: self.accesskit_node.id(), + data: None, + })); + } + + /// Scroll the [`egui::ScrollArea`] containing this node left (100px). + pub fn scroll_left(&self) { + self.event(egui::Event::AccessKitActionRequest(ActionRequest { + action: accesskit::Action::ScrollLeft, + target: self.accesskit_node.id(), + data: None, + })); + } + + /// Scroll the [`egui::ScrollArea`] containing this node right (100px). + pub fn scroll_right(&self) { + self.event(egui::Event::AccessKitActionRequest(ActionRequest { + action: accesskit::Action::ScrollRight, + target: self.accesskit_node.id(), + data: None, + })); + } } diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png new file mode 100644 index 000000000..32969d743 --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70d76e55327de17163bc9c7e128c28153f95db3229dec919352a024eb80544f1 +size 7399 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png new file mode 100644 index 000000000..361925d04 --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4b7b3145401b7cf9815a652a0914b230892ffda3b5e23fea530dafee9c0c3d3 +size 8110 diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index b4e49642f..6d66c5f5a 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -1,5 +1,5 @@ -use egui::{Modifiers, Vec2, include_image}; -use egui_kittest::Harness; +use egui::{Modifiers, ScrollArea, Vec2, include_image}; +use egui_kittest::{Harness, SnapshotResults}; use kittest::Queryable as _; #[test] @@ -81,3 +81,60 @@ fn should_wait_for_images() { harness.snapshot("should_wait_for_images"); } + +fn test_scroll_harness() -> Harness<'static, bool> { + Harness::builder() + .with_size(Vec2::new(100.0, 200.0)) + .build_ui_state( + |ui, state| { + ScrollArea::vertical().show(ui, |ui| { + for i in 0..20 { + ui.label(format!("Item {i}")); + } + if ui.button("Hidden Button").clicked() { + *state = true; + }; + }); + }, + false, + ) +} + +#[test] +fn test_scroll_to_me() { + let mut harness = test_scroll_harness(); + let mut results = SnapshotResults::new(); + + results.add(harness.try_snapshot("test_scroll_initial")); + + harness.get_by_label("Hidden Button").scroll_to_me(); + + harness.run(); + results.add(harness.try_snapshot("test_scroll_scrolled")); + + harness.get_by_label("Hidden Button").click(); + harness.run(); + + assert!( + harness.state(), + "The button was not clicked after scrolling." + ); +} + +#[test] +fn test_scroll_down() { + let mut harness = test_scroll_harness(); + + let button = harness.get_by_label("Hidden Button"); + button.scroll_down(); + button.scroll_down(); + harness.run(); + + harness.get_by_label("Hidden Button").click(); + harness.run(); + + assert!( + harness.state(), + "The button was not clicked after scrolling down. (Probably not scrolled enough / at all)" + ); +} From db3543d034b3de310b20036959b5a100e4977b0e Mon Sep 17 00:00:00 2001 From: Blackberry Float Date: Thu, 3 Jul 2025 07:14:07 -0400 Subject: [PATCH 28/56] Update area struct to allow force resizing (#7114) This is a really small PR so I am skipping the issue (based on contributing.md). This change adds an optional field and thus non breaking for the API. I ran into an issue during my development of an alerts manager widget ([see PR](https://github.com/blackberryfloat/egui_widget_ext/pull/2)) where I needed a scrollable overlay that did not block clicking areas of a parent widget when my alerts did not take up the entire parent. To achieve this I detect the sizing pass via the invisible flag and only render the alerts content and then on the next pass I add the scroll bar in around the alert content. Whenever the alert content changed though I would need to create a new Area with a new id to get proper sizing. That is a memory leak so I wanted to reset the size state to trigger a sizing pass. Memory is rightfully protected enough that the path to remove memory was dropped and I just added a hook to set a resize flag. I am sure there are better ways but this is what made sense to me. Looking forward to thoughts. ~~Logistics wise, I have proposed it as a patch because I was based off 0.31.1 for testing. I was also thinking it could be released quickly. I am happy to cherry pick onto main after. If that is not allowed I can rebase to main and pull against that.~~ (rebased on main) --------- Co-authored-by: Wesley Murray --- crates/egui/src/containers/area.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index d40df8358..5ae4a4b30 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -121,6 +121,7 @@ pub struct Area { new_pos: Option, fade_in: bool, layout: Layout, + sizing_pass: bool, } impl WidgetWithState for Area { @@ -147,6 +148,7 @@ impl Area { anchor: None, fade_in: true, layout: Layout::default(), + sizing_pass: false, } } @@ -357,6 +359,27 @@ impl Area { self.layout = layout; self } + + /// While true, a sizing pass will be done. This means the area will be invisible + /// and the contents will be laid out to estimate the proper containing size of the area. + /// If false, there will be no change to the default area behavior. This is useful if the + /// area contents area dynamic and you need to need to make sure the area adjusts its size + /// accordingly. + /// + /// This should only be set to true during the specific frames you want force a sizing pass. + /// Do NOT hard-code this as `.sizing_pass(true)`, as it will cause the area to never be + /// visible. + /// + /// # Arguments + /// - resize: If true, the area will be resized to fit its contents. False will keep the + /// default area resizing behavior. + /// + /// Default: `false`. + #[inline] + pub fn sizing_pass(mut self, resize: bool) -> Self { + self.sizing_pass = resize; + self + } } pub(crate) struct Prepared { @@ -410,6 +433,7 @@ impl Area { constrain_rect, fade_in, layout, + sizing_pass: force_sizing_pass, } = self; let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.screen_rect()); @@ -425,6 +449,10 @@ impl Area { interactable, last_became_visible_at: None, }); + if force_sizing_pass { + sizing_pass = true; + state.size = None; + } state.pivot = pivot; state.interactable = interactable; if let Some(new_pos) = new_pos { From ba577602a434c78260e1c0582cdc18618c59f2fa Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 3 Jul 2025 13:40:02 +0200 Subject: [PATCH 29/56] Fix crash when using infinite widgets (#7296) * Closes https://github.com/emilk/egui/issues/7100 --- crates/egui/src/hit_test.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index f253a1dfe..1361c1c49 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -91,6 +91,8 @@ pub fn hit_test( } } + close.retain(|rect| !rect.interact_rect.any_nan()); // Protect against bad input and transforms + // When using layer transforms it is common to stack layers close to each other. // For instance, you may have a resize-separator on a panel, with two // transform-layers on either side. From 77df407f50230da3a920315eb1061cd0e6811b45 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 3 Jul 2025 14:23:15 +0200 Subject: [PATCH 30/56] `egui_kittest`: add `failed_pixel_count_threshold` (#7092) I thought about this - so we have two options here: 1. adding it to `SnapshotOptions` 2. adding it to every function which I do not like as this would be a huge breaking change ## Summary This pull request introduces a new feature to the `SnapshotOptions` struct in the `egui_kittest` crate, allowing users to specify a permissible percentage of pixel differences (`diff_percentage`) before a snapshot comparison is considered a failure. This feature provides more flexibility in handling minor visual discrepancies during snapshot testing. ### Additions to `SnapshotOptions`: * Added a new field `diff_percentage` of type `Option` to the `SnapshotOptions` struct. This field allows users to define a tolerance for pixel differences, with a default value of `None` (interpreted as 0% tolerance). * Updated the `Default` implementation of `SnapshotOptions` to initialize `diff_percentage` to `None`. ### Integration into snapshot comparison logic: * Updated the `try_image_snapshot_options` function to handle the new `diff_percentage` field. If a `diff_percentage` is specified, the function calculates the percentage of differing pixels and allows the snapshot to pass if the difference is within the specified tolerance. [[1]](diffhunk://#diff-6f481b5866b82a4fe126b7df2e6c9669040c79d1d200d76b87f376de5dec5065R204) [[2]](diffhunk://#diff-6f481b5866b82a4fe126b7df2e6c9669040c79d1d200d76b87f376de5dec5065R294-R301) * Closes * [x] I have followed the instructions in the PR template --------- Co-authored-by: lucasmerlin Co-authored-by: Emil Ernerfeldt --- .../src/demo/demo_app_windows.rs | 7 +- crates/egui_kittest/src/snapshot.rs | 131 +++++++++++++++++- 2 files changed, 130 insertions(+), 8 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 0e3a0d2c2..4414a9572 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -373,7 +373,7 @@ mod tests { use crate::{Demo as _, demo::demo_app_windows::DemoGroups}; use egui_kittest::kittest::{NodeT as _, Queryable as _}; - use egui_kittest::{Harness, SnapshotOptions, SnapshotResults}; + use egui_kittest::{Harness, OsThreshold, SnapshotOptions, SnapshotResults}; #[test] fn demos_should_match_snapshot() { @@ -410,9 +410,10 @@ mod tests { harness.run_ok(); let mut options = SnapshotOptions::default(); - // The Bézier Curve demo needs a threshold of 2.1 to pass on linux + if name == "Bézier Curve" { - options.threshold = 2.1; + // The Bézier Curve demo needs a threshold of 2.1 to pass on linux: + options = options.threshold(OsThreshold::new(0.0).linux(2.1)); } results.add(harness.try_snapshot_options(&format!("demos/{name}"), &options)); diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 3c7dc265a..e53615d90 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -13,16 +13,117 @@ pub struct SnapshotOptions { /// wgpu backends). pub threshold: f32, + /// The number of pixels that can differ before the snapshot is considered a failure. + /// Preferably, you should use `threshold` to control the sensitivity of the image comparison. + /// As a last resort, you can use this to allow a certain number of pixels to differ. + /// If `None`, the default is `0` (meaning no pixels can differ). + /// If `Some`, the value can be set per OS + pub failed_pixel_count_threshold: usize, + /// The path where the snapshots will be saved. /// The default is `tests/snapshots`. pub output_path: PathBuf, } +/// Helper struct to define the number of pixels that can differ before the snapshot is considered a failure. +/// This is useful if you want to set different thresholds for different operating systems. +/// +/// The default values are 0 / 0.0 +/// +/// Example usage: +/// ```no_run +/// use egui_kittest::{OsThreshold, SnapshotOptions}; +/// let mut harness = egui_kittest::Harness::new_ui(|ui| { +/// ui.label("Hi!"); +/// }); +/// harness.snapshot_options( +/// "os_threshold_example", +/// &SnapshotOptions::new() +/// .threshold(OsThreshold::new(0.0).windows(10.0)) +/// .failed_pixel_count_threshold(OsThreshold::new(0).windows(10).macos(53) +/// )) +/// ``` +#[derive(Debug, Clone, Copy)] +pub struct OsThreshold { + pub windows: T, + pub macos: T, + pub linux: T, + pub fallback: T, +} + +impl From for OsThreshold { + fn from(value: usize) -> Self { + Self::new(value) + } +} + +impl OsThreshold +where + T: Copy, +{ + /// Use the same value for all + pub fn new(same: T) -> Self { + Self { + windows: same, + macos: same, + linux: same, + fallback: same, + } + } + + /// Set the threshold for Windows. + #[inline] + pub fn windows(mut self, threshold: T) -> Self { + self.windows = threshold; + self + } + + /// Set the threshold for macOS. + #[inline] + pub fn macos(mut self, threshold: T) -> Self { + self.macos = threshold; + self + } + + /// Set the threshold for Linux. + #[inline] + pub fn linux(mut self, threshold: T) -> Self { + self.linux = threshold; + self + } + + /// Get the threshold for the current operating system. + pub fn threshold(&self) -> T { + if cfg!(target_os = "windows") { + self.windows + } else if cfg!(target_os = "macos") { + self.macos + } else if cfg!(target_os = "linux") { + self.linux + } else { + self.fallback + } + } +} + +impl From> for usize { + fn from(threshold: OsThreshold) -> Self { + threshold.threshold() + } +} + +impl From> for f32 { + fn from(threshold: OsThreshold) -> Self { + threshold.threshold() + } +} + impl Default for SnapshotOptions { fn default() -> Self { Self { threshold: 0.6, output_path: PathBuf::from("tests/snapshots"), + failed_pixel_count_threshold: 0, // Default is 0, meaning no pixels can differ } } } @@ -37,8 +138,8 @@ impl SnapshotOptions { /// The default is `0.6` (which is enough for most egui tests to pass across different /// wgpu backends). #[inline] - pub fn threshold(mut self, threshold: f32) -> Self { - self.threshold = threshold; + pub fn threshold(mut self, threshold: impl Into) -> Self { + self.threshold = threshold.into(); self } @@ -49,6 +150,20 @@ impl SnapshotOptions { self.output_path = output_path.into(); self } + + /// Change the number of pixels that can differ before the snapshot is considered a failure. + /// + /// Preferably, you should use [`Self::threshold`] to control the sensitivity of the image comparison. + /// As a last resort, you can use this to allow a certain number of pixels to differ. + #[inline] + pub fn failed_pixel_count_threshold( + mut self, + failed_pixel_count_threshold: impl Into>, + ) -> Self { + let failed_pixel_count_threshold = failed_pixel_count_threshold.into().threshold(); + self.failed_pixel_count_threshold = failed_pixel_count_threshold; + self + } } #[derive(Debug)] @@ -58,7 +173,7 @@ pub enum SnapshotError { /// Name of the test name: String, - /// Count of pixels that were different + /// Count of pixels that were different (above the per-pixel threshold). diff: i32, /// Path where the diff image was saved @@ -201,6 +316,7 @@ pub fn try_image_snapshot_options( let SnapshotOptions { threshold, output_path, + failed_pixel_count_threshold, } = options; let parent_path = if let Some(parent) = PathBuf::from(name).parent() { @@ -280,19 +396,24 @@ pub fn try_image_snapshot_options( let result = dify::diff::get_results(previous, new.clone(), *threshold, true, None, &None, &None); - if let Some((diff, result_image)) = result { + if let Some((num_wrong_pixels, result_image)) = result { result_image .save(diff_path.clone()) .map_err(|err| SnapshotError::WriteSnapshot { path: diff_path.clone(), err, })?; + if should_update_snapshots() { update_snapshot() } else { + if num_wrong_pixels as i64 <= *failed_pixel_count_threshold as i64 { + return Ok(()); + } + Err(SnapshotError::Diff { name: name.to_owned(), - diff, + diff: num_wrong_pixels, diff_path, }) } From 2b62c68598f3ecfb5c464ff6758db5eda4d474df Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 3 Jul 2025 14:31:35 +0200 Subject: [PATCH 31/56] Add `egui::Sides` `shrink_left` / `shrink_right` (#7295) This allows contents (on one of the sides) in egui::Sides to shrink. * related https://github.com/rerun-io/rerun/issues/10494 --- crates/egui/src/containers/sides.rs | 212 ++++++++++++++---- .../tests/snapshots/sides/default_long.png | 3 + .../sides/default_long_fit_contents.png | 3 + .../tests/snapshots/sides/default_short.png | 3 + .../sides/default_short_fit_contents.png | 3 + .../snapshots/sides/shrink_left_long.png | 3 + .../sides/shrink_left_long_fit_contents.png | 3 + .../snapshots/sides/shrink_left_short.png | 3 + .../sides/shrink_left_short_fit_contents.png | 3 + .../snapshots/sides/shrink_right_long.png | 3 + .../sides/shrink_right_long_fit_contents.png | 3 + .../snapshots/sides/shrink_right_short.png | 3 + .../sides/shrink_right_short_fit_contents.png | 3 + .../tests/snapshots/sides/wrap_left_long.png | 3 + .../sides/wrap_left_long_fit_contents.png | 3 + .../tests/snapshots/sides/wrap_left_short.png | 3 + .../sides/wrap_left_short_fit_contents.png | 3 + .../tests/snapshots/sides/wrap_right_long.png | 3 + .../sides/wrap_right_long_fit_contents.png | 3 + .../snapshots/sides/wrap_right_short.png | 3 + .../sides/wrap_right_short_fit_contents.png | 3 + tests/egui_tests/tests/test_sides.rs | 76 +++++++ 22 files changed, 308 insertions(+), 40 deletions(-) create mode 100644 tests/egui_tests/tests/snapshots/sides/default_long.png create mode 100644 tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/default_short.png create mode 100644 tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_left_long.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_left_long_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_left_short.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_left_short_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_right_long.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_right_long_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_right_short.png create mode 100644 tests/egui_tests/tests/snapshots/sides/shrink_right_short_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_left_long.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_left_long_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_left_short.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_left_short_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_right_long.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_right_long_fit_contents.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_right_short.png create mode 100644 tests/egui_tests/tests/snapshots/sides/wrap_right_short_fit_contents.png create mode 100644 tests/egui_tests/tests/test_sides.rs diff --git a/crates/egui/src/containers/sides.rs b/crates/egui/src/containers/sides.rs index e34ae70eb..8a67c6c5e 100644 --- a/crates/egui/src/containers/sides.rs +++ b/crates/egui/src/containers/sides.rs @@ -1,4 +1,4 @@ -use emath::Align; +use emath::{Align, NumExt as _}; use crate::{Layout, Ui, UiBuilder}; @@ -20,8 +20,13 @@ use crate::{Layout, Ui, UiBuilder}; /// /// If the parent is not wide enough to fit all widgets, the parent will be expanded to the right. /// -/// The left widgets are first added to the ui, left-to-right. -/// Then the right widgets are added, right-to-left. +/// The left widgets are added left-to-right. +/// The right widgets are added right-to-left. +/// +/// Which side is first depends on the configuration: +/// - [`Sides::extend`] - left widgets are added first +/// - [`Sides::shrink_left`] - right widgets are added first +/// - [`Sides::shrink_right`] - left widgets are added first /// /// ``` /// # egui::__run_test_ui(|ui| { @@ -40,6 +45,16 @@ use crate::{Layout, Ui, UiBuilder}; pub struct Sides { height: Option, spacing: Option, + kind: SidesKind, + wrap_mode: Option, +} + +#[derive(Clone, Copy, Debug, Default)] +enum SidesKind { + #[default] + Extend, + ShrinkLeft, + ShrinkRight, } impl Sides { @@ -68,58 +83,175 @@ impl Sides { self } + /// Try to shrink widgets on the left side. + /// + /// Right widgets will be added first. The left [`Ui`]s max rect will be limited to the + /// remaining space. + #[inline] + pub fn shrink_left(mut self) -> Self { + self.kind = SidesKind::ShrinkLeft; + self + } + + /// Try to shrink widgets on the right side. + /// + /// Left widgets will be added first. The right [`Ui`]s max rect will be limited to the + /// remaining space. + #[inline] + pub fn shrink_right(mut self) -> Self { + self.kind = SidesKind::ShrinkRight; + self + } + + /// Extend the left and right sides to fill the available space. + /// + /// This is the default behavior. + /// The left widgets will be added first, followed by the right widgets. + #[inline] + pub fn extend(mut self) -> Self { + self.kind = SidesKind::Extend; + self + } + + /// The text wrap mode for the shrinking side. + /// + /// Does nothing if [`Self::extend`] is used (the default). + #[inline] + pub fn wrap_mode(mut self, wrap_mode: crate::TextWrapMode) -> Self { + self.wrap_mode = Some(wrap_mode); + self + } + + /// Truncate the text on the shrinking side. + /// + /// This is a shortcut for [`Self::wrap_mode`]. + /// Does nothing if [`Self::extend`] is used (the default). + #[inline] + pub fn truncate(mut self) -> Self { + self.wrap_mode = Some(crate::TextWrapMode::Truncate); + self + } + + /// Wrap the text on the shrinking side. + /// + /// This is a shortcut for [`Self::wrap_mode`]. + /// Does nothing if [`Self::extend`] is used (the default). + #[inline] + pub fn wrap(mut self) -> Self { + self.wrap_mode = Some(crate::TextWrapMode::Wrap); + self + } + pub fn show( self, ui: &mut Ui, add_left: impl FnOnce(&mut Ui) -> RetL, add_right: impl FnOnce(&mut Ui) -> RetR, ) -> (RetL, RetR) { - let Self { height, spacing } = self; + let Self { + height, + spacing, + mut kind, + mut wrap_mode, + } = self; let height = height.unwrap_or_else(|| ui.spacing().interact_size.y); let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing.x); let mut top_rect = ui.available_rect_before_wrap(); top_rect.max.y = top_rect.min.y + height; - let result_left; - let result_right; - - let left_rect = { - let left_max_rect = top_rect; - let mut left_ui = ui.new_child( - UiBuilder::new() - .max_rect(left_max_rect) - .layout(Layout::left_to_right(Align::Center)), - ); - result_left = add_left(&mut left_ui); - left_ui.min_rect() - }; - - let right_rect = { - let right_max_rect = top_rect.with_min_x(left_rect.max.x); - let mut right_ui = ui.new_child( - UiBuilder::new() - .max_rect(right_max_rect) - .layout(Layout::right_to_left(Align::Center)), - ); - result_right = add_right(&mut right_ui); - right_ui.min_rect() - }; - - let mut final_rect = left_rect.union(right_rect); - let min_width = left_rect.width() + spacing + right_rect.width(); - if ui.is_sizing_pass() { - // Make as small as possible: - final_rect.max.x = left_rect.min.x + min_width; - } else { - // If the rects overlap, make sure we expand the allocated rect so that the parent - // ui knows we overflowed, and resizes: - final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width); + kind = SidesKind::Extend; + wrap_mode = None; } - ui.advance_cursor_after_rect(final_rect); + match kind { + SidesKind::ShrinkLeft => { + let (right_rect, result_right) = Self::create_ui( + ui, + top_rect, + Layout::right_to_left(Align::Center), + add_right, + None, + ); + let available_width = top_rect.width() - right_rect.width() - spacing; + let left_rect_constraint = + top_rect.with_max_x(top_rect.min.x + available_width.at_least(0.0)); + let (left_rect, result_left) = Self::create_ui( + ui, + left_rect_constraint, + Layout::left_to_right(Align::Center), + add_left, + wrap_mode, + ); - (result_left, result_right) + ui.advance_cursor_after_rect(left_rect.union(right_rect)); + (result_left, result_right) + } + SidesKind::ShrinkRight => { + let (left_rect, result_left) = Self::create_ui( + ui, + top_rect, + Layout::left_to_right(Align::Center), + add_left, + None, + ); + let right_rect_constraint = top_rect.with_min_x(left_rect.max.x + spacing); + let (right_rect, result_right) = Self::create_ui( + ui, + right_rect_constraint, + Layout::right_to_left(Align::Center), + add_right, + wrap_mode, + ); + + ui.advance_cursor_after_rect(left_rect.union(right_rect)); + (result_left, result_right) + } + SidesKind::Extend => { + let (left_rect, result_left) = Self::create_ui( + ui, + top_rect, + Layout::left_to_right(Align::Center), + add_left, + None, + ); + let right_max_rect = top_rect.with_min_x(left_rect.max.x); + let (right_rect, result_right) = Self::create_ui( + ui, + right_max_rect, + Layout::right_to_left(Align::Center), + add_right, + None, + ); + + let mut final_rect = left_rect.union(right_rect); + let min_width = left_rect.width() + spacing + right_rect.width(); + + if ui.is_sizing_pass() { + final_rect.max.x = left_rect.min.x + min_width; + } else { + final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width); + } + + ui.advance_cursor_after_rect(final_rect); + (result_left, result_right) + } + } + } + + fn create_ui( + ui: &mut Ui, + max_rect: emath::Rect, + layout: Layout, + add_content: impl FnOnce(&mut Ui) -> Ret, + wrap_mode: Option, + ) -> (emath::Rect, Ret) { + let mut child_ui = ui.new_child(UiBuilder::new().max_rect(max_rect).layout(layout)); + if let Some(wrap_mode) = wrap_mode { + child_ui.style_mut().wrap_mode = Some(wrap_mode); + } + let result = add_content(&mut child_ui); + (child_ui.min_rect(), result) } } diff --git a/tests/egui_tests/tests/snapshots/sides/default_long.png b/tests/egui_tests/tests/snapshots/sides/default_long.png new file mode 100644 index 000000000..2d66f3665 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/default_long.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7ceaa95512c67dcbf1c8ba5a8f33bf4833c2e863d09903fb71b5aa2822cc086 +size 7889 diff --git a/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png new file mode 100644 index 000000000..15e9dc464 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 +size 8552 diff --git a/tests/egui_tests/tests/snapshots/sides/default_short.png b/tests/egui_tests/tests/snapshots/sides/default_short.png new file mode 100644 index 000000000..756a3068f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/default_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f +size 1647 diff --git a/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png new file mode 100644 index 000000000..6f3189261 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f +size 1276 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png new file mode 100644 index 000000000..39e1bab98 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88e1557dffa7295e7e7e37ed175fcec40aab939f9b67137a1ce33811e8ae4722 +size 7148 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_long_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_long_fit_contents.png new file mode 100644 index 000000000..15e9dc464 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_long_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 +size 8552 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png new file mode 100644 index 000000000..756a3068f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f +size 1647 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_short_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_short_fit_contents.png new file mode 100644 index 000000000..6f3189261 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_short_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f +size 1276 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png new file mode 100644 index 000000000..3326d9527 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:508209ca303751ef323301b25bb3878410742ea79339b75363d2681b98d2712b +size 7068 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_long_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_long_fit_contents.png new file mode 100644 index 000000000..15e9dc464 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_long_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 +size 8552 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png new file mode 100644 index 000000000..756a3068f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f +size 1647 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_short_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_short_fit_contents.png new file mode 100644 index 000000000..6f3189261 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_short_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f +size 1276 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png new file mode 100644 index 000000000..36929a413 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0c9e39c18fc5bb1fc02a86dbf02e3ffca5537dbe8986d5c5b50cb4984c97466 +size 9085 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_long_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_long_fit_contents.png new file mode 100644 index 000000000..15e9dc464 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_long_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 +size 8552 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png new file mode 100644 index 000000000..756a3068f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f +size 1647 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_short_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_short_fit_contents.png new file mode 100644 index 000000000..6f3189261 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_short_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f +size 1276 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png new file mode 100644 index 000000000..47398293f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6e9ba0acb573853ef5b3dedb1156d99cdf80338ccb160093960e8aaa41bd5df +size 9048 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_long_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_long_fit_contents.png new file mode 100644 index 000000000..15e9dc464 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_long_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 +size 8552 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png new file mode 100644 index 000000000..756a3068f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f +size 1647 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_short_fit_contents.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_short_fit_contents.png new file mode 100644 index 000000000..6f3189261 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_short_fit_contents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f +size 1276 diff --git a/tests/egui_tests/tests/test_sides.rs b/tests/egui_tests/tests/test_sides.rs new file mode 100644 index 000000000..293abd311 --- /dev/null +++ b/tests/egui_tests/tests/test_sides.rs @@ -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}"))); + } +} From 47a2bb10b03e48cac8bd4183246f9984c342e2ad Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 3 Jul 2025 16:34:47 +0200 Subject: [PATCH 32/56] Remove `SelectableLabel` (#7277) * part of https://github.com/emilk/egui/issues/7264 * removes SelectableLabel (Use `Button::selectable` instead) * updates `Ui::selectable_value/label` with IntoAtoms support Had to make some changes to `Button` since the SelecatbleLabel had no frame unless selected. --- crates/egui/src/ui.rs | 20 ++--- crates/egui/src/widgets/button.rs | 51 ++++++++++++- crates/egui/src/widgets/mod.rs | 3 +- crates/egui/src/widgets/selected_label.rs | 90 +---------------------- crates/egui_demo_lib/src/demo/password.rs | 2 +- 5 files changed, 63 insertions(+), 103 deletions(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 3738e6e53..c24ca211b 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -27,7 +27,7 @@ use crate::{ vec2, widgets, widgets::{ Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, RadioButton, - SelectableLabel, Separator, Spinner, TextEdit, Widget, color_picker, + Separator, Spinner, TextEdit, Widget, color_picker, }, }; // ---------------------------------------------------------------------------- @@ -2077,13 +2077,13 @@ impl Ui { Checkbox::new(checked, atoms).ui(self) } - /// Acts like a checkbox, but looks like a [`SelectableLabel`]. + /// Acts like a checkbox, but looks like a [`Button::selectable`]. /// /// Click to toggle to bool. /// /// See also [`Self::checkbox`]. - pub fn toggle_value(&mut self, selected: &mut bool, text: impl Into) -> Response { - let mut response = self.selectable_label(*selected, text); + pub fn toggle_value<'a>(&mut self, selected: &mut bool, atoms: impl IntoAtoms<'a>) -> Response { + let mut response = self.selectable_label(*selected, atoms); if response.clicked() { *selected = !*selected; response.mark_changed(); @@ -2134,10 +2134,10 @@ impl Ui { /// Show a label which can be selected or not. /// - /// See also [`SelectableLabel`] and [`Self::toggle_value`]. + /// See also [`Button::selectable`] and [`Self::toggle_value`]. #[must_use = "You should check if the user clicked this with `if ui.selectable_label(…).clicked() { … } "] - pub fn selectable_label(&mut self, checked: bool, text: impl Into) -> Response { - SelectableLabel::new(checked, text).ui(self) + pub fn selectable_label<'a>(&mut self, checked: bool, text: impl IntoAtoms<'a>) -> Response { + Button::selectable(checked, text).ui(self) } /// Show selectable text. It is selected if `*current_value == selected_value`. @@ -2145,12 +2145,12 @@ impl Ui { /// /// Example: `ui.selectable_value(&mut my_enum, Enum::Alternative, "Alternative")`. /// - /// See also [`SelectableLabel`] and [`Self::toggle_value`]. - pub fn selectable_value( + /// See also [`Button::selectable`] and [`Self::toggle_value`]. + pub fn selectable_value<'a, Value: PartialEq>( &mut self, current_value: &mut Value, selected_value: Value, - text: impl Into, + text: impl IntoAtoms<'a>, ) -> Response { let mut response = self.selectable_label(*current_value == selected_value, text); if response.clicked() && *current_value != selected_value { diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index aa75eabd8..d836c0701 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -29,6 +29,7 @@ pub struct Button<'a> { stroke: Option, small: bool, frame: Option, + frame_when_inactive: bool, min_size: Vec2, corner_radius: Option, selected: bool, @@ -44,6 +45,7 @@ impl<'a> Button<'a> { stroke: None, small: false, frame: None, + frame_when_inactive: true, min_size: Vec2::ZERO, corner_radius: None, selected: false, @@ -52,6 +54,27 @@ impl<'a> Button<'a> { } } + /// Show a selectable button. + /// + /// Equivalent to: + /// ```rust + /// # use egui::{Button, IntoAtoms, __run_test_ui}; + /// # __run_test_ui(|ui| { + /// let selected = true; + /// ui.add(Button::new("toggle me").selected(selected).frame_when_inactive(!selected).frame(true)); + /// # }); + /// ``` + /// + /// See also: + /// - [`Ui::selectable_value`] + /// - [`Ui::selectable_label`] + pub fn selectable(selected: bool, atoms: impl IntoAtoms<'a>) -> Self { + Self::new(atoms) + .selected(selected) + .frame_when_inactive(selected) + .frame(true) + } + /// Creates a button with an image. The size of the image as displayed is defined by the provided size. /// /// Note: In contrast to [`Button::new`], this limits the image size to the default font height @@ -138,6 +161,18 @@ impl<'a> Button<'a> { self } + /// If `false`, the button will not have a frame when inactive. + /// + /// Default: `true`. + /// + /// Note: When [`Self::frame`] (or `ui.visuals().button_frame`) is `false`, this setting + /// has no effect. + #[inline] + pub fn frame_when_inactive(mut self, frame_when_inactive: bool) -> Self { + self.frame_when_inactive = frame_when_inactive; + self + } + /// By default, buttons senses clicks. /// Change this to a drag-button with `Sense::drag()`. #[inline] @@ -220,6 +255,7 @@ impl<'a> Button<'a> { stroke, small, frame, + frame_when_inactive, mut min_size, corner_radius, selected, @@ -243,9 +279,9 @@ impl<'a> Button<'a> { let text = layout.text().map(String::from); - let has_frame = frame.unwrap_or_else(|| ui.visuals().button_frame); + let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame); - let mut button_padding = if has_frame { + let mut button_padding = if has_frame_margin { ui.spacing().button_padding } else { Vec2::ZERO @@ -262,13 +298,22 @@ impl<'a> Button<'a> { let response = if ui.is_rect_visible(prepared.response.rect) { let visuals = ui.style().interact_selectable(&prepared.response, selected); + let visible_frame = if frame_when_inactive { + has_frame_margin + } else { + has_frame_margin + && (prepared.response.hovered() + || prepared.response.is_pointer_button_down_on() + || prepared.response.has_focus()) + }; + if image_tint_follows_text_color { prepared.map_images(|image| image.tint(visuals.text_color())); } prepared.fallback_text_color = visuals.text_color(); - if has_frame { + if visible_frame { let stroke = stroke.unwrap_or(visuals.bg_stroke); let fill = fill.unwrap_or(visuals.weak_bg_fill); prepared.frame = prepared diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index d303b181b..9cf003c94 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -22,6 +22,8 @@ mod slider; mod spinner; pub mod text_edit; +#[expect(deprecated)] +pub use self::selected_label::SelectableLabel; pub use self::{ button::Button, checkbox::Checkbox, @@ -35,7 +37,6 @@ pub use self::{ label::Label, progress_bar::ProgressBar, radio_button::RadioButton, - selected_label::SelectableLabel, separator::Separator, slider::{Slider, SliderClamping, SliderOrientation}, spinner::Spinner, diff --git a/crates/egui/src/widgets/selected_label.rs b/crates/egui/src/widgets/selected_label.rs index 4b2ee9ae2..da18b5fe0 100644 --- a/crates/egui/src/widgets/selected_label.rs +++ b/crates/egui/src/widgets/selected_label.rs @@ -1,88 +1,2 @@ -use crate::{ - NumExt as _, Response, Sense, TextStyle, Ui, Widget, WidgetInfo, WidgetText, WidgetType, -}; - -/// One out of several alternatives, either selected or not. -/// Will mark selected items with a different background color. -/// An alternative to [`crate::RadioButton`] and [`crate::Checkbox`]. -/// -/// Usually you'd use [`Ui::selectable_value`] or [`Ui::selectable_label`] instead. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// #[derive(PartialEq)] -/// enum Enum { First, Second, Third } -/// let mut my_enum = Enum::First; -/// -/// ui.selectable_value(&mut my_enum, Enum::First, "First"); -/// -/// // is equivalent to: -/// -/// if ui.add(egui::SelectableLabel::new(my_enum == Enum::First, "First")).clicked() { -/// my_enum = Enum::First -/// } -/// # }); -/// ``` -#[must_use = "You should put this widget in a ui with `ui.add(widget);`"] -pub struct SelectableLabel { - selected: bool, - text: WidgetText, -} - -impl SelectableLabel { - pub fn new(selected: bool, text: impl Into) -> Self { - Self { - selected, - text: text.into(), - } - } -} - -impl Widget for SelectableLabel { - fn ui(self, ui: &mut Ui) -> Response { - let Self { selected, text } = self; - - let button_padding = ui.spacing().button_padding; - let total_extra = button_padding + button_padding; - - let wrap_width = ui.available_width() - total_extra.x; - let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - - let mut desired_size = total_extra + galley.size(); - desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); - let (rect, response) = ui.allocate_at_least(desired_size, Sense::click()); - response.widget_info(|| { - WidgetInfo::selected( - WidgetType::SelectableLabel, - ui.is_enabled(), - selected, - galley.text(), - ) - }); - - if ui.is_rect_visible(response.rect) { - let text_pos = ui - .layout() - .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) - .min; - - let visuals = ui.style().interact_selectable(&response, selected); - - if selected || response.hovered() || response.highlighted() || response.has_focus() { - let rect = rect.expand(visuals.expansion); - - ui.painter().rect( - rect, - visuals.corner_radius, - visuals.weak_bg_fill, - visuals.bg_stroke, - epaint::StrokeKind::Inside, - ); - } - - ui.painter().galley(text_pos, galley, visuals.text_color()); - } - - response - } -} +#[deprecated = "SelectableLabel has been removed. Use Button::selectable() instead"] +pub struct SelectableLabel {} diff --git a/crates/egui_demo_lib/src/demo/password.rs b/crates/egui_demo_lib/src/demo/password.rs index f22b5aa8a..04b3c6f37 100644 --- a/crates/egui_demo_lib/src/demo/password.rs +++ b/crates/egui_demo_lib/src/demo/password.rs @@ -27,7 +27,7 @@ pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response { let result = ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // Toggle the `show_plaintext` bool with a button: let response = ui - .add(egui::SelectableLabel::new(show_plaintext, "👁")) + .selectable_label(show_plaintext, "👁") .on_hover_text("Show/hide password"); if response.clicked() { From d94386de3dc6009f241af15eb17f4329a5cf60cd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 4 Jul 2025 09:55:03 +0200 Subject: [PATCH 33/56] Fix `debug_assert` triggered by `menu`/`intersect_ray` (#7299) --- crates/egui/src/input_state/mod.rs | 5 ++++- crates/emath/src/rect.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index fd3e78a21..a3ebf532e 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -1465,7 +1465,10 @@ impl PointerState { } if let Some(pos) = self.hover_pos() { - return rect.intersects_ray(pos, self.direction()); + let dir = self.direction(); + if dir != Vec2::ZERO { + return rect.intersects_ray(pos, self.direction()); + } } false } diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index dc63315b6..777c12527 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -651,7 +651,7 @@ impl Rect { pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool { debug_assert!( d.is_normalized(), - "expected normalized direction, but `d` has length {}", + "Debug assert: expected normalized direction, but `d` has length {}", d.length() ); From 7ac137bfc167d0f7ff78d5fd042d6042ff190455 Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:15:48 -0400 Subject: [PATCH 34/56] Make the font atlas use a color image (#7298) * [x] I have followed the instructions in the PR template Splitting this out from the Parley work as requested. This removes `FontImage` and makes the font atlas use a `ColorImage`. It converts alpha to coverage at glyph-drawing time, not at delta-upload time. This doesn't do much now, but will allow for color emoji rendering once we start using Parley. I've changed things around so that we pass in `text_alpha_to_coverage` to the `Fonts` the same way we do with `pixels_per_point` and `max_texture_side`, reusing the existing code to check if the setting differs and recreating the font atlas if so. I'm not quite sure why this wasn't done in the first place. I've left `ImageData` as an enum for now, in case we want to add support for more texture pixel formats in the future (which I personally think would be worthwhile). If you'd like, I can just remove that enum entirely. --- crates/egui-wgpu/src/renderer.rs | 13 -- crates/egui/src/context.rs | 63 ++------- crates/egui/src/lib.rs | 2 +- crates/egui_demo_lib/benches/benchmark.rs | 7 +- crates/egui_glow/src/painter.rs | 17 --- crates/epaint/benches/benchmark.rs | 6 +- crates/epaint/src/image.rs | 160 +++++----------------- crates/epaint/src/lib.rs | 2 +- crates/epaint/src/shapes/text_shape.rs | 7 +- crates/epaint/src/text/font.rs | 3 +- crates/epaint/src/text/fonts.rs | 42 ++++-- crates/epaint/src/text/text_layout.rs | 37 ++++- crates/epaint/src/texture_atlas.rs | 29 ++-- crates/epaint/src/textures.rs | 2 +- 14 files changed, 147 insertions(+), 243 deletions(-) diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 473c73028..41a9b3b78 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -564,19 +564,6 @@ impl Renderer { ); Cow::Borrowed(&image.pixels) } - epaint::ImageData::Font(image) => { - assert_eq!( - width as usize * height as usize, - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - profiling::scope!("font -> sRGBA"); - Cow::Owned( - image - .srgba_pixels(Default::default()) - .collect::>(), - ) - } }; let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice()); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index abb03b1c8..f3bc73cec 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -78,7 +78,7 @@ impl Default for WrappedTextureManager { // Will be filled in later let font_id = tex_mngr.alloc( "egui_font_texture".into(), - epaint::FontImage::new([0, 0]).into(), + epaint::ColorImage::filled([0, 0], Color32::TRANSPARENT).into(), Default::default(), ); assert_eq!( @@ -610,6 +610,8 @@ impl ContextImpl { log::trace!("Adding new fonts"); } + let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage; + let mut is_new = false; let fonts = self @@ -624,13 +626,14 @@ impl ContextImpl { Fonts::new( pixels_per_point, max_texture_side, + text_alpha_from_coverage, self.font_definitions.clone(), ) }); { profiling::scope!("Fonts::begin_pass"); - fonts.begin_pass(pixels_per_point, max_texture_side); + fonts.begin_pass(pixels_per_point, max_texture_side, text_alpha_from_coverage); } if is_new && self.memory.options.preload_font_glyphs { @@ -1921,16 +1924,6 @@ impl Context { } } - pub(crate) fn reset_font_atlas(&self) { - let pixels_per_point = self.pixels_per_point(); - let fonts = self.read(|ctx| { - ctx.fonts - .get(&pixels_per_point.into()) - .map(|current_fonts| current_fonts.lock().fonts.definitions().clone()) - }); - self.memory_mut(|mem| mem.new_font_definitions = fonts); - } - /// Tell `egui` which fonts to use. /// /// The default `egui` fonts only support latin and cyrillic alphabets, @@ -2066,19 +2059,10 @@ impl Context { /// You can use [`Ui::style_mut`] to change the style of a single [`Ui`]. pub fn set_style_of(&self, theme: Theme, style: impl Into>) { let style = style.into(); - let mut recreate_font_atlas = false; - self.options_mut(|opt| { - let dest = match theme { - Theme::Dark => &mut opt.dark_style, - Theme::Light => &mut opt.light_style, - }; - recreate_font_atlas = - dest.visuals.text_alpha_from_coverage != style.visuals.text_alpha_from_coverage; - *dest = style; + self.options_mut(|opt| match theme { + Theme::Dark => opt.dark_style = style, + Theme::Light => opt.light_style = style, }); - if recreate_font_atlas { - self.reset_font_atlas(); - } } /// The [`crate::Visuals`] used by all subsequent windows, panels etc. @@ -2475,28 +2459,7 @@ impl ContextImpl { } // Inform the backend of all textures that have been updated (including font atlas). - let textures_delta = { - // HACK to get much nicer looking text in light mode. - // This assumes all text is black-on-white in light mode, - // and white-on-black in dark mode, which is not necessarily true, - // but often close enough. - // Of course this fails for cases when there is black-on-white text in dark mode, - // and white-on-black text in light mode. - - let text_alpha_from_coverage = - self.memory.options.style().visuals.text_alpha_from_coverage; - - let mut textures_delta = self.tex_manager.0.write().take_delta(); - - for (_, delta) in &mut textures_delta.set { - if let ImageData::Font(font) = &mut delta.image { - delta.image = - ImageData::Color(font.to_color_image(text_alpha_from_coverage).into()); - } - } - - textures_delta - }; + let textures_delta = self.tex_manager.0.write().take_delta(); let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); @@ -3094,17 +3057,9 @@ impl Context { options.ui(ui); - let text_alpha_from_coverage_changed = - prev_options.style().visuals.text_alpha_from_coverage - != options.style().visuals.text_alpha_from_coverage; - if options != prev_options { self.options_mut(move |o| *o = options); } - - if text_alpha_from_coverage_changed { - ui.ctx().reset_font_atlas(); - } } fn fonts_tweak_ui(&self, ui: &mut Ui) { diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index b4daa9326..7abc5ba41 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -467,7 +467,7 @@ pub use emath::{ remap_clamp, vec2, }; pub use epaint::{ - ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback, + ClippedPrimitive, ColorImage, CornerRadius, ImageData, Margin, Mesh, PaintCallback, PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index e0c86f0db..02f098e67 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -168,6 +168,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let fonts = egui::epaint::text::Fonts::new( pixels_per_point, max_texture_side, + egui::epaint::AlphaFromCoverage::default(), egui::FontDefinitions::default(), ); { @@ -210,7 +211,11 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut rng = rand::rng(); b.iter(|| { - fonts.begin_pass(pixels_per_point, max_texture_side); + fonts.begin_pass( + pixels_per_point, + max_texture_side, + egui::epaint::AlphaFromCoverage::default(), + ); // Delete a random character, simulating a user making an edit in a long file: let mut new_string = string.clone(); diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index a574cbb71..5833d73ef 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -534,23 +534,6 @@ impl Painter { self.upload_texture_srgb(delta.pos, image.size, delta.options, data); } - egui::ImageData::Font(image) => { - assert_eq!( - image.width() * image.height(), - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - - let data: Vec = { - profiling::scope!("font -> sRGBA"); - image - .srgba_pixels(Default::default()) - .flat_map(|a| a.to_array()) - .collect() - }; - - self.upload_texture_srgb(delta.pos, image.size, delta.options, &data); - } }; } diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 676e1d0fd..d4b10a216 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -1,8 +1,8 @@ use criterion::{Criterion, black_box, criterion_group, criterion_main}; use epaint::{ - ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions, - Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, + AlphaFromCoverage, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, + TessellationOptions, Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, }; #[global_allocator] @@ -66,7 +66,7 @@ fn tessellate_circles(c: &mut Criterion) { let pixels_per_point = 2.0; let options = TessellationOptions::default(); - let atlas = TextureAtlas::new([4096, 256]); + let atlas = TextureAtlas::new([4096, 256], AlphaFromCoverage::default()); let font_tex_size = atlas.size(); let prepared_discs = atlas.prepared_discs(); diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index 6b40714cd..e14ea869e 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -7,24 +7,20 @@ use std::sync::Arc; /// /// To load an image file, see [`ColorImage::from_rgba_unmultiplied`]. /// -/// In order to paint the image on screen, you first need to convert it to +/// This is currently an enum with only one variant, but more image types may be added in the future. /// -/// See also: [`ColorImage`], [`FontImage`]. -#[derive(Clone, PartialEq)] +/// See also: [`ColorImage`]. +#[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ImageData { /// RGBA image. Color(Arc), - - /// Used for the font texture. - Font(FontImage), } impl ImageData { pub fn size(&self) -> [usize; 2] { match self { Self::Color(image) => image.size, - Self::Font(image) => image.size, } } @@ -38,7 +34,7 @@ impl ImageData { pub fn bytes_per_pixel(&self) -> usize { match self { - Self::Color(_) | Self::Font(_) => 4, + Self::Color(_) => 4, } } } @@ -271,6 +267,37 @@ impl ColorImage { } Self::new([width, height], output) } + + /// Clone a sub-region as a new image. + pub fn region_by_pixels(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self { + assert!( + x + w <= self.width(), + "x + w should be <= self.width(), but x: {}, w: {}, width: {}", + x, + w, + self.width() + ); + assert!( + y + h <= self.height(), + "y + h should be <= self.height(), but y: {}, h: {}, height: {}", + y, + h, + self.height() + ); + + let mut pixels = Vec::with_capacity(w * h); + for y in y..y + h { + let offset = y * self.width() + x; + pixels.extend(&self.pixels[offset..(offset + w)]); + } + assert_eq!( + pixels.len(), + w * h, + "pixels.len should be w * h, but got {}", + pixels.len() + ); + Self::new([w, h], pixels) + } } impl std::ops::Index<(usize, usize)> for ColorImage { @@ -371,127 +398,12 @@ impl AlphaFromCoverage { } } -/// A single-channel image designed for the font texture. -/// -/// Each value represents "coverage", i.e. how much a texel is covered by a character. -/// -/// This is roughly interpreted as the opacity of a white image. -#[derive(Clone, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct FontImage { - /// width, height - pub size: [usize; 2], - - /// The coverage value. - /// - /// Often you want to use [`Self::srgba_pixels`] instead. - pub pixels: Vec, -} - -impl FontImage { - pub fn new(size: [usize; 2]) -> Self { - Self { - size, - pixels: vec![0.0; size[0] * size[1]], - } - } - - #[inline] - pub fn width(&self) -> usize { - self.size[0] - } - - #[inline] - pub fn height(&self) -> usize { - self.size[1] - } - - /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom. - #[inline] - pub fn srgba_pixels( - &self, - alpha_from_coverage: AlphaFromCoverage, - ) -> impl ExactSizeIterator + '_ { - self.pixels - .iter() - .map(move |&coverage| alpha_from_coverage.color_from_coverage(coverage)) - } - - /// Convert this coverage image to a [`ColorImage`]. - pub fn to_color_image(&self, alpha_from_coverage: AlphaFromCoverage) -> ColorImage { - profiling::function_scope!(); - let pixels = self.srgba_pixels(alpha_from_coverage).collect(); - ColorImage::new(self.size, pixels) - } - - /// Clone a sub-region as a new image. - pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self { - assert!( - x + w <= self.width(), - "x + w should be <= self.width(), but x: {}, w: {}, width: {}", - x, - w, - self.width() - ); - assert!( - y + h <= self.height(), - "y + h should be <= self.height(), but y: {}, h: {}, height: {}", - y, - h, - self.height() - ); - - let mut pixels = Vec::with_capacity(w * h); - for y in y..y + h { - let offset = y * self.width() + x; - pixels.extend(&self.pixels[offset..(offset + w)]); - } - assert_eq!( - pixels.len(), - w * h, - "pixels.len should be w * h, but got {}", - pixels.len() - ); - Self { - size: [w, h], - pixels, - } - } -} - -impl std::ops::Index<(usize, usize)> for FontImage { - type Output = f32; - - #[inline] - fn index(&self, (x, y): (usize, usize)) -> &f32 { - let [w, h] = self.size; - assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}"); - &self.pixels[y * w + x] - } -} - -impl std::ops::IndexMut<(usize, usize)> for FontImage { - #[inline] - fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 { - let [w, h] = self.size; - assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}"); - &mut self.pixels[y * w + x] - } -} - -impl From for ImageData { - #[inline(always)] - fn from(image: FontImage) -> Self { - Self::Font(image) - } -} - // ---------------------------------------------------------------------------- /// A change to an image. /// /// Either a whole new image, or an update to a rectangular region of it. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[must_use = "The painter must take care of this"] pub struct ImageDelta { diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 7afc7b146..f02889d97 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -50,7 +50,7 @@ pub use self::{ color::ColorMode, corner_radius::CornerRadius, corner_radius_f32::CornerRadiusF32, - image::{AlphaFromCoverage, ColorImage, FontImage, ImageData, ImageDelta}, + image::{AlphaFromCoverage, ColorImage, ImageData, ImageDelta}, margin::Margin, margin_f32::*, mesh::{Mesh, Mesh16, Vertex}, diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index bf9db964b..b366c86cf 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -179,7 +179,12 @@ mod tests { #[test] fn text_bounding_box_under_rotation() { - let fonts = Fonts::new(1.0, 1024, FontDefinitions::default()); + let fonts = Fonts::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let font = FontId::monospace(12.0); let mut t = crate::Shape::text( diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 72fffaad0..dd095c443 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -279,12 +279,13 @@ impl FontImpl { } else { let glyph_pos = { let atlas = &mut self.atlas.lock(); + let text_alpha_from_coverage = atlas.text_alpha_from_coverage; let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height)); glyph.draw(|x, y, v| { if 0.0 < v { let px = glyph_pos.0 + x as usize; let py = glyph_pos.1 + y as usize; - image[(px, py)] = v; + image[(px, py)] = text_alpha_from_coverage.color_from_coverage(v); } }); glyph_pos diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 6e90b5666..5f9006915 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, sync::Arc}; use crate::{ - TextureAtlas, + AlphaFromCoverage, TextureAtlas, mutex::{Mutex, MutexGuard}, text::{ Galley, LayoutJob, LayoutSection, @@ -430,36 +430,56 @@ impl Fonts { pub fn new( pixels_per_point: f32, max_texture_side: usize, + text_alpha_from_coverage: AlphaFromCoverage, definitions: FontDefinitions, ) -> Self { let fonts_and_cache = FontsAndCache { - fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions), + fonts: FontsImpl::new( + pixels_per_point, + max_texture_side, + text_alpha_from_coverage, + definitions, + ), galley_cache: Default::default(), }; Self(Arc::new(Mutex::new(fonts_and_cache))) } /// Call at the start of each frame with the latest known - /// `pixels_per_point` and `max_texture_side`. + /// `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`. /// /// Call after painting the previous frame, but before using [`Fonts`] for the new frame. /// - /// This function will react to changes in `pixels_per_point` and `max_texture_side`, + /// This function will react to changes in `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`, /// as well as notice when the font atlas is getting full, and handle that. - pub fn begin_pass(&self, pixels_per_point: f32, max_texture_side: usize) { + pub fn begin_pass( + &self, + pixels_per_point: f32, + max_texture_side: usize, + text_alpha_from_coverage: AlphaFromCoverage, + ) { let mut fonts_and_cache = self.0.lock(); let pixels_per_point_changed = fonts_and_cache.fonts.pixels_per_point != pixels_per_point; let max_texture_side_changed = fonts_and_cache.fonts.max_texture_side != max_texture_side; + let text_alpha_from_coverage_changed = + fonts_and_cache.fonts.atlas.lock().text_alpha_from_coverage != text_alpha_from_coverage; let font_atlas_almost_full = fonts_and_cache.fonts.atlas.lock().fill_ratio() > 0.8; - let needs_recreate = - pixels_per_point_changed || max_texture_side_changed || font_atlas_almost_full; + let needs_recreate = pixels_per_point_changed + || max_texture_side_changed + || text_alpha_from_coverage_changed + || font_atlas_almost_full; if needs_recreate { let definitions = fonts_and_cache.fonts.definitions.clone(); *fonts_and_cache = FontsAndCache { - fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions), + fonts: FontsImpl::new( + pixels_per_point, + max_texture_side, + text_alpha_from_coverage, + definitions, + ), galley_cache: Default::default(), }; } @@ -497,7 +517,7 @@ impl Fonts { /// The full font atlas image. #[inline] - pub fn image(&self) -> crate::FontImage { + pub fn image(&self) -> crate::ColorImage { self.lock().fonts.atlas.lock().image().clone() } @@ -642,6 +662,7 @@ impl FontsImpl { pub fn new( pixels_per_point: f32, max_texture_side: usize, + text_alpha_from_coverage: AlphaFromCoverage, definitions: FontDefinitions, ) -> Self { assert!( @@ -651,7 +672,7 @@ impl FontsImpl { let texture_width = max_texture_side.at_most(16 * 1024); let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways. - let atlas = TextureAtlas::new([texture_width, initial_height]); + let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage); let atlas = Arc::new(Mutex::new(atlas)); @@ -1120,6 +1141,7 @@ mod tests { let mut fonts = FontsImpl::new( pixels_per_point, max_texture_side, + AlphaFromCoverage::default(), FontDefinitions::default(), ); diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 3777d860c..7915bbf61 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1034,11 +1034,18 @@ fn is_cjk_break_allowed(c: char) -> bool { #[cfg(test)] mod tests { + use crate::AlphaFromCoverage; + use super::{super::*, *}; #[test] fn test_zero_max_width() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default()); layout_job.wrap.max_width = 0.0; let galley = layout(&mut fonts, layout_job.into()); @@ -1049,7 +1056,12 @@ mod tests { fn test_truncate_with_newline() { // No matter where we wrap, we should be appending the newline character. - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let text_format = TextFormat { font_id: FontId::monospace(12.0), ..Default::default() @@ -1094,7 +1106,12 @@ mod tests { #[test] fn test_cjk() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section( "日本語とEnglishの混在した文章".into(), TextFormat::default(), @@ -1109,7 +1126,12 @@ mod tests { #[test] fn test_pre_cjk() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section( "日本語とEnglishの混在した文章".into(), TextFormat::default(), @@ -1124,7 +1146,12 @@ mod tests { #[test] fn test_truncate_width() { - let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default()); + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); let mut layout_job = LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default()); layout_job.wrap.max_width = f32::INFINITY; diff --git a/crates/epaint/src/texture_atlas.rs b/crates/epaint/src/texture_atlas.rs index 8e174e544..36dd1b48e 100644 --- a/crates/epaint/src/texture_atlas.rs +++ b/crates/epaint/src/texture_atlas.rs @@ -1,6 +1,7 @@ +use ecolor::Color32; use emath::{Rect, remap_clamp}; -use crate::{FontImage, ImageDelta}; +use crate::{AlphaFromCoverage, ColorImage, ImageDelta}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct Rectu { @@ -57,7 +58,7 @@ pub struct PreparedDisc { /// More characters can be added, possibly expanding the texture. #[derive(Clone)] pub struct TextureAtlas { - image: FontImage, + image: ColorImage, /// What part of the image that is dirty dirty: Rectu, @@ -72,18 +73,22 @@ pub struct TextureAtlas { /// pre-rasterized discs of radii `2^i`, where `i` is the index. discs: Vec, + + /// Controls how to convert glyph coverage to alpha. + pub(crate) text_alpha_from_coverage: AlphaFromCoverage, } impl TextureAtlas { - pub fn new(size: [usize; 2]) -> Self { + pub fn new(size: [usize; 2], text_alpha_from_coverage: AlphaFromCoverage) -> Self { assert!(size[0] >= 1024, "Tiny texture atlas"); let mut atlas = Self { - image: FontImage::new(size), + image: ColorImage::filled(size, Color32::TRANSPARENT), dirty: Rectu::EVERYTHING, cursor: (0, 0), row_height: 0, overflowed: false, discs: vec![], // will be filled in below + text_alpha_from_coverage, }; // Make the top left pixel fully white for `WHITE_UV`, i.e. painting something with solid color: @@ -93,7 +98,7 @@ impl TextureAtlas { (0, 0), "Expected the first allocation to be at (0, 0), but was at {pos:?}" ); - image[pos] = 1.0; + image[pos] = Color32::WHITE; // Allocate a series of anti-aliased discs used to render small filled circles: // TODO(emilk): these circles can be packed A LOT better. @@ -116,7 +121,7 @@ impl TextureAtlas { let coverage = remap_clamp(distance_to_center, (r - 0.5)..=(r + 0.5), 1.0..=0.0); image[((x as i32 + hw + dx) as usize, (y as i32 + hw + dy) as usize)] = - coverage; + text_alpha_from_coverage.color_from_coverage(coverage); } } atlas.discs.push(PrerasterizedDisc { @@ -184,7 +189,7 @@ impl TextureAtlas { /// The full font atlas image. #[inline] - pub fn image(&self) -> &FontImage { + pub fn image(&self) -> &ColorImage { &self.image } @@ -200,14 +205,14 @@ impl TextureAtlas { } else { let pos = [dirty.min_x, dirty.min_y]; let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y]; - let region = self.image.region(pos, size); + let region = self.image.region_by_pixels(pos, size); Some(ImageDelta::partial(pos, region, texture_options)) } } /// Returns the coordinates of where the rect ended up, /// and invalidates the region. - pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) { + pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut ColorImage) { /// On some low-precision GPUs (my old iPad) characters get muddled up /// if we don't add some empty pixels between the characters. /// On modern high-precision GPUs this is not needed. @@ -254,13 +259,15 @@ impl TextureAtlas { } } -fn resize_to_min_height(image: &mut FontImage, required_height: usize) -> bool { +fn resize_to_min_height(image: &mut ColorImage, required_height: usize) -> bool { while required_height >= image.height() { image.size[1] *= 2; // double the height } if image.width() * image.height() > image.pixels.len() { - image.pixels.resize(image.width() * image.height(), 0.0); + image + .pixels + .resize(image.width() * image.height(), Color32::TRANSPARENT); true } else { false diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index cc191a75f..0944a9052 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -271,7 +271,7 @@ pub enum TextureWrapMode { /// What has been allocated and freed during the last period. /// /// These are commands given to the integration painter. -#[derive(Clone, Default, PartialEq)] +#[derive(Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[must_use = "The painter must take care of this"] pub struct TexturesDelta { From a811b975c24ecdcb0dec50a7195a5312d98385da Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 09:33:08 +0200 Subject: [PATCH 35/56] Better deprecation of SelectableLabel --- crates/egui/src/widgets/selected_label.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/selected_label.rs b/crates/egui/src/widgets/selected_label.rs index da18b5fe0..536ef43da 100644 --- a/crates/egui/src/widgets/selected_label.rs +++ b/crates/egui/src/widgets/selected_label.rs @@ -1,2 +1,13 @@ -#[deprecated = "SelectableLabel has been removed. Use Button::selectable() instead"] +#![expect(deprecated, clippy::new_ret_no_self)] + +use crate::WidgetText; + +#[deprecated = "Use `Button::selectable()` instead"] pub struct SelectableLabel {} + +impl SelectableLabel { + #[deprecated = "Use `Button::selectable()` instead"] + pub fn new(selected: bool, text: impl Into) -> super::Button<'static> { + crate::Button::selectable(selected, text) + } +} From 6d807074222c1f049ffb17082ff6afca8ed0a67a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 12:02:01 +0200 Subject: [PATCH 36/56] Fix tooltips sometimes changing position each frame (#7304) There was a bug in how we decide where to place a `Tooltip` (or other `Popup`), which could lead to tooltips jumping around every frame, especially if it changed size slightly. The new code is simpler and bug-free. --- crates/egui/src/containers/popup.rs | 1 + crates/emath/src/align.rs | 8 +++- crates/emath/src/rect_align.rs | 57 ++++++++++++----------------- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 434747cda..b63825e46 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -484,6 +484,7 @@ impl<'a> Popup<'a> { self.gap, expected_popup_size, ) + .unwrap_or_default() } /// Show the popup. diff --git a/crates/emath/src/align.rs b/crates/emath/src/align.rs index 89b28d4af..a672c456e 100644 --- a/crates/emath/src/align.rs +++ b/crates/emath/src/align.rs @@ -146,7 +146,7 @@ impl Align { // ---------------------------------------------------------------------------- /// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`]. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Align2(pub [Align; 2]); @@ -298,3 +298,9 @@ impl std::ops::IndexMut for Align2 { pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect { Align2::CENTER_CENTER.align_size_within_rect(size, frame) } + +impl std::fmt::Debug for Align2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Align2({:?}, {:?})", self.x(), self.y()) + } +} diff --git a/crates/emath/src/rect_align.rs b/crates/emath/src/rect_align.rs index 5a8102ad1..31580405f 100644 --- a/crates/emath/src/rect_align.rs +++ b/crates/emath/src/rect_align.rs @@ -7,9 +7,9 @@ use crate::{Align2, Pos2, Rect, Vec2}; /// /// There are helper constants for the 12 common menu positions: /// ```text -/// ┌───────────┐ ┌────────┐ ┌─────────┐ -/// │ TOP_START │ │ TOP │ │ TOP_END │ -/// └───────────┘ └────────┘ └─────────┘ +/// ┌───────────┐ ┌────────┐ ┌─────────┐ +/// │ TOP_START │ │ TOP │ │ TOP_END │ +/// └───────────┘ └────────┘ └─────────┘ /// ┌──────────┐ ┌────────────────────────────────────┐ ┌───────────┐ /// │LEFT_START│ │ │ │RIGHT_START│ /// └──────────┘ │ │ └───────────┘ @@ -19,9 +19,9 @@ use crate::{Align2, Pos2, Rect, Vec2}; /// ┌──────────┐ │ │ ┌───────────┐ /// │ LEFT_END │ │ │ │ RIGHT_END │ /// └──────────┘ └────────────────────────────────────┘ └───────────┘ -/// ┌────────────┐ ┌──────┐ ┌──────────┐ -/// │BOTTOM_START│ │BOTTOM│ │BOTTOM_END│ -/// └────────────┘ └──────┘ └──────────┘ +/// ┌────────────┐ ┌──────┐ ┌──────────┐ +/// │BOTTOM_START│ │BOTTOM│ │BOTTOM_END│ +/// └────────────┘ └──────┘ └──────────┘ /// ``` // There is no `new` function on purpose, since writing out `parent` and `child` is more // reasonable. @@ -235,45 +235,34 @@ impl RectAlign { [self.flip_x(), self.flip_y(), self.flip()] } - /// Look for the [`RectAlign`] that fits best in the available space. + /// Look for the first alternative [`RectAlign`] that allows the child rect to fit + /// inside the `screen_rect`. + /// + /// If no alternative fits, the first is returned. + /// If no alternatives are given, `None` is returned. /// /// See also: /// - [`RectAlign::symmetries`] to calculate alternatives /// - [`RectAlign::MENU_ALIGNS`] for the 12 common menu positions pub fn find_best_align( - mut values_to_try: impl Iterator, - available_space: Rect, + values_to_try: impl Iterator, + screen_rect: Rect, parent_rect: Rect, gap: f32, - size: Vec2, - ) -> Self { - let area = size.x * size.y; - - let blocked_area = |pos: Self| { - let rect = pos.align_rect(&parent_rect, size, gap); - area - available_space.intersect(rect).area() - }; - - let first = values_to_try.next().unwrap_or_default(); - - if blocked_area(first) == 0.0 { - return first; - } - - let mut best_area = blocked_area(first); - let mut best = first; + expected_size: Vec2, + ) -> Option { + let mut first_choice = None; for align in values_to_try { - let blocked = blocked_area(align); - if blocked == 0.0 { - return align; - } - if blocked < best_area { - best = align; - best_area = blocked; + first_choice = first_choice.or(Some(align)); // Remember the first alternative + + let suggested_popup_rect = align.align_rect(&parent_rect, expected_size, gap); + + if screen_rect.contains_rect(suggested_popup_rect) { + return Some(align); } } - best + first_choice } } From 3622a03a460c1eb65d1c31dcf947eeddc8515e5b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 12:02:51 +0200 Subject: [PATCH 37/56] Mark `Popup` with `#[must_use]` --- crates/egui/src/containers/popup.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index b63825e46..44dafc62f 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -160,6 +160,7 @@ impl From for UiKind { } } +#[must_use = "Call `.show()` to actually display the popup"] pub struct Popup<'a> { id: Id, ctx: Context, From 93d562221b27e7345e0bbc59fad9dd91c2f2a585 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 12:03:03 +0200 Subject: [PATCH 38/56] Change `Rect::area` to return zero for negative rectangles (#7305) Previously a single-negative rectangle (where `min.x > max.x` XOR `min.y > max.y`) would return a negative area, while a doubly-negative rectangle (`min.x > max.x` AND `min.y > max.y`) would return a positive area. Now both return zero instead. --- crates/emath/src/rect.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 777c12527..8810fe361 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{Div, Mul, Pos2, Rangef, Rot2, Vec2, lerp, pos2, vec2}; +use crate::{Div, Mul, NumExt as _, Pos2, Rangef, Rot2, Vec2, lerp, pos2, vec2}; /// A rectangular region of space. /// @@ -341,11 +341,13 @@ impl Rect { self.max - self.min } + /// Note: this can be negative. #[inline(always)] pub fn width(&self) -> f32 { self.max.x - self.min.x } + /// Note: this can be negative. #[inline(always)] pub fn height(&self) -> f32 { self.max.y - self.min.y @@ -373,9 +375,10 @@ impl Rect { } } + /// This is never negative, and instead returns zero for negative rectangles. #[inline(always)] pub fn area(&self) -> f32 { - self.width() * self.height() + self.width().at_least(0.0) * self.height().at_least(0.0) } /// The distance from the rect to the position. From 933d305159a9e3855cdf5edf34eb98d589ca361b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 12:06:59 +0200 Subject: [PATCH 39/56] Improve doc-string for `Image::alt_text` --- crates/egui/src/widgets/image.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 0ec4e9c90..08b377843 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -278,7 +278,8 @@ impl<'a> Image<'a> { } /// Set alt text for the image. This will be shown when the image fails to load. - /// It will also be read to screen readers. + /// + /// It will also be used for accessibility (e.g. read by screen readers). #[inline] pub fn alt_text(mut self, label: impl Into) -> Self { self.alt_text = Some(label.into()); @@ -672,7 +673,7 @@ pub fn paint_texture_load_result( rect: Rect, show_loading_spinner: Option, options: &ImageOptions, - alt: Option<&str>, + alt_text: Option<&str>, ) { match tlr { Ok(TexturePoll::Ready { texture }) => { @@ -697,9 +698,9 @@ pub fn paint_texture_load_result( 0.0, TextFormat::simple(font_id.clone(), ui.visuals().error_fg_color), ); - if let Some(alt) = alt { + if let Some(alt_text) = alt_text { job.append( - alt, + alt_text, ui.spacing().item_spacing.x, TextFormat::simple(font_id, ui.visuals().text_color()), ); From b11b77e85f5b11bc0180e49773b0e875b57b6784 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 12:07:13 +0200 Subject: [PATCH 40/56] Save a few CPU cycles with earlier early-out from `Popup::show` (#7306) --- crates/egui/src/containers/popup.rs | 52 +++++++++++++++-------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 44dafc62f..b16b61648 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -492,31 +492,11 @@ impl<'a> Popup<'a> { /// Returns `None` if the popup is not open or anchor is `PopupAnchor::Pointer` and there is /// no pointer. pub fn show(self, content: impl FnOnce(&mut Ui) -> R) -> Option> { - let best_align = self.get_best_align(); + let hover_pos = self.ctx.pointer_hover_pos(); - let Popup { - id, - ctx, - anchor, - open_kind, - close_behavior, - kind, - info, - layer_id, - rect_align: _, - alternative_aligns: _, - gap, - widget_clicked_elsewhere, - width, - sense, - layout, - frame, - style, - } = self; - - let hover_pos = ctx.pointer_hover_pos(); - if let OpenKind::Memory { set, .. } = open_kind { - ctx.memory_mut(|mem| match set { + let id = self.id; + if let OpenKind::Memory { set } = self.open_kind { + self.ctx.memory_mut(|mem| match set { Some(SetOpenCommand::Bool(open)) => { if open { match self.anchor { @@ -538,10 +518,32 @@ impl<'a> Popup<'a> { }); } - if !open_kind.is_open(id, &ctx) { + if !self.open_kind.is_open(self.id, &self.ctx) { return None; } + let best_align = self.get_best_align(); + + let Popup { + id, + ctx, + anchor, + open_kind, + close_behavior, + kind, + info, + layer_id, + rect_align: _, + alternative_aligns: _, + gap, + widget_clicked_elsewhere, + width, + sense, + layout, + frame, + style, + } = self; + if kind != PopupKind::Tooltip { ctx.pass_state_mut(|fs| { fs.layers From 09596a5e7b4a18eb0cbcaf07084141187c36d87d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 13:50:53 +0200 Subject: [PATCH 41/56] egui_kittest: more ergonomic functions taking `Impl Into` (#7307) --- crates/egui_demo_app/tests/test_demo_app.rs | 2 +- .../src/demo/demo_app_windows.rs | 2 +- .../src/demo/tests/tessellation_test.rs | 2 +- crates/egui_demo_lib/src/rendering_test.rs | 2 +- crates/egui_kittest/src/snapshot.rs | 42 ++++++++++++------- tests/egui_tests/tests/test_sides.rs | 2 +- tests/egui_tests/tests/test_widgets.rs | 4 +- 7 files changed, 34 insertions(+), 22 deletions(-) diff --git a/crates/egui_demo_app/tests/test_demo_app.rs b/crates/egui_demo_app/tests/test_demo_app.rs index 961990b90..63b3b2a89 100644 --- a/crates/egui_demo_app/tests/test_demo_app.rs +++ b/crates/egui_demo_app/tests/test_demo_app.rs @@ -69,6 +69,6 @@ fn test_demo_app() { // Can't use Harness::run because fractal clock keeps requesting repaints harness.run_steps(4); - results.add(harness.try_snapshot(&anchor.to_string())); + results.add(harness.try_snapshot(anchor.to_string())); } } diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 4414a9572..7f24d7284 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -416,7 +416,7 @@ mod tests { options = options.threshold(OsThreshold::new(0.0).linux(2.1)); } - results.add(harness.try_snapshot_options(&format!("demos/{name}"), &options)); + results.add(harness.try_snapshot_options(format!("demos/{name}"), &options)); } } diff --git a/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs b/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs index c1a325a5f..cb08cf24e 100644 --- a/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs @@ -374,7 +374,7 @@ mod tests { harness.fit_contents(); harness.run(); - harness.snapshot(&format!("tessellation_test/{name}")); + harness.snapshot(format!("tessellation_test/{name}")); } } } diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index e14bfca07..427d0f4ee 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -745,7 +745,7 @@ mod tests { harness.fit_contents(); - results.add(harness.try_snapshot(&format!("rendering_test/dpi_{dpi:.2}"))); + results.add(harness.try_snapshot(format!("rendering_test/dpi_{dpi:.2}"))); } } } diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index e53615d90..8a457ec52 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -310,7 +310,15 @@ fn should_update_snapshots() -> bool { /// reading or writing the snapshot. pub fn try_image_snapshot_options( new: &image::RgbaImage, - name: &str, + name: impl Into, + options: &SnapshotOptions, +) -> SnapshotResult { + try_image_snapshot_options_impl(new, name.into(), options) +} + +fn try_image_snapshot_options_impl( + new: &image::RgbaImage, + name: String, options: &SnapshotOptions, ) -> SnapshotResult { let SnapshotOptions { @@ -319,7 +327,7 @@ pub fn try_image_snapshot_options( failed_pixel_count_threshold, } = options; - let parent_path = if let Some(parent) = PathBuf::from(name).parent() { + let parent_path = if let Some(parent) = PathBuf::from(&name).parent() { output_path.join(parent) } else { output_path.clone() @@ -385,7 +393,7 @@ pub fn try_image_snapshot_options( return update_snapshot(); } else { return Err(SnapshotError::SizeMismatch { - name: name.to_owned(), + name, expected: previous.dimensions(), actual: new.dimensions(), }); @@ -412,7 +420,7 @@ pub fn try_image_snapshot_options( } Err(SnapshotError::Diff { - name: name.to_owned(), + name, diff: num_wrong_pixels, diff_path, }) @@ -435,7 +443,7 @@ pub fn try_image_snapshot_options( /// # Errors /// Returns a [`SnapshotError`] if the image does not match the snapshot or if there was an error /// reading or writing the snapshot. -pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> SnapshotResult { +pub fn try_image_snapshot(current: &image::RgbaImage, name: impl Into) -> SnapshotResult { try_image_snapshot_options(current, name, &SnapshotOptions::default()) } @@ -456,7 +464,11 @@ pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> SnapshotRes /// Panics if the image does not match the snapshot or if there was an error reading or writing the /// snapshot. #[track_caller] -pub fn image_snapshot_options(current: &image::RgbaImage, name: &str, options: &SnapshotOptions) { +pub fn image_snapshot_options( + current: &image::RgbaImage, + name: impl Into, + options: &SnapshotOptions, +) { match try_image_snapshot_options(current, name, options) { Ok(_) => {} Err(err) => { @@ -475,7 +487,7 @@ pub fn image_snapshot_options(current: &image::RgbaImage, name: &str, options: & /// Panics if the image does not match the snapshot or if there was an error reading or writing the /// snapshot. #[track_caller] -pub fn image_snapshot(current: &image::RgbaImage, name: &str) { +pub fn image_snapshot(current: &image::RgbaImage, name: impl Into) { match try_image_snapshot(current, name) { Ok(_) => {} Err(err) => { @@ -506,13 +518,13 @@ impl Harness<'_, State> { /// error reading or writing the snapshot, if the rendering fails or if no default renderer is available. pub fn try_snapshot_options( &mut self, - name: &str, + name: impl Into, options: &SnapshotOptions, ) -> SnapshotResult { let image = self .render() .map_err(|err| SnapshotError::RenderError { err })?; - try_image_snapshot_options(&image, name, options) + try_image_snapshot_options(&image, name.into(), options) } /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot. @@ -523,7 +535,7 @@ impl Harness<'_, State> { /// # Errors /// Returns a [`SnapshotError`] if the image does not match the snapshot, if there was an /// error reading or writing the snapshot, if the rendering fails or if no default renderer is available. - pub fn try_snapshot(&mut self, name: &str) -> SnapshotResult { + pub fn try_snapshot(&mut self, name: impl Into) -> SnapshotResult { let image = self .render() .map_err(|err| SnapshotError::RenderError { err })?; @@ -549,7 +561,7 @@ impl Harness<'_, State> { /// Panics if the image does not match the snapshot, if there was an error reading or writing the /// snapshot, if the rendering fails or if no default renderer is available. #[track_caller] - pub fn snapshot_options(&mut self, name: &str, options: &SnapshotOptions) { + pub fn snapshot_options(&mut self, name: impl Into, options: &SnapshotOptions) { match self.try_snapshot_options(name, options) { Ok(_) => {} Err(err) => { @@ -567,7 +579,7 @@ impl Harness<'_, State> { /// Panics if the image does not match the snapshot, if there was an error reading or writing the /// snapshot, if the rendering fails or if no default renderer is available. #[track_caller] - pub fn snapshot(&mut self, name: &str) { + pub fn snapshot(&mut self, name: impl Into) { match self.try_snapshot(name) { Ok(_) => {} Err(err) => { @@ -588,7 +600,7 @@ impl Harness<'_, State> { )] pub fn try_wgpu_snapshot_options( &mut self, - name: &str, + name: impl Into, options: &SnapshotOptions, ) -> SnapshotResult { self.try_snapshot_options(name, options) @@ -598,7 +610,7 @@ impl Harness<'_, State> { since = "0.31.0", note = "Use `try_snapshot` instead. This function will be removed in 0.32" )] - pub fn try_wgpu_snapshot(&mut self, name: &str) -> SnapshotResult { + pub fn try_wgpu_snapshot(&mut self, name: impl Into) -> SnapshotResult { self.try_snapshot(name) } @@ -606,7 +618,7 @@ impl Harness<'_, State> { since = "0.31.0", note = "Use `snapshot_options` instead. This function will be removed in 0.32" )] - pub fn wgpu_snapshot_options(&mut self, name: &str, options: &SnapshotOptions) { + pub fn wgpu_snapshot_options(&mut self, name: impl Into, options: &SnapshotOptions) { self.snapshot_options(name, options); } diff --git a/tests/egui_tests/tests/test_sides.rs b/tests/egui_tests/tests/test_sides.rs index 293abd311..52d35db4c 100644 --- a/tests/egui_tests/tests/test_sides.rs +++ b/tests/egui_tests/tests/test_sides.rs @@ -71,6 +71,6 @@ fn test_variants( harness.fit_contents(); } - results.add(harness.try_snapshot(&format!("sides/{name}_{variant_name}"))); + results.add(harness.try_snapshot(format!("sides/{name}_{variant_name}"))); } } diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs index 4ec317521..a6050e95b 100644 --- a/tests/egui_tests/tests/test_widgets.rs +++ b/tests/egui_tests/tests/test_widgets.rs @@ -244,7 +244,7 @@ fn test_widget_layout(name: &str, mut w: impl FnMut(&mut Ui) -> Response) -> Sna }); harness.fit_contents(); - harness.try_snapshot(&format!("layout/{name}")) + harness.try_snapshot(format!("layout/{name}")) } /// Utility to create a snapshot test of the different states of a egui widget. @@ -370,7 +370,7 @@ impl<'a> VisualTests<'a> { harness.fit_contents(); - harness.try_snapshot(&format!("visuals/{}", self.name)) + harness.try_snapshot(format!("visuals/{}", self.name)) } } From dd1052108ee5775d5730e346e88f933869a62463 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jul 2025 13:58:22 +0200 Subject: [PATCH 42/56] Add snapshot test for image blending (#7309) This adds a test that can be used to see the improvements made by this PR (if any): * https://github.com/emilk/egui/pull/5839 --- crates/egui_demo_lib/data/ring.png | Bin 0 -> 507 bytes crates/egui_demo_lib/tests/image_blending.rs | 25 ++++++++++++++++++ .../snapshots/image_blending/image_x1.png | 3 +++ .../snapshots/image_blending/image_x2.png | 3 +++ 4 files changed, 31 insertions(+) create mode 100644 crates/egui_demo_lib/data/ring.png create mode 100644 crates/egui_demo_lib/tests/image_blending.rs create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_x1.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_x2.png diff --git a/crates/egui_demo_lib/data/ring.png b/crates/egui_demo_lib/data/ring.png new file mode 100644 index 0000000000000000000000000000000000000000..f82db91963b9888718c91603837ea40377b3f6d4 GIT binary patch literal 507 zcmVVK~#7F%~p$T zgfI*>rw+gd%?OOZ2pxe<+6|5kxD9Ru=my;YIsvzVYfFwWh7Haketx#&BnEgg z3d>|fbo8lJ*Mnp#isA>(0+WzjNoEXVBB~eD zNkekuF$lx!T;^=u4ne}IFX|o>Nu#!_+758m-5~?UW6|-E+Z$+w)b_N*$-=jn8oa_u zz|`%9%iUMQL8$ZB#wI=YdD@v=gPp848;$D{qsv){>l(-&AyLq(GuL6bh`Qra)n(N{ xF=AgFjTpu(L@uSzS_u1ROnWfU)i%Ma;t$Q!*^gw- Date: Mon, 7 Jul 2025 17:46:27 +0200 Subject: [PATCH 43/56] Improve texture filtering by doing it in gamma space (#7311) * Closes https://github.com/emilk/egui/pull/5839 This makes some transparent images look a lot nicer when blended: ![image](https://github.com/user-attachments/assets/7f370aaf-886a-423c-8391-c378849b63ca) Cursive text will also look nicer. This unfortunately changes the contract of what `register_native_texture` expects --------- Co-authored-by: Adrian Blumer --- crates/egui-wgpu/src/egui.wgsl | 10 ++++------ crates/egui-wgpu/src/renderer.rs | 8 ++++---- crates/egui/src/lib.rs | 10 ++++------ .../tests/snapshots/easymarkeditor.png | 4 ++-- .../tests/snapshots/imageviewer.png | 4 ++-- crates/egui_demo_lib/src/rendering_test.rs | 16 +++++++++------- .../tests/snapshots/demos/Bézier Curve.png | 4 ++-- .../tests/snapshots/demos/Clipboard Test.png | 4 ++-- .../tests/snapshots/demos/Scene.png | 4 ++-- .../tests/snapshots/demos/Tessellation Test.png | 4 ++-- .../tests/snapshots/image_blending/image_x1.png | 4 ++-- .../tests/snapshots/image_blending/image_x2.png | 4 ++-- .../tests/snapshots/rendering_test/dpi_1.00.png | 4 ++-- .../tests/snapshots/rendering_test/dpi_1.25.png | 4 ++-- .../tests/snapshots/rendering_test/dpi_1.50.png | 4 ++-- .../tests/snapshots/rendering_test/dpi_1.67.png | 4 ++-- .../tests/snapshots/rendering_test/dpi_1.75.png | 4 ++-- .../tests/snapshots/rendering_test/dpi_2.00.png | 4 ++-- .../tessellation_test/Additive rectangle.png | 4 ++-- .../tessellation_test/Blurred stroke.png | 4 ++-- .../snapshots/tessellation_test/Blurred.png | 4 ++-- .../tessellation_test/Minimal rounding.png | 4 ++-- .../snapshots/tessellation_test/Normal.png | 4 ++-- .../Thick stroke, minimal rounding.png | 4 ++-- .../snapshots/tessellation_test/Thin filled.png | 4 ++-- .../tessellation_test/Thin stroked.png | 4 ++-- .../tests/snapshots/widget_gallery_dark_x1.png | 4 ++-- .../tests/snapshots/widget_gallery_dark_x2.png | 4 ++-- .../tests/snapshots/widget_gallery_light_x1.png | 4 ++-- .../tests/snapshots/widget_gallery_light_x2.png | 4 ++-- crates/egui_glow/src/painter.rs | 10 ++-------- crates/egui_glow/src/shader/fragment.glsl | 17 ----------------- 32 files changed, 75 insertions(+), 100 deletions(-) diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index b60d9de9e..2921be74f 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -97,9 +97,8 @@ fn vs_main( @fragment fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { - // We always have an sRGB aware texture at the moment. - let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); - let tex_gamma = gamma_from_linear_rgba(tex_linear); + // We expect "normal" textures that are NOT sRGB-aware. + let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); var out_color_gamma = in.color * tex_gamma; // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. @@ -115,9 +114,8 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { @fragment fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { - // We always have an sRGB aware texture at the moment. - let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); - let tex_gamma = gamma_from_linear_rgba(tex_linear); + // We expect "normal" textures that are NOT sRGB-aware. + let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); var out_color_gamma = in.color * tex_gamma; // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 41a9b3b78..012613aeb 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -629,9 +629,9 @@ impl Renderer { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, // Minspec for wgpu WebGL emulation is WebGL2, so this should always be supported. + format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], + view_formats: &[wgpu::TextureFormat::Rgba8Unorm], }) }; let origin = wgpu::Origin3d::ZERO; @@ -690,7 +690,7 @@ impl Renderer { /// /// This enables the application to reference the texture inside an image ui element. /// This effectively enables off-screen rendering inside the egui UI. Texture must have - /// the texture format [`wgpu::TextureFormat::Rgba8UnormSrgb`]. + /// the texture format [`wgpu::TextureFormat::Rgba8Unorm`]. pub fn register_native_texture( &mut self, device: &wgpu::Device, @@ -738,7 +738,7 @@ impl Renderer { /// This allows applications to specify individual minification/magnification filters as well as /// custom mipmap and tiling options. /// - /// The texture must have the format [`wgpu::TextureFormat::Rgba8UnormSrgb`]. + /// The texture must have the format [`wgpu::TextureFormat::Rgba8Unorm`]. /// Any compare function supplied in the [`wgpu::SamplerDescriptor`] will be ignored. #[expect(clippy::needless_pass_by_value)] // false positive pub fn register_native_texture_with_sampler_options( diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 7abc5ba41..9c4686bde 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -161,12 +161,10 @@ //! //! * egui uses premultiplied alpha, so make sure your blending function is `(ONE, ONE_MINUS_SRC_ALPHA)`. //! * Make sure your texture sampler is clamped (`GL_CLAMP_TO_EDGE`). -//! * egui prefers linear color spaces for all blending so: -//! * Use an sRGBA-aware texture if available (e.g. `GL_SRGB8_ALPHA8`). -//! * Otherwise: remember to decode gamma in the fragment shader. -//! * Decode the gamma of the incoming vertex colors in your vertex shader. -//! * Turn on sRGBA/linear framebuffer if available (`GL_FRAMEBUFFER_SRGB`). -//! * Otherwise: gamma-encode the colors before you write them again. +//! * egui prefers gamma color spaces for all blending so: +//! * Do NOT use an sRGBA-aware texture (NOT `GL_SRGB8_ALPHA8`). +//! * Multiply texture and vertex colors in gamma space +//! * Turn OFF sRGBA/gamma framebuffer (NO `GL_FRAMEBUFFER_SRGB`). //! //! //! # Understanding immediate mode diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index 6a1d0290a..f06e16cba 100644 --- a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png +++ b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f62d5375ff784e333e01a31b84d9caadf2dcbd2b19647a08977dab6550b48828 -size 179654 +oid sha256:fc3dbdcd483d4da7a9c1a00f0245a7882997fbcd2d26f8d6a6d2d855f3382063 +size 179724 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index a13af2e71..e6f108e98 100644 --- a/crates/egui_demo_app/tests/snapshots/imageviewer.png +++ b/crates/egui_demo_app/tests/snapshots/imageviewer.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e37b3ce49c9ccc1a64beb58b176e23ab6c1fa2d897f676b0de85e510e6bfa85 -size 100845 +oid sha256:c8ad2c2d494e2287b878049091688069e4d86b69ae72b89cb7ecbe47d8c35e33 +size 100766 diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index 427d0f4ee..e6f8b2c2d 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -159,7 +159,7 @@ impl ColorTest { ui.separator(); // TODO(emilk): test color multiplication (image tint), - // to make sure vertex and texture color multiplication is done in linear space. + // to make sure vertex and texture color multiplication is done in gamma space. ui.label("Gamma interpolation:"); self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Gamma); @@ -191,8 +191,8 @@ impl ColorTest { ui.separator(); - ui.label("Linear interpolation (texture sampling):"); - self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Linear); + ui.label("Texture interpolation (texture sampling) should be in gamma space:"); + self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Gamma); } fn show_gradients( @@ -245,11 +245,10 @@ impl ColorTest { let g = Gradient::endpoints(left, right); match interpolation { - Interpolation::Linear => { - // texture sampler is sRGBA aware, and should therefore be linear - self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g); - } + Interpolation::Linear => {} Interpolation::Gamma => { + self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g); + // vertex shader uses gamma self.vertex_gradient( ui, @@ -330,7 +329,10 @@ fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Respon #[derive(Clone, Copy)] enum Interpolation { + /// egui used to want Linear interpolation for some things, but now we're always in gamma space. + #[expect(unused)] Linear, + Gamma, } diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png b/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png index 8bceea77e..0bf7d928c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbe9f58cce2466360b4b93b03afaaee36711b3017ddff1b2b56bfe49ea91a076 -size 31306 +oid sha256:13262df01a7f2cd5655b8b0bb9379ae02a851c877314375f047a7d749908125c +size 31368 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png index ec9510008..449c88683 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4f807098e0bc56eaacabb76d646a76036cc66a7a6e54b1c934fa9fecb5b0170 -size 26470 +oid sha256:27d5aa7b7e6bd5f59c1765e98ca4588545284456e4cc255799ea797950e09850 +size 26461 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index f5bb0ffd1..760c84e8f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdf3535530c1abb1262383ff9a3f2a740ad2c62ccec33ec5fb435be11625d139 -size 35125 +oid sha256:aabc0e3821a2d9b21708e9b8d9be02ad55055ccabe719a93af921dba2384b4b3 +size 34297 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png index 6f3ca31d5..462a40ad9 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16dc96246f011c6e9304409af7b4084f28e20cd813e44abca73834386e98b9b1 -size 70373 +oid sha256:a3f8873c9cfb80ddeb1ccc0fa04c1c84ea936db1685238f5d29ee6e89f55e457 +size 68814 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_x1.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_x1.png index e53ea7352..7ef5676bb 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_blending/image_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae04cea447427982f1d68bb2250563aaa3be137a77f6dd3f253da77c194c84cf -size 812 +oid sha256:e057c0bba4ec4c30e890c39153bd6dd17c511f410bfb894e66ef3ef9973d8fd4 +size 807 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_x2.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_x2.png index 25ce3ca22..89fad98f9 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_blending/image_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff053e309e6ae38a4b6fe1dd58f1255116fffab6182ce5f77b6360b00cf2af47 -size 2067 +oid sha256:c8b573f58a41efe26a0bf335e27cc123ffd4c13b24576e46d96ddedfed68b606 +size 2027 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png index 51b8d8540..200de9835 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f6cf5b14056522d06f0cb1e56bafd7e5ab7a9033eb358748d43d748bb0ceef1 -size 553177 +oid sha256:39bd11647241521c0ad5c7163a1af4f1aa86792018657091a2d47bb7f2c48b47 +size 598408 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png index 3e73d0abb..ea9298ad6 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd3bd1f64995db34a14dbc860ae8b8e269073ed7b8f10d10ce8f99b613cfc999 -size 769357 +oid sha256:080a59163ab60d60738cfab5afac7cfbddfb585d8a0ee7d03540924b458badea +size 833822 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png index 4b9a5194e..86ec338c7 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f12e6145f3a1c3fda6dede3daeb0e52ed2bffb35531d823133224a477798a14a -size 907800 +oid sha256:216d3d028f48f4bfbd6aca0a25500655d4eb4d5581a387397ed0b048d57fc0c3 +size 984737 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png index c5f324368..3b239324a 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05bdcfd2c34b6d7badede14f5495dce34e5e9cfe421314f40dcea15e9f865736 -size 1024735 +oid sha256:399fc3d64e7ff637425effe6c80d684be1cf8bb9b555458352d0ae6c62bddb5a +size 1109505 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png index 8e4481d06..8d4a1b365 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8365c89f6b823f01464a9310bab7717bf25305b335cdeecf21711c7dca9f053f -size 1140082 +oid sha256:30ce4874a1adb8acf8c8e404f3e19e683eca3133cdef15befbc50d6c09618094 +size 1241773 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png index de8c8b321..854ee6b29 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b38021057ec6b5bb39c41bd4afaf5e9ff38687216d52d5bba8cbf7b6fdfe9a4f -size 1291518 +oid sha256:135fbe5f4ee485ee671931b64c16410b9073d40bedb23dc2545fc863448e8c63 +size 1398091 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Additive rectangle.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Additive rectangle.png index 2fdbaff3d..852bc6bb2 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Additive rectangle.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Additive rectangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ac90da596084a880487035b276177e98d711854143373d59860f01733b1c0cd -size 45592 +oid sha256:1b0fe7aa33506c59142aff764c6b229e8c55b35c8038312b22ea2659987a081a +size 45578 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred stroke.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred stroke.png index 5eb8bf536..49ba9ad07 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred stroke.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred stroke.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e412d424aac7b9cbdfdb8e36bd598e6cbc77183da7733c94c5f20e70699b8b4a -size 87263 +oid sha256:3a3512ea7235640db79e86aa84039621784270201c9213c910f15e7451e5600b +size 87336 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred.png index e9e1a078d..6130a530e 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Blurred.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:222a32da21c69ee46e847e29fb05fd5e1d2de6bb7a22358549bc426f8243fdcb -size 119671 +oid sha256:dc4918a534f26b72d42ef20221e26c0f91a0252870a1987c1fe4cc4aa3c74872 +size 119406 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Minimal rounding.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Minimal rounding.png index a08a658eb..7969d6bee 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Minimal rounding.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Minimal rounding.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d42e11f50a9522dd5ae73e8f8336bfb01493751705055a63abea3f5258f7c9c1 -size 51626 +oid sha256:71182570a65693839fd7cd7390025731ab3f3f88ab55bc67d8be6466fe5a2c11 +size 51843 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png index 9d19dbc9b..49141c40d 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Normal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c33617dfde24071fa65aff2543f22883f5526152fb344997b1877aeb38df72fe -size 54848 +oid sha256:a0dc0294f990730da34fcbbc53f44280306ec6179826c20a6c9ee946e1148b61 +size 55042 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thick stroke, minimal rounding.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thick stroke, minimal rounding.png index 7816cfdb0..e8f61ae77 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thick stroke, minimal rounding.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thick stroke, minimal rounding.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbf40a1f56a6e280002719c6556fe477c93fa7fe88d398372ed36efaa1b83a62 -size 55282 +oid sha256:3004adfe5a864bdc768ceb42d1f09b6debcaf5413f8fea4d36b0aff99e4584f9 +size 55511 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin filled.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin filled.png index 6005e865a..139648c38 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin filled.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin filled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33621731155ebb463fb01ea41ab20272885250efcd7d5c7683c10936b296e14d -size 36446 +oid sha256:b99360833f59a212a965a13d52485ab8ad0e6420b9288b2d6936507067c22a85 +size 36395 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin stroked.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin stroked.png index 713e01fcc..10ad7603b 100644 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin stroked.png +++ b/crates/egui_demo_lib/tests/snapshots/tessellation_test/Thin stroked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:186bd8a3146ad8f1977955e3f7fa593877ad1bf1e8376d32f446c67f36a2aafe -size 36493 +oid sha256:82aa004f668f0ac6b493717b4bff8436ccc1e991c7fb3fcde5b5f3a123c06b9f +size 36428 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png index d607894d4..9cd2d630e 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62df72fd7e2404c4aa482f09eff5103ee28e8afc42ee8c8c74307a246f64cda6 -size 64651 +oid sha256:7e21bb01ae6e4226402a97b7086b49604cdde6b41a6770199df68dc940cd9a45 +size 64748 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x2.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x2.png index ffb00ce22..f881f639c 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_dark_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f5a7397601cb718d5529842a428d2d328d4fe3d1a9cf1a3ca6d583d8525f75e -size 153190 +oid sha256:0626bc45888ad250bf4b49c7f7f462a93ab91e3a2817fd7d0902411043c97132 +size 153289 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png index 948766c97..5b88cc531 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34d85b6015112ea2733f7246f8daabfb9d983523e187339e4d26bfc1f3a3bba3 -size 59460 +oid sha256:919a82c95468300bcd09471eb31d53d25d50cdcb02c27ddbc759d24e65da92b6 +size 59398 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png index 150365d5f..a1971cad6 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery_light_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f51d75010cd1213daa6a1282d352655e64b69da7bca478011ea055a2e5349bc -size 146500 +oid sha256:a55e39a640b0e2cc992286a86dcf38460f1abcc7b964df9022549ca1a94c4df5 +size 146408 diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 5833d73ef..98fe25a45 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -172,12 +172,7 @@ impl Painter { let supported_extensions = gl.supported_extensions(); log::trace!("OpenGL extensions: {supported_extensions:?}"); - let srgb_textures = shader_version == ShaderVersion::Es300 // WebGL2 always support sRGB - || supported_extensions.iter().any(|extension| { - // EXT_sRGB, GL_ARB_framebuffer_sRGB, GL_EXT_sRGB, GL_EXT_texture_sRGB_decode, … - extension.contains("sRGB") - }); - log::debug!("SRGB texture Support: {:?}", srgb_textures); + let srgb_textures = false; // egui wants normal sRGB-unaware textures let supports_srgb_framebuffer = !cfg!(target_arch = "wasm32") && supported_extensions.iter().any(|extension| { @@ -202,11 +197,10 @@ impl Painter { &gl, glow::FRAGMENT_SHADER, &format!( - "{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n#define SRGB_TEXTURES {}\n{}\n{}", + "{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n{}\n{}", shader_version_declaration, shader_version.is_new_shader_interface() as i32, dithering as i32, - srgb_textures as i32, shader_prefix, FRAG_SRC ), diff --git a/crates/egui_glow/src/shader/fragment.glsl b/crates/egui_glow/src/shader/fragment.glsl index f2792ed04..07a931b53 100644 --- a/crates/egui_glow/src/shader/fragment.glsl +++ b/crates/egui_glow/src/shader/fragment.glsl @@ -43,25 +43,8 @@ vec3 dither_interleaved(vec3 rgb, float levels) { return rgb + noise / (levels - 1.0); } -// 0-1 sRGB gamma from 0-1 linear -vec3 srgb_gamma_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(12.92); - vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-1 sRGBA gamma from 0-1 linear -vec4 srgba_gamma_from_linear(vec4 rgba) { - return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a); -} - void main() { -#if SRGB_TEXTURES - vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc)); -#else vec4 texture_in_gamma = texture2D(u_sampler, v_tc); -#endif // We multiply the colors in gamma space, because that's the only way to get text to look right. vec4 frag_color_gamma = v_rgba_in_gamma * texture_in_gamma; From 508c60b2e2f2a0e63e911529dd71f2d995a9c50f Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 9 Jul 2025 08:19:04 +0200 Subject: [PATCH 44/56] Add `Galley::intrinsic_size` and use it in `AtomLayout` (#7146) - part of https://github.com/emilk/egui/issues/5762 - also allows me to simplify sizing logic in egui_flex --- crates/egui/src/atomics/atom.rs | 4 +- crates/egui/src/atomics/atom_kind.rs | 9 ++-- crates/egui/src/atomics/atom_layout.rs | 18 +++---- crates/egui/src/atomics/sized_atom.rs | 4 +- crates/epaint/src/shapes/text_shape.rs | 2 + crates/epaint/src/text/fonts.rs | 57 +++++++++++++++++++++ crates/epaint/src/text/text_layout.rs | 47 ++++++++++++++++- crates/epaint/src/text/text_layout_types.rs | 12 +++++ tests/egui_tests/tests/test_atoms.rs | 32 ++++++++++++ 9 files changed, 166 insertions(+), 19 deletions(-) diff --git a/crates/egui/src/atomics/atom.rs b/crates/egui/src/atomics/atom.rs index 4f4b5b750..ee5ff30d4 100644 --- a/crates/egui/src/atomics/atom.rs +++ b/crates/egui/src/atomics/atom.rs @@ -81,7 +81,7 @@ impl<'a> Atom<'a> { wrap_mode = Some(TextWrapMode::Truncate); } - let (preferred, kind) = self.kind.into_sized(ui, available_size, wrap_mode); + let (intrinsic, kind) = self.kind.into_sized(ui, available_size, wrap_mode); let size = self .size @@ -89,7 +89,7 @@ impl<'a> Atom<'a> { SizedAtom { size, - preferred_size: preferred, + intrinsic_size: intrinsic.at_least(self.size.unwrap_or_default()), grow: self.grow, kind, } diff --git a/crates/egui/src/atomics/atom_kind.rs b/crates/egui/src/atomics/atom_kind.rs index 2672e646b..b85b504a2 100644 --- a/crates/egui/src/atomics/atom_kind.rs +++ b/crates/egui/src/atomics/atom_kind.rs @@ -81,11 +81,10 @@ impl<'a> AtomKind<'a> { ) -> (Vec2, SizedAtomKind<'a>) { match self { AtomKind::Text(text) => { - let galley = text.into_galley(ui, wrap_mode, available_size.x, TextStyle::Button); - ( - galley.size(), // TODO(#5762): calculate the preferred size - SizedAtomKind::Text(galley), - ) + let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); + let galley = + text.into_galley(ui, Some(wrap_mode), available_size.x, TextStyle::Button); + (galley.intrinsic_size, SizedAtomKind::Text(galley)) } AtomKind::Image(image) => { let size = image.load_and_calc_size(ui, available_size); diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs index a25a4b7c6..53819fbb0 100644 --- a/crates/egui/src/atomics/atom_layout.rs +++ b/crates/egui/src/atomics/atom_layout.rs @@ -183,10 +183,10 @@ impl<'a> AtomLayout<'a> { let mut desired_width = 0.0; - // Preferred width / height is the ideal size of the widget, e.g. the size where the + // intrinsic width / height is the ideal size of the widget, e.g. the size where the // text is not wrapped. Used to set Response::intrinsic_size. - let mut preferred_width = 0.0; - let mut preferred_height = 0.0; + let mut intrinsic_width = 0.0; + let mut intrinsic_height = 0.0; let mut height: f32 = 0.0; @@ -203,7 +203,7 @@ impl<'a> AtomLayout<'a> { if atoms.len() > 1 { let gap_space = gap * (atoms.len() as f32 - 1.0); desired_width += gap_space; - preferred_width += gap_space; + intrinsic_width += gap_space; } for (idx, item) in atoms.into_iter().enumerate() { @@ -224,10 +224,10 @@ impl<'a> AtomLayout<'a> { let size = sized.size; desired_width += size.x; - preferred_width += sized.preferred_size.x; + intrinsic_width += sized.intrinsic_size.x; height = height.at_least(size.y); - preferred_height = preferred_height.at_least(sized.preferred_size.y); + intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y); sized_items.push(sized); } @@ -243,10 +243,10 @@ impl<'a> AtomLayout<'a> { let size = sized.size; desired_width += size.x; - preferred_width += sized.preferred_size.x; + intrinsic_width += sized.intrinsic_size.x; height = height.at_least(size.y); - preferred_height = preferred_height.at_least(sized.preferred_size.y); + intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y); sized_items.insert(index, sized); } @@ -259,7 +259,7 @@ impl<'a> AtomLayout<'a> { let mut response = ui.interact(rect, id, sense); response.intrinsic_size = - Some((Vec2::new(preferred_width, preferred_height) + margin.sum()).at_least(min_size)); + Some((Vec2::new(intrinsic_width, intrinsic_height) + margin.sum()).at_least(min_size)); AllocatedAtomLayout { sized_atoms: sized_items, diff --git a/crates/egui/src/atomics/sized_atom.rs b/crates/egui/src/atomics/sized_atom.rs index 50fa443a9..f1ae0f81b 100644 --- a/crates/egui/src/atomics/sized_atom.rs +++ b/crates/egui/src/atomics/sized_atom.rs @@ -12,8 +12,8 @@ pub struct SizedAtom<'a> { /// size.x + gap. pub size: Vec2, - /// Preferred size of the atom. This is used to calculate `Response::intrinsic_size`. - pub preferred_size: Vec2, + /// Intrinsic size of the atom. This is used to calculate `Response::intrinsic_size`. + pub intrinsic_size: Vec2, pub kind: SizedAtomKind<'a>, } diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index b366c86cf..9505dc49b 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -130,10 +130,12 @@ impl TextShape { num_vertices: _, num_indices: _, pixels_per_point: _, + intrinsic_size, } = Arc::make_mut(galley); *rect = transform.scaling * *rect; *mesh_bounds = transform.scaling * *mesh_bounds; + *intrinsic_size = transform.scaling * *intrinsic_size; for text::PlacedRow { pos, row } in rows { *pos *= transform.scaling; diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 5f9006915..a08f3206e 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -1072,6 +1072,7 @@ mod tests { use core::f32; use super::*; + use crate::text::{TextWrapping, layout}; use crate::{Stroke, text::TextFormat}; use ecolor::Color32; use emath::Align; @@ -1183,4 +1184,60 @@ mod tests { } } } + + #[test] + fn test_intrinsic_size() { + let pixels_per_point = [1.0, 1.3, 2.0, 0.867]; + let max_widths = [40.0, 80.0, 133.0, 200.0]; + let rounded_output_to_gui = [false, true]; + + for pixels_per_point in pixels_per_point { + let mut fonts = FontsImpl::new( + pixels_per_point, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); + + for &max_width in &max_widths { + for round_output_to_gui in rounded_output_to_gui { + for mut job in jobs() { + job.wrap = TextWrapping::wrap_at_width(max_width); + + job.round_output_to_gui = round_output_to_gui; + + let galley_wrapped = layout(&mut fonts, job.clone().into()); + + job.wrap = TextWrapping::no_max_width(); + + let text = job.text.clone(); + let galley_unwrapped = layout(&mut fonts, job.into()); + + let intrinsic_size = galley_wrapped.intrinsic_size; + let unwrapped_size = galley_unwrapped.size(); + + let difference = (intrinsic_size - unwrapped_size).length().abs(); + similar_asserts::assert_eq!( + format!("{intrinsic_size:.4?}"), + format!("{unwrapped_size:.4?}"), + "Wrapped intrinsic size should almost match unwrapped size. Intrinsic: {intrinsic_size:.8?} vs unwrapped: {unwrapped_size:.8?} + Difference: {difference:.8?} + wrapped rows: {}, unwrapped rows: {} + pixels_per_point: {pixels_per_point}, text: {text:?}, max_width: {max_width}, round_output_to_gui: {round_output_to_gui}", + galley_wrapped.rows.len(), + galley_unwrapped.rows.len() + ); + similar_asserts::assert_eq!( + format!("{intrinsic_size:.4?}"), + format!("{unwrapped_size:.4?}"), + "Unwrapped galley intrinsic size should exactly match its size. \ + {:.8?} vs {:8?}", + galley_unwrapped.intrinsic_size, + galley_unwrapped.size(), + ); + } + } + } + } + } } diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 7915bbf61..6dc0aa03f 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -82,6 +82,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { num_indices: 0, pixels_per_point: fonts.pixels_per_point(), elided: true, + intrinsic_size: Vec2::ZERO, }; } @@ -94,6 +95,8 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { let point_scale = PointScale::new(fonts.pixels_per_point()); + let intrinsic_size = calculate_intrinsic_size(point_scale, &job, ¶graphs); + let mut elided = false; let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided); if elided { @@ -124,7 +127,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { } // Calculate the Y positions and tessellate the text: - galley_from_rows(point_scale, job, rows, elided) + galley_from_rows(point_scale, job, rows, elided, intrinsic_size) } // Ignores the Y coordinate. @@ -190,6 +193,46 @@ fn layout_section( } } +/// Calculate the intrinsic size of the text. +/// +/// The result is eventually passed to `Response::intrinsic_size`. +/// This works by calculating the size of each `Paragraph` (instead of each `Row`). +fn calculate_intrinsic_size( + point_scale: PointScale, + job: &LayoutJob, + paragraphs: &[Paragraph], +) -> Vec2 { + let mut intrinsic_size = Vec2::ZERO; + for (idx, paragraph) in paragraphs.iter().enumerate() { + if paragraph.glyphs.is_empty() { + if idx == 0 { + intrinsic_size.y += point_scale.round_to_pixel(paragraph.empty_paragraph_height); + } + continue; + } + intrinsic_size.x = f32::max( + paragraph + .glyphs + .last() + .map(|l| l.max_x()) + .unwrap_or_default(), + intrinsic_size.x, + ); + + let mut height = paragraph + .glyphs + .iter() + .map(|g| g.line_height) + .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) + .unwrap_or(paragraph.empty_paragraph_height); + if idx == 0 { + height = f32::max(height, job.first_row_min_height); + } + intrinsic_size.y += point_scale.round_to_pixel(height); + } + intrinsic_size +} + // Ignores the Y coordinate. fn rows_from_paragraphs( paragraphs: Vec, @@ -610,6 +653,7 @@ fn galley_from_rows( job: Arc, mut rows: Vec, elided: bool, + intrinsic_size: Vec2, ) -> Galley { let mut first_row_min_height = job.first_row_min_height; let mut cursor_y = 0.0; @@ -680,6 +724,7 @@ fn galley_from_rows( num_vertices, num_indices, pixels_per_point: point_scale.pixels_per_point, + intrinsic_size, }; if galley.job.round_output_to_gui { diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 016bfe104..36d92479e 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -560,6 +560,12 @@ pub struct Galley { /// so that we can warn if this has changed once we get to /// tessellation. pub pixels_per_point: f32, + + /// This is the size that a non-wrapped, non-truncated, non-justified version of the text + /// would have. + /// + /// Useful for advanced layouting. + pub intrinsic_size: Vec2, } #[derive(Clone, Debug, PartialEq)] @@ -821,6 +827,8 @@ impl Galley { .at_most(rect.min.x + self.job.wrap.max_width) .floor_ui(); } + + self.intrinsic_size = self.intrinsic_size.round_ui(); } /// Append each galley under the previous one. @@ -836,6 +844,7 @@ impl Galley { num_vertices: 0, num_indices: 0, pixels_per_point, + intrinsic_size: Vec2::ZERO, }; for (i, galley) in galleys.iter().enumerate() { @@ -872,6 +881,9 @@ impl Galley { // Note that if `galley.elided` is true this will be the last `Galley` in // the vector and the loop will end. merged_galley.elided |= galley.elided; + merged_galley.intrinsic_size.x = + f32::max(merged_galley.intrinsic_size.x, galley.intrinsic_size.x); + merged_galley.intrinsic_size.y += galley.intrinsic_size.y; } if merged_galley.job.round_output_to_gui { diff --git a/tests/egui_tests/tests/test_atoms.rs b/tests/egui_tests/tests/test_atoms.rs index abc9f2d05..98e90c1c5 100644 --- a/tests/egui_tests/tests/test_atoms.rs +++ b/tests/egui_tests/tests/test_atoms.rs @@ -69,3 +69,35 @@ fn single_test(name: &str, mut f: impl FnMut(&mut Ui)) -> SnapshotResult { harness.try_snapshot(name) } + +#[test] +fn test_intrinsic_size() { + 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 = ui.add(Button::new( + "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; + }); + } + assert_eq!(intrinsic_size.unwrap().round(), Vec2::new(305.0, 18.0)); +} From fbe0aadf63ddc40634a4d26918a428d15e621422 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Jul 2025 10:12:47 +0200 Subject: [PATCH 45/56] Add `Popup::from_toggle_button_response` (#7315) Adds a convenience constructor for `Popup` --- crates/egui/src/containers/popup.rs | 94 ++++++++++++++++------------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index b16b61648..13b095d35 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -211,6 +211,57 @@ impl<'a> Popup<'a> { } } + /// Show a popup relative to some widget. + /// The popup will be always open. + /// + /// See [`Self::menu`] and [`Self::context_menu`] for common use cases. + pub fn from_response(response: &Response) -> Self { + let mut popup = Self::new( + response.id.with("popup"), + response.ctx.clone(), + response, + response.layer_id, + ); + popup.widget_clicked_elsewhere = response.clicked_elsewhere(); + popup + } + + /// Show a popup relative to some widget, + /// toggling the open state based on the widget's click state. + /// + /// See [`Self::menu`] and [`Self::context_menu`] for common use cases. + pub fn from_toggle_button_response(button_response: &Response) -> Self { + Self::from_response(button_response) + .open_memory(button_response.clicked().then_some(SetOpenCommand::Toggle)) + } + + /// Show a popup when the widget was clicked. + /// Sets the layout to `Layout::top_down_justified(Align::Min)`. + pub fn menu(button_response: &Response) -> Self { + Self::from_toggle_button_response(button_response) + .kind(PopupKind::Menu) + .layout(Layout::top_down_justified(Align::Min)) + .style(menu_style) + .gap(0.0) + } + + /// Show a context menu when the widget was secondary clicked. + /// Sets the layout to `Layout::top_down_justified(Align::Min)`. + /// In contrast to [`Self::menu`], this will open at the pointer position. + pub fn context_menu(response: &Response) -> Self { + Self::menu(response) + .open_memory(if response.secondary_clicked() { + Some(SetOpenCommand::Bool(true)) + } else if response.clicked() { + // Explicitly close the menu if the widget was clicked + // Without this, the context menu would stay open if the user clicks the widget + Some(SetOpenCommand::Bool(false)) + } else { + None + }) + .at_pointer_fixed() + } + /// Set the kind of the popup. Used for [`Area::kind`] and [`Area::order`]. #[inline] pub fn kind(mut self, kind: PopupKind) -> Self { @@ -243,49 +294,6 @@ impl<'a> Popup<'a> { self } - /// Show a popup relative to some widget. - /// The popup will be always open. - /// - /// See [`Self::menu`] and [`Self::context_menu`] for common use cases. - pub fn from_response(response: &Response) -> Self { - let mut popup = Self::new( - response.id.with("popup"), - response.ctx.clone(), - response, - response.layer_id, - ); - popup.widget_clicked_elsewhere = response.clicked_elsewhere(); - popup - } - - /// Show a popup when the widget was clicked. - /// Sets the layout to `Layout::top_down_justified(Align::Min)`. - pub fn menu(response: &Response) -> Self { - Self::from_response(response) - .open_memory(response.clicked().then_some(SetOpenCommand::Toggle)) - .kind(PopupKind::Menu) - .layout(Layout::top_down_justified(Align::Min)) - .style(menu_style) - .gap(0.0) - } - - /// Show a context menu when the widget was secondary clicked. - /// Sets the layout to `Layout::top_down_justified(Align::Min)`. - /// In contrast to [`Self::menu`], this will open at the pointer position. - pub fn context_menu(response: &Response) -> Self { - Self::menu(response) - .open_memory(if response.secondary_clicked() { - Some(SetOpenCommand::Bool(true)) - } else if response.clicked() { - // Explicitly close the menu if the widget was clicked - // Without this, the context menu would stay open if the user clicks the widget - Some(SetOpenCommand::Bool(false)) - } else { - None - }) - .at_pointer_fixed() - } - /// Force the popup to be open or closed. #[inline] pub fn open(mut self, open: bool) -> Self { From a7f14ca17672c61d74d1182be6cf50bb3d7bf33e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Jul 2025 12:55:06 +0200 Subject: [PATCH 46/56] Deprecate `Memory::popup` API in favor of new `Popup` API (#7317) * Closes #7037 * Closes #7297 This deprecates all popup-related function in `Memory`, replacing them with the new `egui::Popup`. The new API is nicer in all ways, so we should encourage people to use it. --- crates/egui/src/containers/combo_box.rs | 4 +- crates/egui/src/containers/modal.rs | 11 ++- crates/egui/src/containers/popup.rs | 100 +++++++++++++++++++----- crates/egui/src/memory/mod.rs | 18 ++++- crates/egui/src/widgets/color_picker.rs | 2 +- crates/egui_demo_lib/src/demo/modals.rs | 6 +- 6 files changed, 108 insertions(+), 33 deletions(-) diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index fc5f33905..6ef928849 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -293,7 +293,7 @@ impl ComboBox { /// Check if the [`ComboBox`] with the given id has its popup menu currently opened. pub fn is_open(ctx: &Context, id: Id) -> bool { - ctx.memory(|m| m.is_popup_open(Self::widget_to_popup_id(id))) + Popup::is_id_open(ctx, Self::widget_to_popup_id(id)) } /// Convert a [`ComboBox`] id to the id used to store it's popup state. @@ -315,7 +315,7 @@ fn combo_box_dyn<'c, R>( ) -> InnerResponse> { let popup_id = ComboBox::widget_to_popup_id(button_id); - let is_popup_open = ui.memory(|m| m.is_popup_open(popup_id)); + let is_popup_open = Popup::is_id_open(ui.ctx(), popup_id); let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); diff --git a/crates/egui/src/containers/modal.rs b/crates/egui/src/containers/modal.rs index 2edc628e9..e36ad6e1b 100644 --- a/crates/egui/src/containers/modal.rs +++ b/crates/egui/src/containers/modal.rs @@ -1,7 +1,8 @@ +use emath::{Align2, Vec2}; + use crate::{ Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind, }; -use emath::{Align2, Vec2}; /// A modal dialog. /// @@ -80,13 +81,11 @@ impl Modal { frame, } = self; - let (is_top_modal, any_popup_open) = ctx.memory_mut(|mem| { + let is_top_modal = ctx.memory_mut(|mem| { mem.set_modal_layer(area.layer()); - ( - mem.top_modal_layer() == Some(area.layer()), - mem.any_popup_open(), - ) + mem.top_modal_layer() == Some(area.layer()) }); + let any_popup_open = crate::Popup::is_any_open(ctx); let InnerResponse { inner: (inner, backdrop_response), response, diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 13b095d35..66cedddae 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -1,11 +1,15 @@ -use crate::containers::menu::{MenuConfig, MenuState, menu_style}; -use crate::style::StyleModifier; +#![expect(deprecated)] // This is a new, safe wrapper around the old `Memory::popup` API. + +use std::iter::once; + +use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2}; + use crate::{ Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response, Sense, Ui, UiKind, UiStackInfo, + containers::menu::{MenuConfig, MenuState, menu_style}, + style::StyleModifier, }; -use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2}; -use std::iter::once; /// What should we anchor the popup to? /// @@ -64,9 +68,7 @@ impl PopupAnchor { match self { Self::ParentRect(rect) => Some(rect), Self::Pointer => ctx.pointer_hover_pos().map(Rect::from_pos), - Self::PointerFixed => ctx - .memory(|mem| mem.popup_position(popup_id)) - .map(Rect::from_pos), + Self::PointerFixed => Popup::position_of_id(ctx, popup_id).map(Rect::from_pos), Self::Position(pos) => Some(Rect::from_pos(pos)), } } @@ -122,12 +124,12 @@ enum OpenKind<'a> { impl OpenKind<'_> { /// Returns `true` if the popup should be open - fn is_open(&self, id: Id, ctx: &Context) -> bool { + fn is_open(&self, popup_id: Id, ctx: &Context) -> bool { match self { OpenKind::Open => true, OpenKind::Closed => false, OpenKind::Bool(open) => **open, - OpenKind::Memory { .. } => ctx.memory(|mem| mem.is_popup_open(id)), + OpenKind::Memory { .. } => Popup::is_id_open(ctx, popup_id), } } } @@ -217,7 +219,7 @@ impl<'a> Popup<'a> { /// See [`Self::menu`] and [`Self::context_menu`] for common use cases. pub fn from_response(response: &Response) -> Self { let mut popup = Self::new( - response.id.with("popup"), + Self::default_response_id(response), response.ctx.clone(), response, response.layer_id, @@ -455,7 +457,7 @@ impl<'a> Popup<'a> { OpenKind::Open => true, OpenKind::Closed => false, OpenKind::Bool(open) => **open, - OpenKind::Memory { .. } => self.ctx.memory(|mem| mem.is_popup_open(self.id)), + OpenKind::Memory { .. } => Self::is_id_open(&self.ctx, self.id), } } @@ -504,26 +506,26 @@ impl<'a> Popup<'a> { let id = self.id; if let OpenKind::Memory { set } = self.open_kind { - self.ctx.memory_mut(|mem| match set { + match set { Some(SetOpenCommand::Bool(open)) => { if open { match self.anchor { PopupAnchor::PointerFixed => { - mem.open_popup_at(id, hover_pos); + self.ctx.memory_mut(|mem| mem.open_popup_at(id, hover_pos)); } - _ => mem.open_popup(id), + _ => Popup::open_id(&self.ctx, id), } } else { - mem.close_popup(id); + Self::close_id(&self.ctx, id); } } Some(SetOpenCommand::Toggle) => { - mem.toggle_popup(id); + Self::toggle_id(&self.ctx, id); } None => { - mem.keep_popup_open(id); + self.ctx.memory_mut(|mem| mem.keep_popup_open(id)); } - }); + } } if !self.open_kind.is_open(self.id, &self.ctx) { @@ -627,3 +629,65 @@ impl<'a> Popup<'a> { Some(response) } } + +/// ## Static methods +impl Popup<'_> { + /// The default ID when constructing a popup from the [`Response`] of e.g. a button. + pub fn default_response_id(response: &Response) -> Id { + response.id.with("popup") + } + + /// Is the given popup open? + /// + /// This assumes the use of either: + /// * [`Self::open_memory`] + /// * [`Self::from_toggle_button_response`] + /// * [`Self::menu`] + /// * [`Self::context_menu`] + /// + /// The popup id should be the same as either you set with [`Self::id`] or the + /// default one from [`Self::default_response_id`]. + pub fn is_id_open(ctx: &Context, popup_id: Id) -> bool { + ctx.memory(|mem| mem.is_popup_open(popup_id)) + } + + /// Is any popup open? + /// + /// This assumes the egui memory is being used to track the open state of popups. + pub fn is_any_open(ctx: &Context) -> bool { + ctx.memory(|mem| mem.any_popup_open()) + } + + /// Open the given popup and close all others. + /// + /// If you are NOT using [`Popup::show`], you must + /// also call [`crate::Memory::keep_popup_open`] as long as + /// you're showing the popup. + pub fn open_id(ctx: &Context, popup_id: Id) { + ctx.memory_mut(|mem| mem.open_popup(popup_id)); + } + + /// Toggle the given popup between closed and open. + /// + /// Note: At most, only one popup can be open at a time. + pub fn toggle_id(ctx: &Context, popup_id: Id) { + ctx.memory_mut(|mem| mem.toggle_popup(popup_id)); + } + + /// Close all currently open popups. + pub fn close_all(ctx: &Context) { + ctx.memory_mut(|mem| mem.close_all_popups()); + } + + /// Close the given popup, if it is open. + /// + /// See also [`Self::close_all`] if you want to close any / all currently open popups. + pub fn close_id(ctx: &Context, popup_id: Id) { + ctx.memory_mut(|mem| mem.close_popup(popup_id)); + } + + /// Get the position for this popup, if it is open. + pub fn position_of_id(ctx: &Context, popup_id: Id) -> Option { + ctx.memory(|mem| mem.popup_position(popup_id)) + } +} diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 8f98de305..d4912f9d8 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -1012,11 +1012,11 @@ impl OpenPopup { } } -/// ## Popups -/// Popups are things like combo-boxes, color pickers, menus etc. -/// Only one can be open at a time. +/// ## Deprecated popup API +/// Use [`crate::Popup`] instead. impl Memory { /// Is the given popup open? + #[deprecated = "Use Popup::is_id_open instead"] pub fn is_popup_open(&self, popup_id: Id) -> bool { self.popups .get(&self.viewport_id) @@ -1025,6 +1025,7 @@ impl Memory { } /// Is any popup open? + #[deprecated = "Use Popup::is_any_open instead"] pub fn any_popup_open(&self) -> bool { self.popups.contains_key(&self.viewport_id) || self.everything_is_visible() } @@ -1032,6 +1033,7 @@ impl Memory { /// Open the given popup and close all others. /// /// Note that you must call `keep_popup_open` on subsequent frames as long as the popup is open. + #[deprecated = "Use Popup::open_id instead"] pub fn open_popup(&mut self, popup_id: Id) { self.popups .insert(self.viewport_id, OpenPopup::new(popup_id, None)); @@ -1042,6 +1044,7 @@ impl Memory { /// This is needed because in some cases popups can go away without `close_popup` being /// called. For example, when a context menu is open and the underlying widget stops /// being rendered. + #[deprecated = "Use Popup::show instead"] pub fn keep_popup_open(&mut self, popup_id: Id) { if let Some(state) = self.popups.get_mut(&self.viewport_id) { if state.id == popup_id { @@ -1051,12 +1054,14 @@ impl Memory { } /// Open the popup and remember its position. + #[deprecated = "Use Popup with PopupAnchor::Position instead"] pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into>) { self.popups .insert(self.viewport_id, OpenPopup::new(popup_id, pos.into())); } /// Get the position for this popup. + #[deprecated = "Use Popup::position_of_id instead"] pub fn popup_position(&self, id: Id) -> Option { self.popups .get(&self.viewport_id) @@ -1064,6 +1069,7 @@ impl Memory { } /// Close any currently open popup. + #[deprecated = "Use Popup::close_all instead"] pub fn close_all_popups(&mut self) { self.popups.clear(); } @@ -1071,7 +1077,9 @@ impl Memory { /// Close the given popup, if it is open. /// /// See also [`Self::close_all_popups`] if you want to close any / all currently open popups. + #[deprecated = "Use Popup::close_id instead"] pub fn close_popup(&mut self, popup_id: Id) { + #[expect(deprecated)] if self.is_popup_open(popup_id) { self.popups.remove(&self.viewport_id); } @@ -1080,14 +1088,18 @@ impl Memory { /// Toggle the given popup between closed and open. /// /// Note: At most, only one popup can be open at a time. + #[deprecated = "Use Popup::toggle_id instead"] pub fn toggle_popup(&mut self, popup_id: Id) { + #[expect(deprecated)] if self.is_popup_open(popup_id) { self.close_popup(popup_id); } else { self.open_popup(popup_id); } } +} +impl Memory { /// If true, all windows, menus, tooltips, etc., will be visible at once. /// /// This is useful for testing, benchmarking, pre-caching, etc. diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index f8605beaf..17d6b650b 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -491,7 +491,7 @@ pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> b pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response { let popup_id = ui.auto_id_with("popup"); - let open = ui.memory(|mem| mem.is_popup_open(popup_id)); + let open = Popup::is_id_open(ui.ctx(), popup_id); let mut button_response = color_button(ui, (*hsva).into(), open); if ui.style().explanation_tooltips { button_response = button_response.on_hover_text("Click to edit color"); diff --git a/crates/egui_demo_lib/src/demo/modals.rs b/crates/egui_demo_lib/src/demo/modals.rs index 0aefbce82..a916c8bdf 100644 --- a/crates/egui_demo_lib/src/demo/modals.rs +++ b/crates/egui_demo_lib/src/demo/modals.rs @@ -164,8 +164,8 @@ impl crate::View for Modals { mod tests { use crate::Demo as _; use crate::demo::modals::Modals; - use egui::Key; use egui::accesskit::Role; + use egui::{Key, Popup}; use egui_kittest::kittest::Queryable as _; use egui_kittest::{Harness, SnapshotResults}; @@ -187,12 +187,12 @@ mod tests { // Harness::run would fail because we keep requesting repaints to simulate progress. harness.run_ok(); - assert!(harness.ctx.memory(|mem| mem.any_popup_open())); + assert!(Popup::is_any_open(&harness.ctx)); assert!(harness.state().user_modal_open); harness.key_press(Key::Escape); harness.run_ok(); - assert!(!harness.ctx.memory(|mem| mem.any_popup_open())); + assert!(!Popup::is_any_open(&harness.ctx)); assert!(harness.state().user_modal_open); } From 207e71c2ae5c0a62c08375e88199e6fa6efc7c2e Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 9 Jul 2025 14:53:19 +0200 Subject: [PATCH 47/56] Exclude `\n` when splitting `Galley`s (#7316) * Follow up to #7146 Previously when galleys were splitted, each exept the last had an extra empty row that had to be removed when they were concated. This changes it to remove the `\n` from the layout jobs when splitting. --- crates/egui/src/atomics/atom_kind.rs | 2 +- crates/epaint/src/text/fonts.rs | 43 +++++++--- crates/epaint/src/text/text_layout.rs | 94 +++++++++++++++++---- crates/epaint/src/text/text_layout_types.rs | 67 ++++++++------- 4 files changed, 146 insertions(+), 60 deletions(-) diff --git a/crates/egui/src/atomics/atom_kind.rs b/crates/egui/src/atomics/atom_kind.rs index b85b504a2..34cac4ceb 100644 --- a/crates/egui/src/atomics/atom_kind.rs +++ b/crates/egui/src/atomics/atom_kind.rs @@ -84,7 +84,7 @@ impl<'a> AtomKind<'a> { let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, TextStyle::Button); - (galley.intrinsic_size, SizedAtomKind::Text(galley)) + (galley.intrinsic_size(), SizedAtomKind::Text(galley)) } AtomKind::Image(image) => { let size = image.load_and_calc_size(ui, available_size); diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index a08f3206e..30c71eea7 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -825,7 +825,7 @@ impl GalleyCache { let job = Arc::new(job); if allow_split_paragraphs && should_cache_each_paragraph_individually(&job) { let (child_galleys, child_hashes) = - self.layout_each_paragraph_individuallly(fonts, &job); + self.layout_each_paragraph_individually(fonts, &job); debug_assert_eq!( child_hashes.len(), child_galleys.len(), @@ -869,7 +869,7 @@ impl GalleyCache { } /// Split on `\n` and lay out (and cache) each paragraph individually. - fn layout_each_paragraph_individuallly( + fn layout_each_paragraph_individually( &mut self, fonts: &mut FontsImpl, job: &LayoutJob, @@ -884,9 +884,11 @@ impl GalleyCache { while start < job.text.len() { let is_first_paragraph = start == 0; + // `end` will not include the `\n` since we don't want to create an empty row in our + // split galley let end = job.text[start..] .find('\n') - .map_or(job.text.len(), |i| start + i + 1); + .map_or(job.text.len(), |i| start + i); let mut paragraph_job = LayoutJob { text: job.text[start..end].to_owned(), @@ -920,7 +922,7 @@ impl GalleyCache { if section_range.end <= start { // The section is behind us current_section += 1; - } else if end <= section_range.start { + } else if end < section_range.start { break; // Haven't reached this one yet. } else { // Section range overlaps with paragraph range @@ -953,10 +955,6 @@ impl GalleyCache { // This will prevent us from invalidating cache entries unnecessarily: if max_rows_remaining != usize::MAX { max_rows_remaining -= galley.rows.len(); - // Ignore extra trailing row, see merging `Galley::concat` for more details. - if end < job.text.len() && !galley.elided { - max_rows_remaining += 1; - } } let elided = galley.elided; @@ -965,7 +963,7 @@ impl GalleyCache { break; } - start = end; + start = end + 1; } (child_galleys, child_hashes) @@ -1091,6 +1089,29 @@ mod tests { Color32::WHITE, f32::INFINITY, ), + { + let mut job = LayoutJob::simple( + "hi".to_owned(), + FontId::default(), + Color32::WHITE, + f32::INFINITY, + ); + job.append("\n", 0.0, TextFormat::default()); + job.append("\n", 0.0, TextFormat::default()); + job.append("world", 0.0, TextFormat::default()); + job.wrap.max_rows = 2; + job + }, + { + let mut job = LayoutJob::simple( + "Test text with a lot of words\n and a newline.".to_owned(), + FontId::new(14.0, FontFamily::Monospace), + Color32::WHITE, + 40.0, + ); + job.first_row_min_height = 30.0; + job + }, LayoutJob::simple( "This some text that may be long.\nDet kanske också finns lite ÅÄÖ här.".to_owned(), FontId::new(14.0, FontFamily::Proportional), @@ -1213,7 +1234,7 @@ mod tests { let text = job.text.clone(); let galley_unwrapped = layout(&mut fonts, job.into()); - let intrinsic_size = galley_wrapped.intrinsic_size; + let intrinsic_size = galley_wrapped.intrinsic_size(); let unwrapped_size = galley_unwrapped.size(); let difference = (intrinsic_size - unwrapped_size).length().abs(); @@ -1232,7 +1253,7 @@ mod tests { format!("{unwrapped_size:.4?}"), "Unwrapped galley intrinsic size should exactly match its size. \ {:.8?} vs {:8?}", - galley_unwrapped.intrinsic_size, + galley_unwrapped.intrinsic_size(), galley_unwrapped.size(), ); } diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 6dc0aa03f..1e0565171 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -204,20 +204,12 @@ fn calculate_intrinsic_size( ) -> Vec2 { let mut intrinsic_size = Vec2::ZERO; for (idx, paragraph) in paragraphs.iter().enumerate() { - if paragraph.glyphs.is_empty() { - if idx == 0 { - intrinsic_size.y += point_scale.round_to_pixel(paragraph.empty_paragraph_height); - } - continue; - } - intrinsic_size.x = f32::max( - paragraph - .glyphs - .last() - .map(|l| l.max_x()) - .unwrap_or_default(), - intrinsic_size.x, - ); + let width = paragraph + .glyphs + .last() + .map(|l| l.max_x()) + .unwrap_or_default(); + intrinsic_size.x = f32::max(intrinsic_size.x, width); let mut height = paragraph .glyphs @@ -253,7 +245,7 @@ fn rows_from_paragraphs( if paragraph.glyphs.is_empty() { rows.push(PlacedRow { - pos: Pos2::ZERO, + pos: pos2(0.0, f32::NAN), row: Arc::new(Row { section_index_at_start: paragraph.section_index_at_start, glyphs: vec![], @@ -659,12 +651,12 @@ fn galley_from_rows( let mut cursor_y = 0.0; for placed_row in &mut rows { - let mut max_row_height = first_row_min_height.max(placed_row.rect().height()); + let mut max_row_height = first_row_min_height.at_least(placed_row.height()); let row = Arc::make_mut(&mut placed_row.row); first_row_min_height = 0.0; for glyph in &row.glyphs { - max_row_height = max_row_height.max(glyph.line_height); + max_row_height = max_row_height.at_least(glyph.line_height); } max_row_height = point_scale.round_to_pixel(max_row_height); @@ -1212,4 +1204,72 @@ mod tests { assert_eq!(row.pos, Pos2::ZERO); assert_eq!(row.rect().max.x, row.glyphs.last().unwrap().max_x()); } + + #[test] + fn test_empty_row() { + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); + + let font_id = FontId::default(); + let font_height = fonts.font(&font_id).row_height(); + + let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY); + + let galley = layout(&mut fonts, job.into()); + + assert_eq!(galley.rows.len(), 1, "Expected one row"); + assert_eq!( + galley.rows[0].row.glyphs.len(), + 0, + "Expected no glyphs in the empty row" + ); + assert_eq!( + galley.size(), + Vec2::new(0.0, font_height.round()), + "Unexpected galley size" + ); + assert_eq!( + galley.intrinsic_size(), + Vec2::new(0.0, font_height.round()), + "Unexpected intrinsic size" + ); + } + + #[test] + fn test_end_with_newline() { + let mut fonts = FontsImpl::new( + 1.0, + 1024, + AlphaFromCoverage::default(), + FontDefinitions::default(), + ); + + let font_id = FontId::default(); + let font_height = fonts.font(&font_id).row_height(); + + let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY); + + let galley = layout(&mut fonts, job.into()); + + assert_eq!(galley.rows.len(), 2, "Expected two rows"); + assert_eq!( + galley.rows[1].row.glyphs.len(), + 0, + "Expected no glyphs in the empty row" + ); + assert_eq!( + galley.size().round(), + Vec2::new(17.0, font_height.round() * 2.0), + "Unexpected galley size" + ); + assert_eq!( + galley.intrinsic_size().round(), + Vec2::new(17.0, font_height.round() * 2.0), + "Unexpected intrinsic size" + ); + } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 36d92479e..79ca50556 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -561,11 +561,7 @@ pub struct Galley { /// tessellation. pub pixels_per_point: f32, - /// This is the size that a non-wrapped, non-truncated, non-justified version of the text - /// would have. - /// - /// Useful for advanced layouting. - pub intrinsic_size: Vec2, + pub(crate) intrinsic_size: Vec2, } #[derive(Clone, Debug, PartialEq)] @@ -801,6 +797,21 @@ impl Galley { self.rect.size() } + /// This is the size that a non-wrapped, non-truncated, non-justified version of the text + /// would have. + /// + /// Useful for advanced layouting. + #[inline] + pub fn intrinsic_size(&self) -> Vec2 { + // We do the rounding here instead of in `round_output_to_gui` so that rounding + // errors don't accumulate when concatenating multiple galleys. + if self.job.round_output_to_gui { + self.intrinsic_size.round_ui() + } else { + self.intrinsic_size + } + } + pub(crate) fn round_output_to_gui(&mut self) { for placed_row in &mut self.rows { // Optimization: only call `make_mut` if necessary (can cause a deep clone) @@ -827,8 +838,6 @@ impl Galley { .at_most(rect.min.x + self.job.wrap.max_width) .floor_ui(); } - - self.intrinsic_size = self.intrinsic_size.round_ui(); } /// Append each galley under the previous one. @@ -849,32 +858,28 @@ impl Galley { for (i, galley) in galleys.iter().enumerate() { let current_y_offset = merged_galley.rect.height(); + let is_last_galley = i + 1 == galleys.len(); - let mut rows = galley.rows.iter(); - // As documented in `Row::ends_with_newline`, a '\n' will always create a - // new `Row` immediately below the current one. Here it doesn't make sense - // for us to append this new row so we just ignore it. - let is_last_row = i + 1 == galleys.len(); - if !is_last_row && !galley.elided { - let popped = rows.next_back(); - debug_assert_eq!(popped.unwrap().row.glyphs.len(), 0, "Bug in Galley::concat"); - } + merged_galley + .rows + .extend(galley.rows.iter().enumerate().map(|(row_idx, placed_row)| { + let new_pos = placed_row.pos + current_y_offset * Vec2::Y; + let new_pos = new_pos.round_to_pixels(pixels_per_point); + merged_galley.mesh_bounds = merged_galley + .mesh_bounds + .union(placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2())); + merged_galley.rect = merged_galley + .rect + .union(Rect::from_min_size(new_pos, placed_row.size)); - merged_galley.rows.extend(rows.map(|placed_row| { - let new_pos = placed_row.pos + current_y_offset * Vec2::Y; - let new_pos = new_pos.round_to_pixels(pixels_per_point); - merged_galley.mesh_bounds = merged_galley - .mesh_bounds - .union(placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2())); - merged_galley.rect = merged_galley - .rect - .union(Rect::from_min_size(new_pos, placed_row.size)); - - super::PlacedRow { - pos: new_pos, - row: placed_row.row.clone(), - } - })); + let mut row = placed_row.row.clone(); + let is_last_row_in_galley = row_idx + 1 == galley.rows.len(); + if !is_last_galley && is_last_row_in_galley { + // Since we remove the `\n` when splitting rows, we need to add it back here + Arc::make_mut(&mut row).ends_with_newline = true; + } + super::PlacedRow { pos: new_pos, row } + })); merged_galley.num_vertices += galley.num_vertices; merged_galley.num_indices += galley.num_indices; From 9fd0ad36e0640b2df24312d945862507e38aa8a3 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 9 Jul 2025 15:29:51 +0200 Subject: [PATCH 48/56] Implement `BitOr` and `BitOrAssign` for `Rect` (#7319) --- crates/egui/src/containers/area.rs | 2 +- crates/egui/src/containers/sides.rs | 6 +++--- crates/egui/src/containers/tooltip.rs | 2 +- crates/egui/src/context.rs | 2 +- crates/egui/src/debug_text.rs | 4 ++-- crates/egui/src/layout.rs | 6 +++--- crates/egui/src/pass_state.rs | 10 +++++----- crates/egui/src/placer.rs | 4 ++-- .../src/text_selection/label_text_selection.rs | 2 +- crates/egui/src/widgets/label.rs | 2 +- crates/egui_demo_lib/src/demo/scrolling.rs | 2 +- crates/egui_extras/src/layout.rs | 2 +- crates/emath/src/rect.rs | 17 +++++++++++++++++ crates/epaint/src/shapes/shape.rs | 2 +- crates/epaint/src/text/text_layout.rs | 5 ++--- crates/epaint/src/text/text_layout_types.rs | 9 +++------ 16 files changed, 45 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 5ae4a4b30..d3d2a7228 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -705,7 +705,7 @@ fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 { let current_column_bb = column_bbs.last_mut().unwrap(); if rect.left() < current_column_bb.right() { // same column - *current_column_bb = current_column_bb.union(rect); + *current_column_bb |= rect; } else { // new column column_bbs.push(rect); diff --git a/crates/egui/src/containers/sides.rs b/crates/egui/src/containers/sides.rs index 8a67c6c5e..709c1b645 100644 --- a/crates/egui/src/containers/sides.rs +++ b/crates/egui/src/containers/sides.rs @@ -185,7 +185,7 @@ impl Sides { wrap_mode, ); - ui.advance_cursor_after_rect(left_rect.union(right_rect)); + ui.advance_cursor_after_rect(left_rect | right_rect); (result_left, result_right) } SidesKind::ShrinkRight => { @@ -205,7 +205,7 @@ impl Sides { wrap_mode, ); - ui.advance_cursor_after_rect(left_rect.union(right_rect)); + ui.advance_cursor_after_rect(left_rect | right_rect); (result_left, result_right) } SidesKind::Extend => { @@ -225,7 +225,7 @@ impl Sides { None, ); - let mut final_rect = left_rect.union(right_rect); + let mut final_rect = left_rect | right_rect; let min_width = left_rect.width() + spacing + right_rect.width(); if ui.is_sizing_pass() { diff --git a/crates/egui/src/containers/tooltip.rs b/crates/egui/src/containers/tooltip.rs index 2060c61cf..99bc95d5c 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -163,7 +163,7 @@ impl Tooltip<'_> { // The popup might not be shown on at_pointer if there is no pointer. if let Some(response) = &response { state.tooltip_count += 1; - state.bounding_rect = state.bounding_rect.union(response.response.rect); + state.bounding_rect |= response.response.rect; response .response .ctx diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index f3bc73cec..9594f03e9 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2689,7 +2689,7 @@ impl Context { self.write(|ctx| { let mut used = ctx.viewport().this_pass.used_by_panels; for (_id, window) in ctx.memory.areas().visible_windows() { - used = used.union(window.rect()); + used |= window.rect(); } used.round_ui() }) diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index f487e795f..2cd1a2755 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -102,7 +102,7 @@ impl State { let location_rect = Align2::RIGHT_TOP.anchor_size(pos - 4.0 * Vec2::X, location_galley.size()); painter.galley(location_rect.min, location_galley, color); - bounding_rect = bounding_rect.union(location_rect); + bounding_rect |= location_rect; } { @@ -117,7 +117,7 @@ impl State { ); let rect = Align2::LEFT_TOP.anchor_size(pos, galley.size()); painter.galley(rect.min, galley, color); - bounding_rect = bounding_rect.union(rect); + bounding_rect |= rect; } pos.y = bounding_rect.max.y + 4.0; diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 3064d0d87..601d1e2e7 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -50,8 +50,8 @@ pub(crate) struct Region { impl Region { /// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect. pub fn expand_to_include_rect(&mut self, rect: Rect) { - self.min_rect = self.min_rect.union(rect); - self.max_rect = self.max_rect.union(rect); + self.min_rect |= rect; + self.max_rect |= rect; } /// Ensure we are big enough to contain the given X-coordinate. @@ -725,7 +725,7 @@ impl Layout { if self.main_wrap { if cursor.intersects(frame_rect.shrink(1.0)) { // make row/column larger if necessary - *cursor = cursor.union(frame_rect); + *cursor |= frame_rect; } else { // this is a new row or column. We temporarily use NAN for what will be filled in later. match self.main_dir { diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index 079bb4eb0..ca0d15720 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -318,7 +318,7 @@ impl PassState { ); self.available_rect.min.x = panel_rect.max.x; self.unused_rect.min.x = panel_rect.max.x; - self.used_by_panels = self.used_by_panels.union(panel_rect); + self.used_by_panels |= panel_rect; } /// Shrink `available_rect`. @@ -329,7 +329,7 @@ impl PassState { ); self.available_rect.max.x = panel_rect.min.x; self.unused_rect.max.x = panel_rect.min.x; - self.used_by_panels = self.used_by_panels.union(panel_rect); + self.used_by_panels |= panel_rect; } /// Shrink `available_rect`. @@ -340,7 +340,7 @@ impl PassState { ); self.available_rect.min.y = panel_rect.max.y; self.unused_rect.min.y = panel_rect.max.y; - self.used_by_panels = self.used_by_panels.union(panel_rect); + self.used_by_panels |= panel_rect; } /// Shrink `available_rect`. @@ -351,13 +351,13 @@ impl PassState { ); self.available_rect.max.y = panel_rect.min.y; self.unused_rect.max.y = panel_rect.min.y; - self.used_by_panels = self.used_by_panels.union(panel_rect); + self.used_by_panels |= panel_rect; } pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) { // Note: we do not shrink `available_rect`, because // we allow windows to cover the CentralPanel. self.unused_rect = Rect::NOTHING; // Nothing left unused after this - self.used_by_panels = self.used_by_panels.union(panel_rect); + self.used_by_panels |= panel_rect; } } diff --git a/crates/egui/src/placer.rs b/crates/egui/src/placer.rs index 6a5d31be0..a56bcdb29 100644 --- a/crates/egui/src/placer.rs +++ b/crates/egui/src/placer.rs @@ -231,7 +231,7 @@ impl Placer { let region = &mut self.region; region.max_rect.min.x = rect.min.x; region.max_rect.max.x = rect.max.x; - region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much + region.max_rect |= region.min_rect; // make sure we didn't shrink too much region.cursor.min.x = region.max_rect.min.x; region.cursor.max.x = region.max_rect.max.x; @@ -246,7 +246,7 @@ impl Placer { let region = &mut self.region; region.max_rect.min.y = rect.min.y; region.max_rect.max.y = rect.max.y; - region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much + region.max_rect |= region.min_rect; // make sure we didn't shrink too much region.cursor.min.y = region.max_rect.min.y; region.cursor.max.y = region.max_rect.max.y; diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index ffbc7ae30..8297bc429 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -546,7 +546,7 @@ impl LabelSelectionState { if let Some(mut cursor_range) = cursor_state.range(galley) { let galley_rect = global_from_galley * Rect::from_min_size(Pos2::ZERO, galley.size()); - self.selection_bbox_this_frame = self.selection_bbox_this_frame.union(galley_rect); + self.selection_bbox_this_frame |= galley_rect; if let Some(selection) = &self.selection { if selection.primary.widget_id == response.id { diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index aa229adff..b25cb908d 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -165,7 +165,7 @@ impl Label { }; select_sense -= Sense::FOCUSABLE; // Don't move focus to labels with TAB key. - sense = sense.union(select_sense); + sense |= select_sense; } if let WidgetText::Galley(galley) = self.text { diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 3e4660d3d..ca3f6e90c 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -222,7 +222,7 @@ fn huge_content_painter(ui: &mut egui::Ui) { font_id.clone(), ui.visuals().text_color(), ); - used_rect = used_rect.union(text_rect); + used_rect |= text_rect; } ui.allocate_rect(used_rect, Sense::hover()); // make sure it is visible! diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index d9210187b..8b2a0fd6e 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -162,7 +162,7 @@ impl<'l> StripLayout<'l> { } else if flags.clip { max_rect } else { - max_rect.union(used_rect) + max_rect | used_rect }; self.set_pos(allocation_rect); diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 8810fe361..089e81671 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,6 +1,7 @@ use std::fmt; use crate::{Div, Mul, NumExt as _, Pos2, Rangef, Rot2, Vec2, lerp, pos2, vec2}; +use std::ops::{BitOr, BitOrAssign}; /// A rectangular region of space. /// @@ -776,6 +777,22 @@ impl Div for Rect { } } +impl BitOr for Rect { + type Output = Self; + + #[inline] + fn bitor(self, other: Self) -> Self { + self.union(other) + } +} + +impl BitOrAssign for Rect { + #[inline] + fn bitor_assign(&mut self, other: Self) { + *self = self.union(other); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 080f1794b..3cdabeadc 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -363,7 +363,7 @@ impl Shape { Self::Vec(shapes) => { let mut rect = Rect::NOTHING; for shape in shapes { - rect = rect.union(shape.visual_bounding_rect()); + rect |= shape.visual_bounding_rect(); } rect } diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 1e0565171..8d4a90fb7 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -691,13 +691,12 @@ fn galley_from_rows( let mut num_indices = 0; for placed_row in &mut rows { - rect = rect.union(placed_row.rect()); + rect |= placed_row.rect(); let row = Arc::make_mut(&mut placed_row.row); row.visuals = tessellate_row(point_scale, &job, &format_summary, row); - mesh_bounds = - mesh_bounds.union(row.visuals.mesh_bounds.translate(placed_row.pos.to_vec2())); + mesh_bounds |= row.visuals.mesh_bounds.translate(placed_row.pos.to_vec2()); num_vertices += row.visuals.mesh.vertices.len(); num_indices += row.visuals.mesh.indices.len(); diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 79ca50556..7635dcede 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -865,12 +865,9 @@ impl Galley { .extend(galley.rows.iter().enumerate().map(|(row_idx, placed_row)| { let new_pos = placed_row.pos + current_y_offset * Vec2::Y; let new_pos = new_pos.round_to_pixels(pixels_per_point); - merged_galley.mesh_bounds = merged_galley - .mesh_bounds - .union(placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2())); - merged_galley.rect = merged_galley - .rect - .union(Rect::from_min_size(new_pos, placed_row.size)); + merged_galley.mesh_bounds |= + placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2()); + merged_galley.rect |= Rect::from_min_size(new_pos, placed_row.size); let mut row = placed_row.row.clone(); let is_last_row_in_galley = row_idx + 1 == galley.rows.len(); From 087e56abaeddbce9a80b13a9387019f5fb3372e5 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 9 Jul 2025 18:18:06 +0200 Subject: [PATCH 49/56] Fix wrong galley split behavior when text ends with new line (#7320) * Fixes a bug introduced by #7316 The last `\n` was ignored for texts ending with `\n` in the galley split logic. --- crates/epaint/src/text/fonts.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 30c71eea7..e59f9bc26 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -886,9 +886,12 @@ impl GalleyCache { let is_first_paragraph = start == 0; // `end` will not include the `\n` since we don't want to create an empty row in our // split galley - let end = job.text[start..] + let mut end = job.text[start..] .find('\n') .map_or(job.text.len(), |i| start + i); + if end == job.text.len() - 1 && job.text.ends_with('\n') { + end += 1; // If the text ends with a newline, we include it in the last paragraph. + } let mut paragraph_job = LayoutJob { text: job.text[start..end].to_owned(), @@ -1083,6 +1086,12 @@ mod tests { Color32::WHITE, f32::INFINITY, ), + LayoutJob::simple( + "ends with newlines\n\n".to_owned(), + FontId::new(14.0, FontFamily::Monospace), + Color32::WHITE, + f32::INFINITY, + ), LayoutJob::simple( "Simple test.".to_owned(), FontId::new(14.0, FontFamily::Monospace), From 8d2f39fc08956068202b4836a3b87c29509276f9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Jul 2025 19:36:38 +0200 Subject: [PATCH 50/56] Improve release checklist (#7322) * See https://github.com/emilk/egui/issues/7321 --- RELEASES.md | 36 ++++++++++++++++------------------- scripts/generate_changelog.py | 9 ++++----- scripts/publish_crates.sh | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 25 deletions(-) create mode 100644 scripts/publish_crates.sh diff --git a/RELEASES.md b/RELEASES.md index 788070762..14db36403 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -22,8 +22,11 @@ We don't update the MSRV in a patch release, unless we really, really need to. # Release process +* [ ] copy this checklist to a new egui issue, called "Release 0.xx.y" +* [ ] close all issues in the milestone for this release + ## Patch release -* [ ] Make a branch off of the latest release +* [ ] make a branch off of the latest release * [ ] cherry-pick what you want to release * [ ] run `cargo semver-checks` @@ -49,7 +52,10 @@ We don't update the MSRV in a patch release, unless we really, really need to. * [ ] run `scripts/generate_example_screenshots.sh` if needed * [ ] write a short release note that fits in a bluesky post * [ ] record gif for `CHANGELOG.md` release note (and later bluesky post) -* [ ] update changelogs using `scripts/generate_changelog.py --version 0.x.0 --write` +* [ ] update changelogs + * [ ] run `scripts/generate_changelog.py --version 0.x.0 --write` + * [ ] read changelogs and clean them up if needed + * [ ] write a good intro with highlight for the main changelog * [ ] bump version numbers in workspace `Cargo.toml` ## Actual release @@ -57,7 +63,7 @@ I usually do this all on the `main` branch, but doing it in a release branch is * [ ] Run `typos` * [ ] `git commit -m 'Release 0.x.0 - '` -* [ ] `cargo publish` (see below) +* [ ] Publish the crates by running `scripts/publish_crates.sh` * [ ] `git tag -a 0.x.0 -m 'Release 0.x.0 - '` * [ ] `git pull --tags ; git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force ; git push --tags` * [ ] merge release PR or push to `main` @@ -66,23 +72,6 @@ I usually do this all on the `main` branch, but doing it in a release branch is * Follow the format of the last release * [ ] wait for documentation to build: https://docs.rs/releases/queue -### `cargo publish`: -``` -(cd crates/emath && cargo publish --quiet) && echo "✅ emath" -(cd crates/ecolor && cargo publish --quiet) && echo "✅ ecolor" -(cd crates/epaint_default_fonts && cargo publish --quiet) && echo "✅ epaint_default_fonts" -(cd crates/epaint && cargo publish --quiet) && echo "✅ epaint" -(cd crates/egui && cargo publish --quiet) && echo "✅ egui" -(cd crates/egui-winit && cargo publish --quiet) && echo "✅ egui-winit" -(cd crates/egui-wgpu && cargo publish --quiet) && echo "✅ egui-wgpu" -(cd crates/eframe && cargo publish --quiet) && echo "✅ eframe" -(cd crates/egui_kittest && cargo publish --quiet) && echo "✅ egui_kittest" -(cd crates/egui_extras && cargo publish --quiet) && echo "✅ egui_extras" -(cd crates/egui_demo_lib && cargo publish --quiet) && echo "✅ egui_demo_lib" -(cd crates/egui_glow && cargo publish --quiet) && echo "✅ egui_glow" -``` - -\ ## Announcements * [ ] [Bluesky](https://bsky.app/profile/ernerfeldt.bsky.social) @@ -91,6 +80,7 @@ I usually do this all on the `main` branch, but doing it in a release branch is * [ ] [r/programming](https://www.reddit.com/r/programming/comments/1bocsf6/announcing_egui_027_an_easytouse_crossplatform/) * [ ] [This Week in Rust](https://github.com/rust-lang/this-week-in-rust/pull/5167) + ## After release * [ ] publish new `eframe_template` * [ ] publish new `egui_plot` @@ -98,3 +88,9 @@ I usually do this all on the `main` branch, but doing it in a release branch is * [ ] publish new `egui_tiles` * [ ] make a PR to `egui_commonmark` * [ ] make a PR to `rerun` + + +## Finally +* [ ] Close the milestone +* [ ] Close this issue +* [ ] Improve `RELEASES.md` with what you learned this time around diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py index 7e3ae4284..61986ee80 100755 --- a/scripts/generate_changelog.py +++ b/scripts/generate_changelog.py @@ -192,7 +192,9 @@ def remove_prefix(text, prefix): def print_section(heading: str, content: str) -> None: if content != "": print(f"## {heading}") - print(content) + print(content.strip()) + print() + print() print() @@ -345,11 +347,8 @@ def main() -> None: print() for crate in crate_names: if crate in crate_sections: - prs = crate_sections[crate] - print_section(crate, changelog_from_prs(prs, crate)) - print() + print_section(crate, changelog_from_prs(crate_sections[crate], crate)) print_section("Unsorted PRs", "\n".join([f"* {item}" for item in unsorted_prs])) - print() print_section( "Unsorted commits", "\n".join([f"* {item}" for item in unsorted_commits]) ) diff --git a/scripts/publish_crates.sh b/scripts/publish_crates.sh new file mode 100644 index 000000000..705b88c41 --- /dev/null +++ b/scripts/publish_crates.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +(cd crates/emath && cargo publish --quiet) && echo "✅ emath" +(cd crates/ecolor && cargo publish --quiet) && echo "✅ ecolor" +(cd crates/epaint_default_fonts && cargo publish --quiet) && echo "✅ epaint_default_fonts" +(cd crates/epaint && cargo publish --quiet) && echo "✅ epaint" +(cd crates/egui && cargo publish --quiet) && echo "✅ egui" +(cd crates/egui-winit && cargo publish --quiet) && echo "✅ egui-winit" +(cd crates/egui-wgpu && cargo publish --quiet) && echo "✅ egui-wgpu" +(cd crates/eframe && cargo publish --quiet) && echo "✅ eframe" +(cd crates/egui_kittest && cargo publish --quiet) && echo "✅ egui_kittest" +(cd crates/egui_extras && cargo publish --quiet) && echo "✅ egui_extras" +(cd crates/egui_demo_lib && cargo publish --quiet) && echo "✅ egui_demo_lib" +(cd crates/egui_glow && cargo publish --quiet) && echo "✅ egui_glow" From 9478a6223b5145daff24844b317aeaa75a746096 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Jul 2025 10:33:48 +0200 Subject: [PATCH 51/56] Rename `egui::containers::menu::Bar` to `egui::MenuBar` (#7327) The old name is still there, just deprecated. --- crates/egui/src/containers/menu.rs | 28 +++++++++++++++---- crates/egui/src/lib.rs | 2 +- crates/egui/src/menu.rs | 2 +- .../src/demo/demo_app_windows.rs | 4 +-- crates/egui_kittest/tests/menu.rs | 4 +-- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index d07d3ab6c..13d3ebe26 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -1,3 +1,5 @@ +//! See [`MenuBar`] for an example + use crate::style::StyleModifier; use crate::{ Button, Color32, Context, Frame, Id, InnerResponse, IntoAtoms, Layout, Popup, @@ -163,13 +165,29 @@ impl MenuState { /// The menu bar goes well in a [`crate::TopBottomPanel::top`], /// but can also be placed in a [`crate::Window`]. /// In the latter case you may want to wrap it in [`Frame`]. +/// +/// ### Example: +/// ``` +/// # egui::__run_test_ui(|ui| { +/// egui::MenuBar::new().ui(ui, |ui| { +/// ui.menu_button("File", |ui| { +/// if ui.button("Quit").clicked() { +/// ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); +/// } +/// }); +/// }); +/// # }); +/// ``` #[derive(Clone, Debug)] -pub struct Bar { +pub struct MenuBar { config: MenuConfig, style: StyleModifier, } -impl Default for Bar { +#[deprecated = "Renamed to `egui::MenuBar`"] +pub type Bar = MenuBar; + +impl Default for MenuBar { fn default() -> Self { Self { config: MenuConfig::default(), @@ -178,7 +196,7 @@ impl Default for Bar { } } -impl Bar { +impl MenuBar { pub fn new() -> Self { Self::default() } @@ -234,8 +252,8 @@ impl Bar { /// A thin wrapper around a [`Button`] that shows a [`Popup::menu`] when clicked. /// -/// The only thing this does is search for the current menu config (if set via [`Bar`]). -/// If your menu button is not in a [`Bar`] it's fine to use [`Ui::button`] and [`Popup::menu`] +/// The only thing this does is search for the current menu config (if set via [`MenuBar`]). +/// If your menu button is not in a [`MenuBar`] it's fine to use [`Ui::button`] and [`Popup::menu`] /// directly. pub struct MenuButton<'a> { pub button: Button<'a>, diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 9c4686bde..c2908df19 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -481,7 +481,7 @@ pub mod text { pub use self::{ atomics::*, - containers::*, + containers::{menu::MenuBar, *}, context::{Context, RepaintCause, RequestRepaintInfo}, data::{ Key, UserData, diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 99bbd450b..f245473db 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -87,7 +87,7 @@ fn set_menu_style(style: &mut Style) { /// The menu bar goes well in a [`crate::TopBottomPanel::top`], /// but can also be placed in a [`crate::Window`]. /// In the latter case you may want to wrap it in [`Frame`]. -#[deprecated = "Use `crate::containers::menu::Bar` instead"] +#[deprecated = "Use `egui::MenuBar::new().ui(` instead"] pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse { ui.horizontal(|ui| { set_menu_style(ui.style_mut()); diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 7f24d7284..9ee660896 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -237,7 +237,7 @@ impl DemoWindows { fn mobile_top_bar(&mut self, ctx: &Context) { egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { - menu::Bar::new() + menu::MenuBar::new() .config(menu::MenuConfig::new().style(StyleModifier::default())) .ui(ui, |ui| { let font_size = 16.5; @@ -290,7 +290,7 @@ impl DemoWindows { }); egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { - menu::Bar::new().ui(ui, |ui| { + menu::MenuBar::new().ui(ui, |ui| { file_menu_button(ui); }); }); diff --git a/crates/egui_kittest/tests/menu.rs b/crates/egui_kittest/tests/menu.rs index 00470bd5c..b7d001308 100644 --- a/crates/egui_kittest/tests/menu.rs +++ b/crates/egui_kittest/tests/menu.rs @@ -1,4 +1,4 @@ -use egui::containers::menu::{Bar, MenuConfig, SubMenuButton}; +use egui::containers::menu::{MenuBar, MenuConfig, SubMenuButton}; use egui::{PopupCloseBehavior, Ui, include_image}; use egui_kittest::{Harness, SnapshotResults}; use kittest::Queryable as _; @@ -18,7 +18,7 @@ impl TestMenu { fn ui(&mut self, ui: &mut Ui) { ui.vertical(|ui| { - Bar::new().config(self.config.clone()).ui(ui, |ui| { + MenuBar::new().config(self.config.clone()).ui(ui, |ui| { egui::Sides::new().show( ui, |ui| { From 14c2e5d3d5b1bce77b72e0c3367af0a2be9222d2 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 10 Jul 2025 10:48:14 +0200 Subject: [PATCH 52/56] Set intrinsic size for Label (#7328) Sets intrinsic size for labels --- crates/egui/src/response.rs | 4 +- crates/egui/src/widgets/label.rs | 4 +- tests/egui_tests/tests/test_atoms.rs | 59 ++++++++++++++++------------ 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index d9dc61c36..85dc8a607 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -59,8 +59,8 @@ pub struct Response { /// The intrinsic / desired size of the widget. /// - /// For a button, this will be the size of the label + the frames padding, - /// even if the button is laid out in a justified layout and the actual size will be larger. + /// This is the size that a non-wrapped, non-truncated, non-justified version of the widget + /// would have. /// /// If this is `None`, use [`Self::rect`] instead. /// diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index b25cb908d..1abac7886 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -220,6 +220,7 @@ impl Label { .rect_without_leading_space() .translate(pos.to_vec2()); let mut response = ui.allocate_rect(rect, sense); + response.intrinsic_size = Some(galley.intrinsic_size()); for placed_row in galley.rows.iter().skip(1) { let rect = placed_row.rect().translate(pos.to_vec2()); response |= ui.allocate_rect(rect, sense); @@ -252,7 +253,8 @@ impl Label { }; let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); - let (rect, response) = ui.allocate_exact_size(galley.size(), sense); + let (rect, mut response) = ui.allocate_exact_size(galley.size(), sense); + response.intrinsic_size = Some(galley.intrinsic_size()); let galley_pos = match galley.job.halign { Align::LEFT => rect.left_top(), Align::Center => rect.center_top(), diff --git a/tests/egui_tests/tests/test_atoms.rs b/tests/egui_tests/tests/test_atoms.rs index 98e90c1c5..cf2abbe1a 100644 --- a/tests/egui_tests/tests/test_atoms.rs +++ b/tests/egui_tests/tests/test_atoms.rs @@ -72,32 +72,39 @@ fn single_test(name: &str, mut f: impl FnMut(&mut Ui)) -> SnapshotResult { #[test] fn test_intrinsic_size() { - 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 = ui.add(Button::new( - "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:?}" + 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.", ); - } - assert!( - response.intrinsic_size.is_some(), - "intrinsic_size should be set for `Button`" - ); - intrinsic_size = response.intrinsic_size; - }); + 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); + } + }); + } } - assert_eq!(intrinsic_size.unwrap().round(), Vec2::new(305.0, 18.0)); } From c0325e9be2b49eee8b5ae64cf80ef3cdc861d88e Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 10 Jul 2025 15:35:04 +0200 Subject: [PATCH 53/56] Add more docs to menu (#7331) Improves the docs a bit --- crates/egui/src/containers/close_tag.rs | 8 ++++++++ crates/egui/src/containers/menu.rs | 17 ++++++++++++++++- crates/egui/src/containers/mod.rs | 3 ++- crates/egui/src/containers/popup.rs | 1 + crates/egui/src/ui.rs | 2 +- crates/egui/src/ui_builder.rs | 2 +- 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/close_tag.rs b/crates/egui/src/containers/close_tag.rs index 3d5e3f434..3e93dbbd2 100644 --- a/crates/egui/src/containers/close_tag.rs +++ b/crates/egui/src/containers/close_tag.rs @@ -1,5 +1,13 @@ +#[expect(unused_imports)] +use crate::{Ui, UiBuilder}; use std::sync::atomic::AtomicBool; +/// A tag to mark a container as closable. +/// +/// Usually set via [`UiBuilder::closable`]. +/// +/// [`Ui::close`] will find the closest parent [`ClosableTag`] and set its `close` field to `true`. +/// Use [`Ui::should_close`] to check if close has been called. #[derive(Debug, Default)] pub struct ClosableTag { pub close: AtomicBool, diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index 13d3ebe26..1e2cbfa2e 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -1,4 +1,12 @@ -//! See [`MenuBar`] for an example +//! Popup menus, context menus and menu bars. +//! +//! Show menus via +//! - [`Popup::menu`] and [`Popup::context_menu`] +//! - [`Ui::menu_button`], [`MenuButton`] and [`SubMenuButton`] +//! - [`MenuBar`] +//! - [`Response::context_menu`] +//! +//! See [`MenuBar`] for an example. use crate::style::StyleModifier; use crate::{ @@ -52,6 +60,7 @@ pub fn is_in_menu(ui: &Ui) -> bool { false } +/// Configuration and style for menus. #[derive(Clone, Debug)] pub struct MenuConfig { /// Is this a menu bar? @@ -122,8 +131,10 @@ impl MenuConfig { } } +/// Holds the state of the menu. #[derive(Clone)] pub struct MenuState { + /// The currently open sub menu in this menu. pub open_item: Option, last_visible_pass: u64, } @@ -359,6 +370,10 @@ impl<'a> SubMenuButton<'a> { } } +/// Show a submenu in a menu. +/// +/// Useful if you want to make custom menu buttons. +/// Usually, just use [`MenuButton`] or [`SubMenuButton`] instead. #[derive(Clone, Debug, Default)] pub struct SubMenu { config: Option, diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index 31898838e..4312385da 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -3,7 +3,7 @@ //! For instance, a [`Frame`] adds a frame and background to some contained UI. pub(crate) mod area; -pub mod close_tag; +mod close_tag; pub mod collapsing_header; mod combo_box; pub mod frame; @@ -21,6 +21,7 @@ pub(crate) mod window; pub use { area::{Area, AreaState}, + close_tag::ClosableTag, collapsing_header::{CollapsingHeader, CollapsingResponse}, combo_box::*, frame::Frame, diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 66cedddae..9c2804138 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -162,6 +162,7 @@ impl From for UiKind { } } +/// A popup container. #[must_use = "Call `.show()` to actually display the popup"] pub struct Popup<'a> { id: Id, diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index c24ca211b..56392156a 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -5,9 +5,9 @@ use emath::GuiRounding as _; use epaint::mutex::RwLock; use std::{any::Any, hash::Hash, sync::Arc}; +use crate::ClosableTag; #[cfg(debug_assertions)] use crate::Stroke; -use crate::close_tag::ClosableTag; use crate::containers::menu; use crate::{ Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtoms, diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index fcb389fd9..549170182 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -1,8 +1,8 @@ use std::{hash::Hash, sync::Arc}; +use crate::ClosableTag; #[expect(unused_imports)] // Used for doclinks use crate::Ui; -use crate::close_tag::ClosableTag; use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo}; /// Build a [`Ui`] as the child of another [`Ui`]. From a9124af00d5a7623e316ea272fd2e38ae27c4e36 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 10 Jul 2025 16:38:52 +0200 Subject: [PATCH 54/56] Update kittest to 0.2 (#7332) --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abd6aca03..e0830058e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2421,8 +2421,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kittest" -version = "0.1.0" -source = "git+https://github.com/rerun-io/kittest?branch=main#91bf0fd98b5afe04427bb3aea4c68c6e0034b4bd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c1bfc4cb16136b6f00fb85a281e4b53d026401cf5dff9a427c466bde5891f0b" dependencies = [ "accesskit", "accesskit_consumer", diff --git a/Cargo.toml b/Cargo.toml index b1f350b3d..c4ea5258c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,7 +85,7 @@ glutin = { version = "0.32.0", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } home = "0.5.9" image = { version = "0.25", default-features = false } -kittest = { version = "0.1.0", git = "https://github.com/rerun-io/kittest", branch = "main" } +kittest = { version = "0.2.0" } log = { version = "0.4", features = ["std"] } mimalloc = "0.1.46" nohash-hasher = "0.2" From fabd4aa7a532c9302564ad774a24afb2af978527 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Jul 2025 16:58:39 +0200 Subject: [PATCH 55/56] Release 0.32.0 - Atoms, popups, and better SVG support (#7329) --- CHANGELOG.md | 265 ++++++++++++++++++++ Cargo.lock | 32 +-- Cargo.toml | 26 +- RELEASES.md | 30 +-- crates/ecolor/CHANGELOG.md | 6 + crates/eframe/CHANGELOG.md | 27 ++ crates/egui-wgpu/CHANGELOG.md | 6 + crates/egui-winit/CHANGELOG.md | 8 + crates/egui_extras/CHANGELOG.md | 23 ++ crates/egui_glow/CHANGELOG.md | 5 + crates/egui_kittest/CHANGELOG.md | 13 + crates/epaint/CHANGELOG.md | 26 ++ crates/epaint_default_fonts/CHANGELOG.md | 4 + examples/confirm_exit/screenshot.png | 4 +- examples/custom_3d_glow/screenshot.png | 4 +- examples/custom_font/screenshot.png | 4 +- examples/custom_font_style/screenshot.png | 4 +- examples/custom_window_frame/screenshot.png | 4 +- examples/external_eventloop/screenshot.png | 3 + examples/hello_world_simple/screenshot.png | 4 +- examples/images/screenshot.png | 4 +- examples/keyboard_events/screenshot.png | 4 +- examples/popups/screenshot.png | 3 + examples/puffin_profiler/screenshot.png | 4 +- examples/user_attention/screenshot.png | 4 +- scripts/generate_example_screenshots.sh | 6 +- scripts/publish_crates.sh | 2 +- 27 files changed, 459 insertions(+), 66 deletions(-) create mode 100644 examples/external_eventloop/screenshot.png create mode 100644 examples/popups/screenshot.png mode change 100644 => 100755 scripts/publish_crates.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 09faecc52..385550843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,271 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 - Atoms, popups, and better SVG support +This is a big egui release, with several exciting new features! + +* _Atoms_ are new layout primitives in egui, for text and images +* Popups, tooltips and menus have undergone a complete rewrite +* Much improved SVG support +* Crisper graphics (especially text!) + +Let's dive in! + +### ⚛️ Atoms + +`egui::Atom` is the new, indivisible building blocks of egui (hence their name). +An `Atom` is an `enum` that can be either `WidgetText`, `Image`, or `Custom`. + +The new `AtomLayout` can be used within widgets to do basic layout. +The initial implementation is as minimal as possible, doing just enough to implement what `Button` could do before. +There is a new `IntoAtoms` trait that works with tuples of `Atom`s. Each atom can be customized with the `AtomExt` trait +which works on everything that implements `Into`, so e.g. `RichText` or `Image`. +So to create a `Button` with text and image you can now do: +```rs +let image = include_image!("my_icon.png").atom_size(Vec2::splat(12.0)); +ui.button((image, "Click me!")); +``` + +Anywhere you see `impl IntoAtoms` you can add any number of images and text, in any order. + +As of 0.32, we have ported the `Button`, `Checkbox`, `RadioButton` to use atoms +(meaning they support adding Atoms and are built on top of `AtomLayout`). +The `Button` implementation is not only more powerful now, but also much simpler, removing ~130 lines of layout math. + +In combination with `ui.read_response`, custom widgets are really simple now, here is a minimal button implementation: + +```rs +pub struct ALButton<'a> { + al: AtomLayout<'a>, +} + +impl<'a> ALButton<'a> { + pub fn new(content: impl IntoAtoms<'a>) -> Self { + Self { + al: AtomLayout::new(content.into_atoms()).sense(Sense::click()), + } + } +} + +impl<'a> Widget for ALButton<'a> { + fn ui(mut self, ui: &mut Ui) -> Response { + let Self { al } = self; + 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) + }); + + let al = al.frame( + Frame::new() + .inner_margin(ui.style().spacing.button_padding) + .fill(visuals.bg_fill) + .stroke(visuals.bg_stroke) + .corner_radius(visuals.corner_radius), + ); + + al.show(ui).response + } +} +``` + +You can even use `Atom::custom` to add custom content to Widgets. Here is a button in a button: + +https://github.com/user-attachments/assets/8c649784-dcc5-4979-85f8-e735b9cdd090 + +```rs +let custom_button_id = Id::new("custom_button"); +let response = Button::new(( + Atom::custom(custom_button_id, Vec2::splat(18.0)), + "Look at my mini button!", +)) +.atom_ui(ui); +if let Some(rect) = response.rect(custom_button_id) { + ui.put(rect, Button::new("🔎").frame_when_inactive(false)); +} +``` +Currently, you need to use `atom_ui` to get a `AtomResponse` which will have the `Rect` to use, but in the future +this could be streamlined, e.g. by adding a `AtomKind::Callback` or by passing the Rects back with `egui::Response`. + +Basing our widgets on `AtomLayout` also allowed us to improve `Response::intrinsic_size`, which will now report the +correct size even if widgets are truncated. `intrinsic_size` is the size that a non-wrapped, non-truncated, +non-justified version of the widget would have, and can be useful in advanced layout +calculations like [egui_flex](https://github.com/lucasmerlin/hello_egui/tree/main/crates/egui_flex). + +##### Details +* Add `AtomLayout`, abstracting layouting within widgets [#5830](https://github.com/emilk/egui/pull/5830) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `Galley::intrinsic_size` and use it in `AtomLayout` [#7146](https://github.com/emilk/egui/pull/7146) by [@lucasmerlin](https://github.com/lucasmerlin) + + +### ❕ Improved popups, tooltips, and menus + +Introduces a new `egui::Popup` api. Checkout the new demo on https://egui.rs: + +https://github.com/user-attachments/assets/74e45243-7d05-4fc3-b446-2387e1412c05 + +We introduced a new `RectAlign` helper to align a rect relative to an other rect. The `Popup` will by default try to find the best `RectAlign` based on the source widgets position (previously submenus would annoyingly overlap if at the edge of the window): + +https://github.com/user-attachments/assets/0c5adb6b-8310-4e0a-b936-646bb4ec02f7 + +`Tooltip` and `menu` have been rewritten based on the new `Popup` api. They are now compatible with each other, meaning you can just show a `ui.menu_button()` in any `Popup` to get a sub menu. There are now customizable `MenuButton` and `SubMenuButton` structs, to help with customizing your menu buttons. This means menus now also support `PopupCloseBehavior` so you can remove your `close_menu` calls from your click handlers! + +The old tooltip and popup apis have been ported to the new api so there should be very little breaking changes. The old menu is still around but deprecated. `ui.menu_button` etc now open the new menu, if you can't update to the new one immediately you can use the old buttons from the deprecated `egui::menu` menu. + +We also introduced `ui.close()` which closes the nearest container. So you can now conveniently close `Window`s, `Collapsible`s, `Modal`s and `Popup`s from within. To use this for your own containers, call `UiBuilder::closable` and then check for closing within that ui via `ui.should_close()`. + +##### Details +* Add `Popup` and `Tooltip`, unifying the previous behaviours [#5713](https://github.com/emilk/egui/pull/5713) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `Ui::close` and `Response::should_close` [#5729](https://github.com/emilk/egui/pull/5729) by [@lucasmerlin](https://github.com/lucasmerlin) +* ⚠️ Improved menu based on `egui::Popup` [#5716](https://github.com/emilk/egui/pull/5716) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add a toggle for the compact menu style [#5777](https://github.com/emilk/egui/pull/5777) by [@s-nie](https://github.com/s-nie) +* Use the new `Popup` API for the color picker button [#7137](https://github.com/emilk/egui/pull/7137) by [@lucasmerlin](https://github.com/lucasmerlin) +* ⚠️ Close popup if `Memory::keep_popup_open` isn't called [#5814](https://github.com/emilk/egui/pull/5814) by [@juancampa](https://github.com/juancampa) +* Fix tooltips sometimes changing position each frame [#7304](https://github.com/emilk/egui/pull/7304) by [@emilk](https://github.com/emilk) +* Change popup memory to be per-viewport [#6753](https://github.com/emilk/egui/pull/6753) by [@mkalte666](https://github.com/mkalte666) +* Deprecate `Memory::popup` API in favor of new `Popup` API [#7317](https://github.com/emilk/egui/pull/7317) by [@emilk](https://github.com/emilk) + + +### ▲ Improved SVG support +You can render SVG in egui with + +```rs +ui.add(egui::Image::new(egui::include_image!("icon.svg")); +``` + +(Requires the use of `egui_extras`, with the `svg` feature enabled and a call to [`install_image_loaders`](https://docs.rs/egui_extras/latest/egui_extras/fn.install_image_loaders.html)). + +Previously this would sometimes result in a blurry SVG, epecially if the `Image` was set to be dynamically scale based on the size of the `Ui` that contained it. Now SVG:s are always pixel-perfect, for truly scalable graphics. + +![svg-scaling](https://github.com/user-attachments/assets/faf63f0c-0ff7-47a0-a4cb-7210efeccb72) + +##### Details +* Support text in SVGs [#5979](https://github.com/emilk/egui/pull/5979) by [@cernec1999](https://github.com/cernec1999) +* Fix sometimes blurry SVGs [#7071](https://github.com/emilk/egui/pull/7071) by [@emilk](https://github.com/emilk) +* Fix incorrect color fringe colors on SVG:s [#7069](https://github.com/emilk/egui/pull/7069) by [@emilk](https://github.com/emilk) +* Make `Image::paint_at` pixel-perfect crisp for SVG images [#7078](https://github.com/emilk/egui/pull/7078) by [@emilk](https://github.com/emilk) + + +### ✨ Crisper graphics +Non-SVG icons are also rendered better, and text sharpness has been improved, especially in light mode. + +![image](https://github.com/user-attachments/assets/7f370aaf-886a-423c-8391-c378849b63ca) + +##### Details +* Improve text sharpness [#5838](https://github.com/emilk/egui/pull/5838) by [@emilk](https://github.com/emilk) +* Improve text rendering in light mode [#7290](https://github.com/emilk/egui/pull/7290) by [@emilk](https://github.com/emilk) +* Improve texture filtering by doing it in gamma space [#7311](https://github.com/emilk/egui/pull/7311) by [@emilk](https://github.com/emilk) +* Make text underline and strikethrough pixel perfect crisp [#5857](https://github.com/emilk/egui/pull/5857) by [@emilk](https://github.com/emilk) + +### Migration guide +We have some silently breaking changes (code compiles fine but behavior changed) that require special care: + +#### Menus close on click by default +- previously menus would only close on click outside +- either + - remove the `ui.close_menu()` calls from button click handlers since they are obsolete + - if the menu should stay open on clicks, change the `PopupCloseBehavior`: + ```rs + // Change this + ui.menu_button("Text", |ui| { /* Menu Content */ }); + // To this: + MenuButton::new("Text").config( + MenuConfig::default().close_behavior(PopupCloseBehavior::CloseOnClickOutside), + ).ui(ui, |ui| { /* Menu Content */ }); + ``` + You can also change the behavior only for a single SubMenu by using `SubMenuButton`, but by default it should be passed to any submenus when using `MenuButton`. + +#### `Memory::is_popup_open` api now requires calls to `Memory::keep_popup_open` +- The popup will immediately close if `keep_popup_open` is not called. +- It's recommended to use the new `Popup` api which handles this for you. +- If you can't switch to the new api for some reason, update the code to call `keep_popup_open`: + ```rs + if ui.memory(|mem| mem.is_popup_open(popup_id)) { + ui.memory_mut(|mem| mem.keep_popup_open(popup_id)); // <- add this line + let area_response = Area::new(popup_id).show(...) + } + ``` + +### ⭐ Other improvements +* Add `Label::show_tooltip_when_elided` [#5710](https://github.com/emilk/egui/pull/5710) by [@bryceberger](https://github.com/bryceberger) +* Deprecate `Ui::allocate_new_ui` in favor of `Ui::scope_builder` [#5764](https://github.com/emilk/egui/pull/5764) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `expand_bg` to customize size of text background [#5365](https://github.com/emilk/egui/pull/5365) by [@MeGaGiGaGon](https://github.com/MeGaGiGaGon) +* Add assert messages and print bad argument values in asserts [#5216](https://github.com/emilk/egui/pull/5216) by [@bircni](https://github.com/bircni) +* Use `TextBuffer` for `layouter` in `TextEdit` instead of `&str` [#5712](https://github.com/emilk/egui/pull/5712) by [@kernelkind](https://github.com/kernelkind) +* Add a `Slider::update_while_editing(bool)` API [#5978](https://github.com/emilk/egui/pull/5978) by [@mbernat](https://github.com/mbernat) +* Add `Scene::drag_pan_buttons` option. Allows specifying which pointer buttons pan the scene by dragging [#5892](https://github.com/emilk/egui/pull/5892) by [@mitchmindtree](https://github.com/mitchmindtree) +* Add `Scene::sense` to customize how `Scene` responds to user input [#5893](https://github.com/emilk/egui/pull/5893) by [@mitchmindtree](https://github.com/mitchmindtree) +* Rework `TextEdit` arrow navigation to handle Unicode graphemes [#5812](https://github.com/emilk/egui/pull/5812) by [@MStarha](https://github.com/MStarha) +* `ScrollArea` improvements for user configurability [#5443](https://github.com/emilk/egui/pull/5443) by [@MStarha](https://github.com/MStarha) +* Add `Response::clicked_with_open_in_background` [#7093](https://github.com/emilk/egui/pull/7093) by [@emilk](https://github.com/emilk) +* Add `Modifiers::matches_any` [#7123](https://github.com/emilk/egui/pull/7123) by [@emilk](https://github.com/emilk) +* Add `Context::format_modifiers` [#7125](https://github.com/emilk/egui/pull/7125) by [@emilk](https://github.com/emilk) +* Add `OperatingSystem::is_mac` [#7122](https://github.com/emilk/egui/pull/7122) by [@emilk](https://github.com/emilk) +* Support vertical-only scrolling by holding down Alt [#7124](https://github.com/emilk/egui/pull/7124) by [@emilk](https://github.com/emilk) +* Support for back-button on Android [#7073](https://github.com/emilk/egui/pull/7073) by [@ardocrat](https://github.com/ardocrat) +* Select all text in DragValue when gaining focus via keyboard [#7107](https://github.com/emilk/egui/pull/7107) by [@Azkellas](https://github.com/Azkellas) +* Add `Context::current_pass_index` [#7276](https://github.com/emilk/egui/pull/7276) by [@emilk](https://github.com/emilk) +* Add `Context::cumulative_frame_nr` [#7278](https://github.com/emilk/egui/pull/7278) by [@emilk](https://github.com/emilk) +* Add `Visuals::text_edit_bg_color` [#7283](https://github.com/emilk/egui/pull/7283) by [@emilk](https://github.com/emilk) +* Add `Visuals::weak_text_alpha` and `weak_text_color` [#7285](https://github.com/emilk/egui/pull/7285) by [@emilk](https://github.com/emilk) +* Add support for scrolling via accesskit / kittest [#7286](https://github.com/emilk/egui/pull/7286) by [@lucasmerlin](https://github.com/lucasmerlin) +* Update area struct to allow force resizing [#7114](https://github.com/emilk/egui/pull/7114) by [@blackberryfloat](https://github.com/blackberryfloat) +* Add `egui::Sides` `shrink_left` / `shrink_right` [#7295](https://github.com/emilk/egui/pull/7295) by [@lucasmerlin](https://github.com/lucasmerlin) +* Set intrinsic size for Label [#7328](https://github.com/emilk/egui/pull/7328) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🔧 Changed +* Raise MSRV to 1.85 [#6848](https://github.com/emilk/egui/pull/6848) by [@torokati44](https://github.com/torokati44), [#7279](https://github.com/emilk/egui/pull/7279) by [@emilk](https://github.com/emilk) +* Set `hint_text` in `WidgetInfo` [#5724](https://github.com/emilk/egui/pull/5724) by [@bircni](https://github.com/bircni) +* Implement `Default` for `ThemePreference` [#5702](https://github.com/emilk/egui/pull/5702) by [@MichaelGrupp](https://github.com/MichaelGrupp) +* Align `available_rect` docs with the new reality after #4590 [#5701](https://github.com/emilk/egui/pull/5701) by [@podusowski](https://github.com/podusowski) +* Clarify platform-specific details for `Viewport` positioning [#5715](https://github.com/emilk/egui/pull/5715) by [@aspiringLich](https://github.com/aspiringLich) +* Simplify the text cursor API [#5785](https://github.com/emilk/egui/pull/5785) by [@valadaptive](https://github.com/valadaptive) +* Bump accesskit to 0.19 [#7040](https://github.com/emilk/egui/pull/7040) by [@valadaptive](https://github.com/valadaptive) +* Better define the meaning of `SizeHint` [#7079](https://github.com/emilk/egui/pull/7079) by [@emilk](https://github.com/emilk) +* Move all input-related options into `InputOptions` [#7121](https://github.com/emilk/egui/pull/7121) by [@emilk](https://github.com/emilk) +* `Button` inherits the `alt_text` of the `Image` in it, if any [#7136](https://github.com/emilk/egui/pull/7136) by [@emilk](https://github.com/emilk) +* Change API of `Tooltip` slightly [#7151](https://github.com/emilk/egui/pull/7151) by [@emilk](https://github.com/emilk) +* Use Rust edition 2024 [#7280](https://github.com/emilk/egui/pull/7280) by [@emilk](https://github.com/emilk) +* Change `ui.disable()` to modify opacity [#7282](https://github.com/emilk/egui/pull/7282) by [@emilk](https://github.com/emilk) +* Make the font atlas use a color image [#7298](https://github.com/emilk/egui/pull/7298) by [@valadaptive](https://github.com/valadaptive) +* Implement `BitOr` and `BitOrAssign` for `Rect` [#7319](https://github.com/emilk/egui/pull/7319) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🔥 Removed +* Remove things that have been deprecated for over a year [#7099](https://github.com/emilk/egui/pull/7099) by [@emilk](https://github.com/emilk) +* Remove `SelectableLabel` [#7277](https://github.com/emilk/egui/pull/7277) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🐛 Fixed +* `Scene`: make `scene_rect` full size on reset [#5801](https://github.com/emilk/egui/pull/5801) by [@graydenshand](https://github.com/graydenshand) +* `Scene`: `TextEdit` selection when placed in a `Scene` [#5791](https://github.com/emilk/egui/pull/5791) by [@karhu](https://github.com/karhu) +* `Scene`: Set transform layer before calling user content [#5884](https://github.com/emilk/egui/pull/5884) by [@mitchmindtree](https://github.com/mitchmindtree) +* Fix: transform `TextShape` underline width [#5865](https://github.com/emilk/egui/pull/5865) by [@emilk](https://github.com/emilk) +* Fix missing repaint after `consume_key` [#7134](https://github.com/emilk/egui/pull/7134) by [@lucasmerlin](https://github.com/lucasmerlin) +* Update `emoji-icon-font` with fix for fullwidth latin characters [#7067](https://github.com/emilk/egui/pull/7067) by [@emilk](https://github.com/emilk) +* Mark all keys as released if the app loses focus [#5743](https://github.com/emilk/egui/pull/5743) by [@emilk](https://github.com/emilk) +* Fix scroll handle extending outside of `ScrollArea` [#5286](https://github.com/emilk/egui/pull/5286) by [@gilbertoalexsantos](https://github.com/gilbertoalexsantos) +* Fix `Response::clicked_elsewhere` not returning `true` sometimes [#5798](https://github.com/emilk/egui/pull/5798) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix kinetic scrolling on touch devices [#5778](https://github.com/emilk/egui/pull/5778) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix `DragValue` expansion when editing [#5809](https://github.com/emilk/egui/pull/5809) by [@MStarha](https://github.com/MStarha) +* Fix disabled `DragValue` eating focus, causing focus to reset [#5826](https://github.com/emilk/egui/pull/5826) by [@KonaeAkira](https://github.com/KonaeAkira) +* Fix semi-transparent colors appearing too bright [#5824](https://github.com/emilk/egui/pull/5824) by [@emilk](https://github.com/emilk) +* Improve drag-to-select text (add margins) [#5797](https://github.com/emilk/egui/pull/5797) by [@hankjordan](https://github.com/hankjordan) +* Fix bug in pointer movement detection [#5329](https://github.com/emilk/egui/pull/5329) by [@rustbasic](https://github.com/rustbasic) +* Protect against NaN in hit-test code [#6851](https://github.com/emilk/egui/pull/6851) by [@Skgland](https://github.com/Skgland) +* Fix image button panicking with tiny `available_space` [#6900](https://github.com/emilk/egui/pull/6900) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix links and text selection in horizontal_wrapped layout [#6905](https://github.com/emilk/egui/pull/6905) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix `leading_space` sometimes being ignored during paragraph splitting [#7031](https://github.com/emilk/egui/pull/7031) by [@afishhh](https://github.com/afishhh) +* Fix typo in deprecation message for `ComboBox::from_id_source` [#7055](https://github.com/emilk/egui/pull/7055) by [@aelmizeb](https://github.com/aelmizeb) +* Bug fix: make sure `end_pass` is called for all loaders [#7072](https://github.com/emilk/egui/pull/7072) by [@emilk](https://github.com/emilk) +* Report image alt text as text if widget contains no other text [#7142](https://github.com/emilk/egui/pull/7142) by [@lucasmerlin](https://github.com/lucasmerlin) +* Slider: move by at least the next increment when using fixed_decimals [#7066](https://github.com/emilk/egui/pull/7066) by [@0x53A](https://github.com/0x53A) +* Fix crash when using infinite widgets [#7296](https://github.com/emilk/egui/pull/7296) by [@emilk](https://github.com/emilk) +* Fix `debug_assert` triggered by `menu`/`intersect_ray` [#7299](https://github.com/emilk/egui/pull/7299) by [@emilk](https://github.com/emilk) +* Change `Rect::area` to return zero for negative rectangles [#7305](https://github.com/emilk/egui/pull/7305) by [@emilk](https://github.com/emilk) + +### 🚀 Performance +* Optimize editing long text by caching each paragraph [#5411](https://github.com/emilk/egui/pull/5411) by [@afishhh](https://github.com/afishhh) +* Make `WidgetText` smaller and faster [#6903](https://github.com/emilk/egui/pull/6903) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 0.31.1 - 2025-03-05 * Fix sizing bug in `TextEdit::singleline` [#5640](https://github.com/emilk/egui/pull/5640) by [@IaVashik](https://github.com/IaVashik) * Fix panic when rendering thin textured rectangles [#5692](https://github.com/emilk/egui/pull/5692) by [@PPakalns](https://github.com/PPakalns) diff --git a/Cargo.lock b/Cargo.lock index e0830058e..b1ed18559 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1197,7 +1197,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.31.1" +version = "0.32.0" dependencies = [ "bytemuck", "cint", @@ -1209,7 +1209,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.31.1" +version = "0.32.0" dependencies = [ "ahash", "bytemuck", @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.31.1" +version = "0.32.0" dependencies = [ "accesskit", "ahash", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.31.1" +version = "0.32.0" dependencies = [ "ahash", "bytemuck", @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.31.1" +version = "0.32.0" dependencies = [ "accesskit_winit", "ahash", @@ -1308,7 +1308,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.31.1" +version = "0.32.0" dependencies = [ "bytemuck", "chrono", @@ -1336,7 +1336,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.31.1" +version = "0.32.0" dependencies = [ "chrono", "criterion", @@ -1353,7 +1353,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.31.1" +version = "0.32.0" dependencies = [ "ahash", "chrono", @@ -1372,7 +1372,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.31.1" +version = "0.32.0" dependencies = [ "ahash", "bytemuck", @@ -1392,7 +1392,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.31.1" +version = "0.32.0" dependencies = [ "dify", "document-features", @@ -1408,7 +1408,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.31.1" +version = "0.32.0" dependencies = [ "egui", "egui_extras", @@ -1438,7 +1438,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.31.1" +version = "0.32.0" dependencies = [ "bytemuck", "document-features", @@ -1535,7 +1535,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.31.1" +version = "0.32.0" dependencies = [ "ab_glyph", "ahash", @@ -1558,7 +1558,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.31.1" +version = "0.32.0" [[package]] name = "equivalent" @@ -3236,7 +3236,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.31.1" +version = "0.32.0" dependencies = [ "eframe", "env_logger", @@ -5481,7 +5481,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.31.1" +version = "0.32.0" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index c4ea5258c..498aefc08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.85" -version = "0.31.1" +version = "0.32.0" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.31.1", path = "crates/emath", default-features = false } -ecolor = { version = "0.31.1", path = "crates/ecolor", default-features = false } -epaint = { version = "0.31.1", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.31.1", path = "crates/epaint_default_fonts" } -egui = { version = "0.31.1", path = "crates/egui", default-features = false } -egui-winit = { version = "0.31.1", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.31.1", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.31.1", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.31.1", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.31.1", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.31.1", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.31.1", path = "crates/eframe", default-features = false } +emath = { version = "0.32.0", path = "crates/emath", default-features = false } +ecolor = { version = "0.32.0", path = "crates/ecolor", default-features = false } +epaint = { version = "0.32.0", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.32.0", path = "crates/epaint_default_fonts" } +egui = { version = "0.32.0", path = "crates/egui", default-features = false } +egui-winit = { version = "0.32.0", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.32.0", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.32.0", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.32.0", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.32.0", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.32.0", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.32.0", path = "crates/eframe", default-features = false } accesskit = "0.19.0" accesskit_winit = "0.27" diff --git a/RELEASES.md b/RELEASES.md index 14db36403..cd94575ea 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -25,8 +25,8 @@ We don't update the MSRV in a patch release, unless we really, really need to. * [ ] copy this checklist to a new egui issue, called "Release 0.xx.y" * [ ] close all issues in the milestone for this release -## Patch release -* [ ] make a branch off of the latest release +## Special steps for patch release +* [ ] make a branch off of the _latest_ release * [ ] cherry-pick what you want to release * [ ] run `cargo semver-checks` @@ -49,6 +49,7 @@ We don't update the MSRV in a patch release, unless we really, really need to. ## Preparation * [ ] make sure there are no important unmerged PRs +* [ ] Create a branch called `release-0.xx.0` and open a PR for it * [ ] run `scripts/generate_example_screenshots.sh` if needed * [ ] write a short release note that fits in a bluesky post * [ ] record gif for `CHANGELOG.md` release note (and later bluesky post) @@ -56,21 +57,22 @@ We don't update the MSRV in a patch release, unless we really, really need to. * [ ] run `scripts/generate_changelog.py --version 0.x.0 --write` * [ ] read changelogs and clean them up if needed * [ ] write a good intro with highlight for the main changelog -* [ ] bump version numbers in workspace `Cargo.toml` +* [ ] run `typos` ## Actual release -I usually do this all on the `main` branch, but doing it in a release branch is also fine, as long as you remember to merge it into `main` later. - -* [ ] Run `typos` -* [ ] `git commit -m 'Release 0.x.0 - '` -* [ ] Publish the crates by running `scripts/publish_crates.sh` +* [ ] bump version numbers in workspace `Cargo.toml` +* [ ] check that CI for the PR is green +* [ ] publish the crates by running `scripts/publish_crates.sh` +* [ ] merge release PR as `Release 0.x.0 - ` +* [ ] Check out the release commit locally * [ ] `git tag -a 0.x.0 -m 'Release 0.x.0 - '` * [ ] `git pull --tags ; git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force ; git push --tags` -* [ ] merge release PR or push to `main` * [ ] check that CI is green * [ ] do a GitHub release: https://github.com/emilk/egui/releases/new - * Follow the format of the last release -* [ ] wait for documentation to build: https://docs.rs/releases/queue + * follow the format of the last release +* [ ] wait for the documentation build to finish: https://docs.rs/releases/queue + * [ ] https://docs.rs/egui/ works + * [ ] https://docs.rs/eframe/ works ## Announcements @@ -91,6 +93,6 @@ I usually do this all on the `main` branch, but doing it in a release branch is ## Finally -* [ ] Close the milestone -* [ ] Close this issue -* [ ] Improve `RELEASES.md` with what you learned this time around +* [ ] close the milestone +* [ ] close this issue +* [ ] improve `RELEASES.md` with what you learned this time around diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 88d510dba..535f8b18e 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,12 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 +* Fix semi-transparent colors appearing too bright [#5824](https://github.com/emilk/egui/pull/5824) by [@emilk](https://github.com/emilk) +* Remove things that have been deprecated for over a year [#7099](https://github.com/emilk/egui/pull/7099) by [@emilk](https://github.com/emilk) +* Make `Hsva` derive serde [#7132](https://github.com/emilk/egui/pull/7132) by [@bircni](https://github.com/bircni) + + ## 0.31.1 - 2025-03-05 Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index df1697281..aa0b3436c 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,33 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 +### ⭐ Added +* Add pointer events and focus handling for apps run in a Shadow DOM [#5627](https://github.com/emilk/egui/pull/5627) by [@xxvvii](https://github.com/xxvvii) +* MacOS: Add `movable_by_window_background` option to viewport [#5412](https://github.com/emilk/egui/pull/5412) by [@jim-ec](https://github.com/jim-ec) +* Add macOS-specific `has_shadow` and `with_has_shadow` to ViewportBuilder [#6850](https://github.com/emilk/egui/pull/6850) by [@gaelanmcmillan](https://github.com/gaelanmcmillan) +* Add external eventloop support [#6750](https://github.com/emilk/egui/pull/6750) by [@wpbrown](https://github.com/wpbrown) + +### 🔧 Changed +* Update MSRV to 1.85 [#7279](https://github.com/emilk/egui/pull/7279) by [@emilk](https://github.com/emilk) +* Use Rust edition 2024 [#7280](https://github.com/emilk/egui/pull/7280) by [@emilk](https://github.com/emilk) +* Rename `should_propagate_event` and add `should_prevent_default` [#5779](https://github.com/emilk/egui/pull/5779) by [@th0rex](https://github.com/th0rex) +* Clarify platform-specific details for `Viewport` positioning [#5715](https://github.com/emilk/egui/pull/5715) by [@aspiringLich](https://github.com/aspiringLich) +* Enhance stability on Windows [#5723](https://github.com/emilk/egui/pull/5723) by [@rustbasic](https://github.com/rustbasic) +* Set `web-sys` min version to `0.3.73` [#5862](https://github.com/emilk/egui/pull/5862) by [@wareya](https://github.com/wareya) +* Bump `ron` to `0.10.1` [#6861](https://github.com/emilk/egui/pull/6861) by [@torokati44](https://github.com/torokati44) +* Disallow `accesskit` on Android NativeActivity, making `hello_android` working again [#6855](https://github.com/emilk/egui/pull/6855) by [@podusowski](https://github.com/podusowski) +* Respect and detect `prefers-color-scheme: no-preference` [#7293](https://github.com/emilk/egui/pull/7293) by [@emilk](https://github.com/emilk) + +### 🐛 Fixed +* Mark all keys as up if the app loses focus [#5743](https://github.com/emilk/egui/pull/5743) by [@emilk](https://github.com/emilk) +* Fix text input on Android [#5759](https://github.com/emilk/egui/pull/5759) by [@StratusFearMe21](https://github.com/StratusFearMe21) +* Fix text distortion on mobile devices/browsers with `glow` backend [#6893](https://github.com/emilk/egui/pull/6893) by [@wareya](https://github.com/wareya) +* Workaround libpng crash on macOS by not creating `NSImage` from png data [#7252](https://github.com/emilk/egui/pull/7252) by [@Wumpf](https://github.com/Wumpf) +* Fix incorrect window sizes for non-resizable windows on Wayland [#7103](https://github.com/emilk/egui/pull/7103) by [@GoldsteinE](https://github.com/GoldsteinE) +* Web: only consume copy/cut events if the canvas has focus [#7270](https://github.com/emilk/egui/pull/7270) by [@emilk](https://github.com/emilk) + + ## 0.31.1 - 2025-03-05 Nothing new diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 7209c91a1..eda975de2 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,12 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 +* Update to wgpu 25 [#6744](https://github.com/emilk/egui/pull/6744) by [@torokati44](https://github.com/torokati44) +* Free textures after submitting queue instead of before with wgpu renderer on Web [#7291](https://github.com/emilk/egui/pull/7291) by [@Wumpf](https://github.com/Wumpf) +* Improve texture filtering by doing it in gamma space [#7311](https://github.com/emilk/egui/pull/7311) by [@emilk](https://github.com/emilk) + + ## 0.31.1 - 2025-03-05 Nothing new diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index efdc9c23d..da51386e9 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,14 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 +* Mark all keys as released if the app loses focus [#5743](https://github.com/emilk/egui/pull/5743) by [@emilk](https://github.com/emilk) +* Fix text input on Android [#5759](https://github.com/emilk/egui/pull/5759) by [@StratusFearMe21](https://github.com/StratusFearMe21) +* Add macOS-specific `has_shadow` and `with_has_shadow` to ViewportBuilder [#6850](https://github.com/emilk/egui/pull/6850) by [@gaelanmcmillan](https://github.com/gaelanmcmillan) +* Support for back-button on Android [#7073](https://github.com/emilk/egui/pull/7073) by [@ardocrat](https://github.com/ardocrat) +* Fix incorrect window sizes for non-resizable windows on Wayland [#7103](https://github.com/emilk/egui/pull/7103) by [@GoldsteinE](https://github.com/GoldsteinE) + + ## 0.31.1 - 2025-03-05 Nothing new diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index bfadf84fb..2c2ae8ba4 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,29 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 - Improved SVG support +### ⭐ Added +* Allow loading multi-MIME formats using the image_loader [#5769](https://github.com/emilk/egui/pull/5769) by [@MYDIH](https://github.com/MYDIH) +* Make ImageLoader use background thread [#5394](https://github.com/emilk/egui/pull/5394) by [@bircni](https://github.com/bircni) +* Add overline option for Table rows [#5637](https://github.com/emilk/egui/pull/5637) by [@akx](https://github.com/akx) +* Support text in SVGs [#5979](https://github.com/emilk/egui/pull/5979) by [@cernec1999](https://github.com/cernec1999) +* Enable setting DatePickerButton start and end year explicitly [#7061](https://github.com/emilk/egui/pull/7061) by [@zachbateman](https://github.com/zachbateman) +* Support custom syntect settings in syntax highlighter [#7084](https://github.com/emilk/egui/pull/7084) by [@mkeeter](https://github.com/mkeeter) + +### 🔧 Changed +* Use enum-map serde feature only when serde is enabled [#5748](https://github.com/emilk/egui/pull/5748) by [@tyssyt](https://github.com/tyssyt) +* Better define the meaning of `SizeHint` [#7079](https://github.com/emilk/egui/pull/7079) by [@emilk](https://github.com/emilk) + +### 🔥 Removed +* Remove things that have been deprecated for over a year [#7099](https://github.com/emilk/egui/pull/7099) by [@emilk](https://github.com/emilk) + +### 🐛 Fixed +* Refactor MIME type support detection in image loader to allow for deferred handling and appended encoding info [#5686](https://github.com/emilk/egui/pull/5686) by [@markusdd](https://github.com/markusdd) +* Fix incorrect color fringe colors on SVG:s [#7069](https://github.com/emilk/egui/pull/7069) by [@emilk](https://github.com/emilk) +* Fix sometimes blurry SVGs [#7071](https://github.com/emilk/egui/pull/7071) by [@emilk](https://github.com/emilk) +* Fix crash in `egui_extras::FileLoader` after `forget_image` [#6995](https://github.com/emilk/egui/pull/6995) by [@bircni](https://github.com/bircni) + + ## 0.31.1 - 2025-03-05 * Fix image_loader for animated image types [#5688](https://github.com/emilk/egui/pull/5688) by [@BSteffaniak](https://github.com/BSteffaniak) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 86c77c942..4969b5569 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,11 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 +### ⭐ Added +* Add `ImageLoader::has_pending` and `wait_for_pending_images` [#7030](https://github.com/emilk/egui/pull/7030) by [@lucasmerlin](https://github.com/lucasmerlin) +* Create custom `egui_kittest::Node` [#7138](https://github.com/emilk/egui/pull/7138) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `HarnessBuilder::theme` [#7289](https://github.com/emilk/egui/pull/7289) by [@emilk](https://github.com/emilk) +* Add support for scrolling via accesskit / kittest [#7286](https://github.com/emilk/egui/pull/7286) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `failed_pixel_count_threshold` [#7092](https://github.com/emilk/egui/pull/7092) by [@bircni](https://github.com/bircni) + +### 🔧 Changed +* More ergonomic functions taking `Impl Into` [#7307](https://github.com/emilk/egui/pull/7307) by [@emlik](https://github.com/emilk) +* Update kittest to 0.2 [#7332](https://github.com/emilk/egui/pull/7332) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 0.31.1 - 2025-03-05 * Fix modifiers not working in kittest [#5693](https://github.com/emilk/egui/pull/5693) by [@lucasmerlin](https://github.com/lucasmerlin) * Enable all features for egui_kittest docs [#5711](https://github.com/emilk/egui/pull/5711) by [@YgorSouza](https://github.com/YgorSouza) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 2c2cfcc1c..bff5336a4 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,32 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 +### ⭐ Added +* Impl AsRef<[u8]> for FontData [#5757](https://github.com/emilk/egui/pull/5757) by [@StratusFearMe21](https://github.com/StratusFearMe21) +* Add `expand_bg` to customize size of text background [#5365](https://github.com/emilk/egui/pull/5365) by [@MeGaGiGaGon](https://github.com/MeGaGiGaGon) +* Add anchored text rotation method, and clarify related docs [#7130](https://github.com/emilk/egui/pull/7130) by [@pmarks](https://github.com/pmarks) +* Add `Galley::intrinsic_size` [#7146](https://github.com/emilk/egui/pull/7146) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🔧 Changed +* Fix semi-transparent colors appearing too bright [#5824](https://github.com/emilk/egui/pull/5824) by [@emilk](https://github.com/emilk) +* Improve text sharpness [#5838](https://github.com/emilk/egui/pull/5838) by [@emilk](https://github.com/emilk) +* Improve text rendering in light mode [#7290](https://github.com/emilk/egui/pull/7290) by [@emilk](https://github.com/emilk) +* Make text underline and strikethrough pixel perfect crisp [#5857](https://github.com/emilk/egui/pull/5857) by [@emilk](https://github.com/emilk) +* Update `emoji-icon-font` with fix for fullwidth latin characters [#7067](https://github.com/emilk/egui/pull/7067) by [@emilk](https://github.com/emilk) +* Add assert messages and print bad argument values in asserts [#5216](https://github.com/emilk/egui/pull/5216) by [@bircni](https://github.com/bircni) + +### 🔥 Removed +* Remove things that have been deprecated for over a year [#7099](https://github.com/emilk/egui/pull/7099) by [@emilk](https://github.com/emilk) + +### 🐛 Fixed +* Fix: transform `TextShape` underline width [#5865](https://github.com/emilk/egui/pull/5865) by [@emilk](https://github.com/emilk) +* Fix `visual_bounding_rect` for rotated text [#7050](https://github.com/emilk/egui/pull/7050) by [@pmarks](https://github.com/pmarks) + +### 🚀 Performance +* Optimize editing long text by caching each paragraph [#5411](https://github.com/emilk/egui/pull/5411) by [@afishhh](https://github.com/afishhh) + + ## 0.31.1 - 2025-03-05 * Fix panic when rendering thin textured rectangles [#5692](https://github.com/emilk/egui/pull/5692) by [@PPakalns](https://github.com/PPakalns) diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index 709733613..f61b14cda 100644 --- a/crates/epaint_default_fonts/CHANGELOG.md +++ b/crates/epaint_default_fonts/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.0 - 2025-07-10 +Nothing new + + ## 0.31.1 - 2025-03-05 Nothing new diff --git a/examples/confirm_exit/screenshot.png b/examples/confirm_exit/screenshot.png index 854955cf7..33debffe2 100644 --- a/examples/confirm_exit/screenshot.png +++ b/examples/confirm_exit/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3122591a76a063f1db0b88b54ecc4afe012ee27a1404c0948d0b9d639aeeece6 -size 3124 +oid sha256:0175461bbd86fffaad3538ea8dcec5001c7511aa201aa37b7736e7d2010b1522 +size 3483 diff --git a/examples/custom_3d_glow/screenshot.png b/examples/custom_3d_glow/screenshot.png index c3907a5ec..19bf33d64 100644 --- a/examples/custom_3d_glow/screenshot.png +++ b/examples/custom_3d_glow/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4dd5ffcf5a530f6fbe959fd74e2f5b4aeaee335baf79ad1cde8e42c34d84156c -size 24590 +oid sha256:b5f36e1df27007b19cf56771145a667b4410dc9f4697afe629a2b42518640d62 +size 26531 diff --git a/examples/custom_font/screenshot.png b/examples/custom_font/screenshot.png index 7e6edc3dd..e7a20348d 100644 --- a/examples/custom_font/screenshot.png +++ b/examples/custom_font/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0ab12a55c0d87f044ef7675f5b94b39977f2c5d3a2ea74eb843dfc85d5b9f31 -size 5645 +oid sha256:a5bdb725a17bb6f8871ee95ae8cf46e55055083b0be14716cccd17615c863c81 +size 6179 diff --git a/examples/custom_font_style/screenshot.png b/examples/custom_font_style/screenshot.png index bb7727a1d..09f76bd13 100644 --- a/examples/custom_font_style/screenshot.png +++ b/examples/custom_font_style/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d443ef30cf12d2c4367135fd663270ed46f4864af2f3aae424610be86c73197 -size 66193 +oid sha256:044417e2259f58b8b71c44c49a60fe928e7faac1616d76eba7e156764c1bc2b2 +size 88583 diff --git a/examples/custom_window_frame/screenshot.png b/examples/custom_window_frame/screenshot.png index 92e11a695..bb327544a 100644 --- a/examples/custom_window_frame/screenshot.png +++ b/examples/custom_window_frame/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfdf77d119ae7926f52ac781455c4b1574eea53276069614738ba8b13b9921ea -size 6024 +oid sha256:1eba63346816dfbcf90c6b87e8df8b2de989d33359fda91041a813c474f85cc1 +size 17981 diff --git a/examples/external_eventloop/screenshot.png b/examples/external_eventloop/screenshot.png new file mode 100644 index 000000000..1236f4f84 --- /dev/null +++ b/examples/external_eventloop/screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f2630b0cfe5b044698e9f9533752e23a130e1984dfa0123645bc040d412dba5 +size 8636 diff --git a/examples/hello_world_simple/screenshot.png b/examples/hello_world_simple/screenshot.png index 8e5d56570..546366aea 100644 --- a/examples/hello_world_simple/screenshot.png +++ b/examples/hello_world_simple/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bb13d0cb819d30ffbcd12d9327589a1e3ca226943cb381fa17eccfb8f7c06fb -size 3229 +oid sha256:7be893df63405f3e86d1eb280083914a7ac63bb21fb8dce47e0476fb48ec9bd8 +size 8293 diff --git a/examples/images/screenshot.png b/examples/images/screenshot.png index 833b6565b..8dd58f2bd 100644 --- a/examples/images/screenshot.png +++ b/examples/images/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a836741d52e1972b2047cefaabf59f601637d430d4b41bf6407ebda4f7931dac -size 273450 +oid sha256:329972caa792f9e3a7caf207f41c35e1e26f0d09067e9282bf6538d560f13f7c +size 79617 diff --git a/examples/keyboard_events/screenshot.png b/examples/keyboard_events/screenshot.png index ed6ba6837..3048a2c5c 100644 --- a/examples/keyboard_events/screenshot.png +++ b/examples/keyboard_events/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cf53e0da26c4295bafe9cbb9ea0ad8c50933e29eb67467c3a11783697ec5494 -size 7525 +oid sha256:45fce5a660dbca5a2ecca2fa93fa99b67dce7b03ad583fb5d44d2642d052a80c +size 8603 diff --git a/examples/popups/screenshot.png b/examples/popups/screenshot.png new file mode 100644 index 000000000..54b1df8c7 --- /dev/null +++ b/examples/popups/screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e082670ac9daaee83e593c5dab0e3fccc6f6a4b824071f4983f7f51da144cad9 +size 17893 diff --git a/examples/puffin_profiler/screenshot.png b/examples/puffin_profiler/screenshot.png index 319a34730..fba55018a 100644 --- a/examples/puffin_profiler/screenshot.png +++ b/examples/puffin_profiler/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:610b003b4c7715751d2d3753d304e2c5a0af17c9259fa24f21a97b33b9d0c3c7 -size 8549 +oid sha256:0787ac28dc8a0ca979f9be5f20c3fb1e2e1c5733add61d201bfe965590afe062 +size 25999 diff --git a/examples/user_attention/screenshot.png b/examples/user_attention/screenshot.png index 015bdf7ec..eaaeda498 100644 --- a/examples/user_attention/screenshot.png +++ b/examples/user_attention/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85a508fa7d16d9bc51f38d3531b30f024d8888f7f74bf2f351453fd13d13e0aa -size 5928 +oid sha256:1d723a89d05be6e254846b8999041c54d5142baefcb8ad8c1effa5a50bae7791 +size 6578 diff --git a/scripts/generate_example_screenshots.sh b/scripts/generate_example_screenshots.sh index 91e338d31..e2ed2361e 100755 --- a/scripts/generate_example_screenshots.sh +++ b/scripts/generate_example_screenshots.sh @@ -7,10 +7,12 @@ cd "$script_path/.." cd examples for EXAMPLE_NAME in $(ls -1d */ | sed 's/\/$//'); do - if [ ${EXAMPLE_NAME} != "hello_world_par" ] && # screenshot not implemented for wgpu backend + if [ ${EXAMPLE_NAME} != "external_eventloop_async" ] && + [ ${EXAMPLE_NAME} != "hello_android" ] && + [ ${EXAMPLE_NAME} != "hello_world_par" ] && # screenshot not implemented for wgpu backend [ ${EXAMPLE_NAME} != "multiple_viewports" ] && - [ ${EXAMPLE_NAME} != "screenshot" ] && [ ${EXAMPLE_NAME} != "puffin_viewer" ] && + [ ${EXAMPLE_NAME} != "screenshot" ] && [ ${EXAMPLE_NAME} != "serial_windows" ]; then echo "" diff --git a/scripts/publish_crates.sh b/scripts/publish_crates.sh old mode 100644 new mode 100755 index 705b88c41..c2f2fc24e --- a/scripts/publish_crates.sh +++ b/scripts/publish_crates.sh @@ -6,9 +6,9 @@ (cd crates/epaint && cargo publish --quiet) && echo "✅ epaint" (cd crates/egui && cargo publish --quiet) && echo "✅ egui" (cd crates/egui-winit && cargo publish --quiet) && echo "✅ egui-winit" +(cd crates/egui_glow && cargo publish --quiet) && echo "✅ egui_glow" (cd crates/egui-wgpu && cargo publish --quiet) && echo "✅ egui-wgpu" (cd crates/eframe && cargo publish --quiet) && echo "✅ eframe" (cd crates/egui_kittest && cargo publish --quiet) && echo "✅ egui_kittest" (cd crates/egui_extras && cargo publish --quiet) && echo "✅ egui_extras" (cd crates/egui_demo_lib && cargo publish --quiet) && echo "✅ egui_demo_lib" -(cd crates/egui_glow && cargo publish --quiet) && echo "✅ egui_glow" From fdcaff8465eac8db8cc1ebbcbb9b97e0791a8363 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Jul 2025 17:06:31 +0200 Subject: [PATCH 56/56] Improve RELEASES.md --- RELEASES.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index cd94575ea..1634ad13d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -63,11 +63,10 @@ We don't update the MSRV in a patch release, unless we really, really need to. * [ ] bump version numbers in workspace `Cargo.toml` * [ ] check that CI for the PR is green * [ ] publish the crates by running `scripts/publish_crates.sh` -* [ ] merge release PR as `Release 0.x.0 - ` -* [ ] Check out the release commit locally * [ ] `git tag -a 0.x.0 -m 'Release 0.x.0 - '` * [ ] `git pull --tags ; git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force ; git push --tags` -* [ ] check that CI is green +* [ ] merge release PR as `Release 0.x.0 - ` +* [ ] check that CI for `main` is green * [ ] do a GitHub release: https://github.com/emilk/egui/releases/new * follow the format of the last release * [ ] wait for the documentation build to finish: https://docs.rs/releases/queue @@ -84,7 +83,7 @@ We don't update the MSRV in a patch release, unless we really, really need to. ## After release -* [ ] publish new `eframe_template` +* [ ] update `eframe_template` * [ ] publish new `egui_plot` * [ ] publish new `egui_table` * [ ] publish new `egui_tiles`