From 884be3491d01a1c1963c4dd63d8d788e1245ce37 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 25 Mar 2025 14:38:51 +0100 Subject: [PATCH 001/388] Fix color picker button (#5847) * related to #5832 (I want to keep that open and actually update the button to use the new popup, but this should be enough to fix it for now) * [X] I have followed the instructions in the PR template --- crates/egui/src/widgets/color_picker.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index 500fb0b88..f8e186658 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -502,8 +502,9 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Res const COLOR_SLIDER_WIDTH: f32 = 275.0; - // TODO(emilk): make it easier to show a temporary popup that closes when you click outside it + // 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) From 7ea3f762b8135db428941d3d73d9dc8f18ddd4ab Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 28 Mar 2025 20:37:38 +0100 Subject: [PATCH 002/388] Make text underline and strikethrough pixel perfect crisp (#5857) Small visual fix: pixel-align any text underline or strikethrough. Before they could be often be blurry. --- .../tests/snapshots/easymarkeditor.png | 4 +- crates/epaint/src/stroke.rs | 60 +++++++++++++++++++ crates/epaint/src/tessellator.rs | 56 +---------------- crates/epaint/src/text/text_layout.rs | 3 +- 4 files changed, 67 insertions(+), 56 deletions(-) diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index f88cb3791..7666b658e 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:fb4ac08fb40dd1413feee549ba977906160c82d0aba427d6d79d2e56080aa04e -size 178975 +oid sha256:3e6a383dca7e91d07df4bf501e2de13d046f04546a08d026efe3f82fc96b6e29 +size 178887 diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index 5d82c1963..50f4f678b 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -2,6 +2,8 @@ use std::{fmt::Debug, sync::Arc}; +use emath::GuiRounding as _; + use super::{emath, Color32, ColorMode, Pos2, Rect}; /// Describes the width and color of a line. @@ -34,6 +36,46 @@ impl Stroke { pub fn is_empty(&self) -> bool { self.width <= 0.0 || self.color == Color32::TRANSPARENT } + + /// For vertical or horizontal lines: + /// round the stroke center to produce a sharp, pixel-aligned line. + pub fn round_center_to_pixel(&self, pixels_per_point: f32, coord: &mut f32) { + // If the stroke is an odd number of pixels wide, + // we want to round the center of it to the center of a pixel. + // + // If however it is an even number of pixels wide, + // we want to round the center to be between two pixels. + // + // We also want to treat strokes that are _almost_ odd as it it was odd, + // to make it symmetric. Same for strokes that are _almost_ even. + // + // For strokes less than a pixel wide we also round to the center, + // because it will rendered as a single row of pixels by the tessellator. + + let pixel_size = 1.0 / pixels_per_point; + + if self.width <= pixel_size || is_nearest_integer_odd(pixels_per_point * self.width) { + *coord = coord.round_to_pixel_center(pixels_per_point); + } else { + *coord = coord.round_to_pixels(pixels_per_point); + } + } + + pub(crate) fn round_rect_to_pixel(&self, pixels_per_point: f32, rect: &mut Rect) { + // We put odd-width strokes in the center of pixels. + // To understand why, see `fn round_center_to_pixel`. + + let pixel_size = 1.0 / pixels_per_point; + + let width = self.width; + if width <= 0.0 { + *rect = rect.round_to_pixels(pixels_per_point); + } else if width <= pixel_size || is_nearest_integer_odd(pixels_per_point * width) { + *rect = rect.round_to_pixel_center(pixels_per_point); + } else { + *rect = rect.round_to_pixels(pixels_per_point); + } + } } impl From<(f32, Color)> for Stroke @@ -182,3 +224,21 @@ impl From for PathStroke { } } } + +/// Returns true if the nearest integer is odd. +fn is_nearest_integer_odd(x: f32) -> bool { + (x * 0.5 + 0.25).fract() > 0.5 +} + +#[test] +fn test_is_nearest_integer_odd() { + assert!(is_nearest_integer_odd(0.6)); + assert!(is_nearest_integer_odd(1.0)); + assert!(is_nearest_integer_odd(1.4)); + assert!(!is_nearest_integer_odd(1.6)); + assert!(!is_nearest_integer_odd(2.0)); + assert!(!is_nearest_integer_odd(2.4)); + assert!(is_nearest_integer_odd(2.6)); + assert!(is_nearest_integer_odd(3.0)); + assert!(is_nearest_integer_odd(3.4)); +} diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index bcb13a12c..914b1cc34 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1656,7 +1656,7 @@ impl Tessellator { if a.x == b.x { // Vertical line let mut x = a.x; - round_line_segment(&mut x, &stroke, self.pixels_per_point); + stroke.round_center_to_pixel(self.pixels_per_point, &mut x); a.x = x; b.x = x; @@ -1677,7 +1677,7 @@ impl Tessellator { if a.y == b.y { // Horizontal line let mut y = a.y; - round_line_segment(&mut y, &stroke, self.pixels_per_point); + stroke.round_center_to_pixel(self.pixels_per_point, &mut y); a.y = y; b.y = y; @@ -1778,7 +1778,6 @@ impl Tessellator { let mut corner_radius = CornerRadiusF32::from(corner_radius); let round_to_pixels = round_to_pixels.unwrap_or(self.options.round_rects_to_pixels); - let pixel_size = 1.0 / self.pixels_per_point; if stroke.width == 0.0 { stroke.color = Color32::TRANSPARENT; @@ -1849,17 +1848,7 @@ impl Tessellator { } StrokeKind::Middle => { // On this path we optimize for crisp and symmetric strokes. - // We put odd-width strokes in the center of pixels. - // To understand why, see `fn round_line_segment`. - if stroke.width <= 0.0 { - rect = rect.round_to_pixels(self.pixels_per_point); - } else if stroke.width <= pixel_size - || is_nearest_integer_odd(self.pixels_per_point * stroke.width) - { - rect = rect.round_to_pixel_center(self.pixels_per_point); - } else { - rect = rect.round_to_pixels(self.pixels_per_point); - } + stroke.round_rect_to_pixel(self.pixels_per_point, &mut rect); } StrokeKind::Outside => { // Put the inside of the stroke on a pixel boundary. @@ -2203,45 +2192,6 @@ impl Tessellator { } } -fn round_line_segment(coord: &mut f32, stroke: &Stroke, pixels_per_point: f32) { - // If the stroke is an odd number of pixels wide, - // we want to round the center of it to the center of a pixel. - // - // If however it is an even number of pixels wide, - // we want to round the center to be between two pixels. - // - // We also want to treat strokes that are _almost_ odd as it it was odd, - // to make it symmetric. Same for strokes that are _almost_ even. - // - // For strokes less than a pixel wide we also round to the center, - // because it will rendered as a single row of pixels by the tessellator. - - let pixel_size = 1.0 / pixels_per_point; - - if stroke.width <= pixel_size || is_nearest_integer_odd(pixels_per_point * stroke.width) { - *coord = coord.round_to_pixel_center(pixels_per_point); - } else { - *coord = coord.round_to_pixels(pixels_per_point); - } -} - -fn is_nearest_integer_odd(width: f32) -> bool { - (width * 0.5 + 0.25).fract() > 0.5 -} - -#[test] -fn test_is_nearest_integer_odd() { - assert!(is_nearest_integer_odd(0.6)); - assert!(is_nearest_integer_odd(1.0)); - assert!(is_nearest_integer_odd(1.4)); - assert!(!is_nearest_integer_odd(1.6)); - assert!(!is_nearest_integer_odd(2.0)); - assert!(!is_nearest_integer_odd(2.4)); - assert!(is_nearest_integer_odd(2.6)); - assert!(is_nearest_integer_odd(3.0)); - assert!(is_nearest_integer_odd(3.4)); -} - #[deprecated = "Use `Tessellator::new(…).tessellate_shapes(…)` instead"] pub fn tessellate_shapes( pixels_per_point: f32, diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 638b7a705..eb1adf5fb 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -866,7 +866,8 @@ fn add_row_hline( let mut last_right_x = f32::NAN; for glyph in &row.glyphs { - let (stroke, y) = stroke_and_y(glyph); + let (stroke, mut y) = stroke_and_y(glyph); + stroke.round_center_to_pixel(point_scale.pixels_per_point, &mut y); if stroke == Stroke::NONE { end_line(line_start.take(), last_right_x); From 83254718a335b358f062c7401bdd4ae0faae814b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 30 Mar 2025 13:15:41 +0200 Subject: [PATCH 003/388] Clean up strikethrough/underline code in epaint --- crates/epaint/src/text/text_layout.rs | 36 ++++++--------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index eb1adf5fb..7ce726f79 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -856,9 +856,15 @@ fn add_row_hline( mesh: &mut Mesh, stroke_and_y: impl Fn(&Glyph) -> (Stroke, f32), ) { + let mut path = crate::tessellator::Path::default(); // reusing path to avoid re-allocations. + let mut end_line = |start: Option<(Stroke, Pos2)>, stop_x: f32| { if let Some((stroke, start)) = start { - add_hline(point_scale, [start, pos2(stop_x, start.y)], stroke, mesh); + let stop = pos2(stop_x, start.y); + path.clear(); + path.add_line_segment([start, stop]); + let feathering = 1.0 / point_scale.pixels_per_point(); + path.stroke_open(feathering, &PathStroke::from(stroke), mesh); } }; @@ -888,34 +894,6 @@ fn add_row_hline( end_line(line_start.take(), last_right_x); } -fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke, mesh: &mut Mesh) { - let antialiased = true; - - if antialiased { - let mut path = crate::tessellator::Path::default(); // TODO(emilk): reuse this to avoid re-allocations. - path.add_line_segment([start, stop]); - let feathering = 1.0 / point_scale.pixels_per_point(); - path.stroke_open(feathering, &PathStroke::from(stroke), mesh); - } else { - // Thin lines often lost, so this is a bad idea - - assert_eq!( - start.y, stop.y, - "Horizontal line must be horizontal, but got: {start:?} -> {stop:?}" - ); - - let min_y = point_scale.round_to_pixel(start.y - 0.5 * stroke.width); - let max_y = point_scale.round_to_pixel(min_y + stroke.width); - - let rect = Rect::from_min_max( - pos2(point_scale.round_to_pixel(start.x), min_y), - pos2(point_scale.round_to_pixel(stop.x), max_y), - ); - - mesh.add_colored_rect(rect, stroke.color); - } -} - // ---------------------------------------------------------------------------- /// Keeps track of good places to break a long row of text. From ab0f0b7b64fa85e8186d6596df6df3253a210b46 Mon Sep 17 00:00:00 2001 From: Timo von Hartz Date: Sun, 30 Mar 2025 14:00:46 +0200 Subject: [PATCH 004/388] Rename `should_propagate_event` & add `should_prevent_default` (#5779) * [x] I have followed the instructions in the PR template Currently eframe [calls `prevent_default()`](https://github.com/emilk/egui/blob/962c7c75166dff3369d20675bcfd527d3287149f/crates/eframe/src/web/events.rs#L307-L369) for all copy / paste events on the [*document*](https://github.com/emilk/egui/blob/962c7c75166dff3369d20675bcfd527d3287149f/crates/eframe/src/web/events.rs#L88), making embedding an egui application in a page (e.g. an react application) hard (as all copy & paste functionality for other elements on the page is broken by this). I'm not sure what the motivation for this is, if any. This commit / PR adds a callback (`should_prevent_default`), similar to `should_propgate_event`, that an egui application can use to overwrite this behavior. It defaults to returning `true` for all events, to keep the existing behavior. I call `should_prevent_default` in every place that `should_propagate_event` is called (which is not all places that `prevent_default` is called!). I'm not sure for the motivation of not calling `should_propagate_event` everywhere that `stop_propagation` is called, but I kept that behavior for the `should_prevent_default` callback too. Please let me know if I'm missing some existing functionality that would allow me to do this, or if there's a reason that we don't want applications to be able to customize this (i.e. if there's a reason to always `prevent_default` for all copy / paste events on the whole document) --- crates/eframe/src/epi.rs | 15 +++- crates/eframe/src/web/events.rs | 141 ++++++++++++++++++++++---------- 2 files changed, 108 insertions(+), 48 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index fb589fe2e..562311dc6 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -499,10 +499,16 @@ pub struct WebOptions { /// If the web event corresponding to an egui event should be propagated /// to the rest of the web page. /// - /// The default is `false`, meaning + /// The default is `true`, meaning /// [`stopPropagation`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation) - /// is called on every event. - pub should_propagate_event: Box bool>, + /// is called on every event, and the event is not propagated to the rest of the web page. + pub should_stop_propagation: Box bool>, + + /// Whether the web event corresponding to an egui event should have `prevent_default` called + /// on it or not. + /// + /// Defaults to true. + pub should_prevent_default: Box bool>, } #[cfg(target_arch = "wasm32")] @@ -519,7 +525,8 @@ impl Default for WebOptions { dithering: true, - should_propagate_event: Box::new(|_| false), + should_stop_propagation: Box::new(|_| true), + should_prevent_default: Box::new(|_| true), } } } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 6a1b7b6db..32fba9ef0 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -139,15 +139,20 @@ fn install_keydown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), J { if let Some(text) = text_from_keyboard_event(&event) { let egui_event = egui::Event::Text(text); - let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + let should_stop_propagation = + (runner.web_options.should_stop_propagation)(&egui_event); + let should_prevent_default = + (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); // If this is indeed text, then prevent any other action. - event.prevent_default(); + if should_prevent_default { + event.prevent_default(); + } // Use web options to tell if the event should be propagated to parent elements. - if !should_propagate { + if should_stop_propagation { event.stop_propagation(); } } @@ -184,7 +189,7 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner) repeat: false, // egui will fill this in for us! modifiers, }; - let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); @@ -201,7 +206,7 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner) } // Use web options to tell if the web event should be propagated to parent elements based on the egui event. - if !should_propagate { + if should_stop_propagation { event.stop_propagation(); } } @@ -261,7 +266,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { let modifiers = modifiers_from_kb_event(&event); runner.input.raw.modifiers = modifiers; - let mut propagate_event = false; + let mut should_stop_propagation = true; if let Some(key) = translate_key(&event.key()) { let egui_event = egui::Event::Key { @@ -271,7 +276,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { repeat: false, modifiers, }; - propagate_event |= (runner.web_options.should_propagate_event)(&egui_event); + should_stop_propagation &= (runner.web_options.should_stop_propagation)(&egui_event); runner.input.raw.events.push(egui_event); } @@ -290,7 +295,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { repeat: false, modifiers, }; - propagate_event |= (runner.web_options.should_propagate_event)(&egui_event); + should_stop_propagation &= (runner.web_options.should_stop_propagation)(&egui_event); runner.input.raw.events.push(egui_event); } } @@ -299,7 +304,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { // Use web options to tell if the web event should be propagated to parent elements based on the egui event. let has_focus = runner.input.raw.focused; - if has_focus && !propagate_event { + if has_focus && should_stop_propagation { event.stop_propagation(); } } @@ -310,19 +315,26 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul if let Ok(text) = data.get_data("text") { let text = text.replace("\r\n", "\n"); - let mut should_propagate = false; + let mut should_stop_propagation = true; + let mut should_prevent_default = true; if !text.is_empty() && runner.input.raw.focused { let egui_event = egui::Event::Paste(text); - should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + should_stop_propagation = + (runner.web_options.should_stop_propagation)(&egui_event); + should_prevent_default = + (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); 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 !should_propagate { + if should_stop_propagation { event.stop_propagation(); } - event.prevent_default(); + + if should_prevent_default { + event.prevent_default(); + } } } })?; @@ -340,10 +352,13 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul } // 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_propagate_event)(&egui::Event::Cut) { + if (runner.web_options.should_stop_propagation)(&egui::Event::Cut) { event.stop_propagation(); } - event.prevent_default(); + + if (runner.web_options.should_prevent_default)(&egui::Event::Cut) { + event.prevent_default(); + } })?; runner_ref.add_event_listener(target, "copy", |event: web_sys::ClipboardEvent, runner| { @@ -359,10 +374,13 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul } // 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_propagate_event)(&egui::Event::Copy) { + if (runner.web_options.should_stop_propagation)(&egui::Event::Copy) { event.stop_propagation(); } - event.prevent_default(); + + if (runner.web_options.should_prevent_default)(&egui::Event::Copy) { + event.prevent_default(); + } })?; Ok(()) @@ -484,7 +502,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<( |event: web_sys::PointerEvent, runner: &mut AppRunner| { let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; - let mut should_propagate = false; + let mut should_stop_propagation = true; if let Some(button) = button_from_mouse_event(&event) { let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); let modifiers = runner.input.raw.modifiers; @@ -494,7 +512,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<( pressed: true, modifiers, }; - should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); runner.input.raw.events.push(egui_event); // In Safari we are only allowed to write to the clipboard during the @@ -506,7 +524,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<( } // Use web options to tell if the web event should be propagated to parent elements based on the egui event. - if !should_propagate { + if should_stop_propagation { event.stop_propagation(); } // Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here. @@ -536,7 +554,10 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), pressed: false, modifiers, }; - let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + let should_stop_propagation = + (runner.web_options.should_stop_propagation)(&egui_event); + let should_prevent_default = + (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); // Previously on iOS, the canvas would not receive focus on @@ -555,10 +576,12 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), // Make sure we paint the output of the above logic call asap: runner.needs_repaint.repaint_asap(); - event.prevent_default(); + if should_prevent_default { + event.prevent_default(); + } // Use web options to tell if the web event should be propagated to parent elements based on the egui event. - if !should_propagate { + if should_stop_propagation { event.stop_propagation(); } } @@ -600,15 +623,19 @@ fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), egui::pos2(event.client_x() as f32, event.client_y() as f32), ) { let egui_event = egui::Event::PointerMoved(pos); - let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); + let should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); 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 !should_propagate { + if should_stop_propagation { event.stop_propagation(); } - event.prevent_default(); + + if should_prevent_default { + event.prevent_default(); + } } }) } @@ -622,10 +649,13 @@ fn install_mouseleave(runner_ref: &WebRunner, target: &EventTarget) -> Result<() 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_propagate_event)(&egui::Event::PointerGone) { + if (runner.web_options.should_stop_propagation)(&egui::Event::PointerGone) { event.stop_propagation(); } - event.prevent_default(); + + if (runner.web_options.should_prevent_default)(&egui::Event::PointerGone) { + event.prevent_default(); + } }, ) } @@ -635,7 +665,8 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<() target, "touchstart", |event: web_sys::TouchEvent, runner| { - let mut should_propagate = false; + let mut should_stop_propagation = true; + let mut should_prevent_default = true; if let Some((pos, _)) = primary_touch_pos(runner, &event) { let egui_event = egui::Event::PointerButton { pos, @@ -643,7 +674,8 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<() pressed: true, modifiers: runner.input.raw.modifiers, }; - should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); + should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); } @@ -651,10 +683,13 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<() 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 !should_propagate { + if should_stop_propagation { event.stop_propagation(); } - event.prevent_default(); + + if should_prevent_default { + event.prevent_default(); + } }, ) } @@ -667,17 +702,23 @@ fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), egui::pos2(touch.client_x() as f32, touch.client_y() as f32), ) { let egui_event = egui::Event::PointerMoved(pos); - let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + let should_stop_propagation = + (runner.web_options.should_stop_propagation)(&egui_event); + let should_prevent_default = + (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); push_touches(runner, egui::TouchPhase::Move, &event); 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 !should_propagate { + if should_stop_propagation { event.stop_propagation(); } - event.prevent_default(); + + if should_prevent_default { + event.prevent_default(); + } } } }) @@ -691,18 +732,23 @@ fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), egui::pos2(touch.client_x() as f32, touch.client_y() as f32), ) { // First release mouse to click: - let mut should_propagate = false; + let mut should_stop_propagation = true; + let mut should_prevent_default = true; let egui_event = egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: false, modifiers: runner.input.raw.modifiers, }; - should_propagate |= (runner.web_options.should_propagate_event)(&egui_event); + should_stop_propagation &= + (runner.web_options.should_stop_propagation)(&egui_event); + should_prevent_default &= (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); // Then remove hover effect: - should_propagate |= - (runner.web_options.should_propagate_event)(&egui::Event::PointerGone); + should_stop_propagation &= + (runner.web_options.should_stop_propagation)(&egui::Event::PointerGone); + should_prevent_default &= + (runner.web_options.should_prevent_default)(&egui::Event::PointerGone); runner.input.raw.events.push(egui::Event::PointerGone); push_touches(runner, egui::TouchPhase::End, &event); @@ -710,10 +756,13 @@ fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), 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 !should_propagate { + if should_stop_propagation { event.stop_propagation(); } - event.prevent_default(); + + if should_prevent_default { + event.prevent_default(); + } // Fix virtual keyboard IOS // Need call focus at the same time of event @@ -769,16 +818,20 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV modifiers, } }; - let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); + let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); + let should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); 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 !should_propagate { + if should_stop_propagation { event.stop_propagation(); } - event.prevent_default(); + + if should_prevent_default { + event.prevent_default(); + } }) } From 943e3618fcdf7f96d3f5f163ad741d96dbe7dbbf Mon Sep 17 00:00:00 2001 From: Hank Jordan Date: Sun, 30 Mar 2025 08:03:19 -0400 Subject: [PATCH 005/388] Improve drag-to-select text (add margins) (#5797) Might want to draw from `interaction.interact_radius` style instead of hard-coding the margin, but I didn't want to create a breaking change. If desired, I can follow up with a separate PR to address that concern. * Closes * [x] I have followed the instructions in the PR template --- crates/epaint/src/text/text_layout_types.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 6d69045ab..b6d7ccf49 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -796,13 +796,16 @@ impl Galley { /// same as a cursor at the end. /// This allows implementing text-selection by dragging above/below the galley. pub fn cursor_from_pos(&self, pos: Vec2) -> CCursor { + // Vertical margin around galley improves text selection UX + const VMARGIN: f32 = 5.0; + if let Some(first_row) = self.rows.first() { - if pos.y < first_row.min_y() { + if pos.y < first_row.min_y() - VMARGIN { return self.begin(); } } if let Some(last_row) = self.rows.last() { - if last_row.max_y() < pos.y { + if last_row.max_y() + VMARGIN < pos.y { return self.end(); } } From 995058bbd10d8c877d49d18b2f78fc65066cb960 Mon Sep 17 00:00:00 2001 From: Alexander Nadeau Date: Sun, 30 Mar 2025 08:04:07 -0400 Subject: [PATCH 006/388] Update web-sys min version to 0.3.73 (#5862) This should prevent compilation errors (which I ran into) where eframe tries to use HtmlElement::set_autofocus(), which doesn't exist until 0.3.73. ``` error[E0599]: no method named `set_autofocus` found for struct `HtmlElement` in the current scope --> C:\Users\wareya\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\eframe-0.31.1\src\web\text_agent.rs:24:15 | 24 | input.set_autofocus(true)?; | ^^^^^^^^^^^^^ | help: there is a method `set_onfocus` with a similar name | 24 | input.set_onfocus(true)?; | ~~~~~~~~~~~ ``` * [x] I have followed the instructions in the PR template --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 92f1ce3c5..cb750bfda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,7 @@ thiserror = "1.0.37" type-map = "0.5.0" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" -web-sys = "0.3.70" +web-sys = "0.3.73" web-time = "1.1.0" # Timekeeping for native and web wgpu = { version = "24.0.0", default-features = false } windows-sys = "0.59" From e3acd710904ecd957f9d88e3593fd7efee5768c8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 30 Mar 2025 16:21:00 +0200 Subject: [PATCH 007/388] Make text background rects pixel-sharp (#5864) Small visual teak: make sure the background text color is pixel-aligned. --- .../tests/snapshots/rendering_test/dpi_1.50.png | 2 +- crates/egui_extras/src/syntax_highlighting.rs | 1 + crates/epaint/src/text/text_layout.rs | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) 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 132864c85..9e1b69fee 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:e6f394c2beb51d95edaf8c7ddc9ff62d3f95913ea88a3840245b6bacf8b850cc +oid sha256:334f52bfee27f9c467de739696fd7ce7c48ec9013e315dc4b2e61eee58f11287 size 907997 diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 77ad0cc2d..ac51c673c 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -33,6 +33,7 @@ pub fn highlight( // performing it at a separate thread (ctx, ctx.style()) can be used and when ui is available // (ui.ctx(), ui.style()) can be used + #[allow(non_local_definitions)] impl egui::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> for Highlighter { fn compute( &mut self, diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 7ce726f79..d1b2d5038 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -715,7 +715,7 @@ fn tessellate_row( mesh.reserve_vertices(row.glyphs.len() * 4); if format_summary.any_background { - add_row_backgrounds(job, row, &mut mesh); + add_row_backgrounds(point_scale, job, row, &mut mesh); } let glyph_index_start = mesh.indices.len(); @@ -753,7 +753,7 @@ fn tessellate_row( /// Create background for glyphs that have them. /// Creates as few rectangular regions as possible. -fn add_row_backgrounds(job: &LayoutJob, row: &Row, mesh: &mut Mesh) { +fn add_row_backgrounds(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh: &mut Mesh) { if row.glyphs.is_empty() { return; } @@ -762,6 +762,7 @@ fn add_row_backgrounds(job: &LayoutJob, row: &Row, mesh: &mut Mesh) { if let Some((color, start_rect, expand)) = start { let rect = Rect::from_min_max(start_rect.left_top(), pos2(stop_x, start_rect.bottom())); let rect = rect.expand(expand); + let rect = rect.round_to_pixels(point_scale.pixels_per_point()); mesh.add_colored_rect(rect, color); } }; From e275409eb1840efb560ad69960a077c2f4405521 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 30 Mar 2025 16:36:03 +0200 Subject: [PATCH 008/388] Fix: transform `TextShape` underline width (#5865) Minor bug fix when transforming a `TextShape` with a `underline` (used for e.g. hyperlinks). Before the underline width would not scale properly; now it will. --- crates/epaint/src/shapes/shape.rs | 32 +++++++++++++++++++++----- crates/epaint/src/shapes/text_shape.rs | 4 +++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index d17f528c8..7b38bda6d 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -456,19 +456,39 @@ impl Shape { rect_shape.blur_width *= transform.scaling; } Self::Text(text_shape) => { - text_shape.pos = transform * text_shape.pos; + let TextShape { + pos, + galley, + underline, + fallback_color: _, + override_text_color: _, + opacity_factor: _, + angle: _, + } = text_shape; - // Scale text: - let galley = Arc::make_mut(&mut text_shape.galley); - for row in &mut galley.rows { + *pos = transform * *pos; + underline.width *= transform.scaling; + + let Galley { + job: _, + rows, + elided: _, + rect, + mesh_bounds, + num_vertices: _, + num_indices: _, + pixels_per_point: _, + } = Arc::make_mut(galley); + + for row in rows { row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds; for v in &mut row.visuals.mesh.vertices { v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y); } } - galley.mesh_bounds = transform.scaling * galley.mesh_bounds; - galley.rect = transform.scaling * galley.rect; + *mesh_bounds = transform.scaling * *mesh_bounds; + *rect = transform.scaling * *rect; } Self::Mesh(mesh) => { Arc::make_mut(mesh).transform(transform); diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index 30287187f..ef549bd9c 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -8,7 +8,9 @@ use crate::*; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TextShape { - /// Top left corner of the first character. + /// Where the origin of [`Self::galley`] is. + /// + /// Usually the top left corner of the first character. pub pos: Pos2, /// The laid out text, from [`Fonts::layout_job`]. From 557bd56e1962266e765cc7b7958c1fd3f14fa3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20G=C5=82uchowski?= Date: Tue, 1 Apr 2025 18:55:39 +0200 Subject: [PATCH 009/388] Optimize editing long text by caching each paragraph (#5411) ## What (written by @emilk) When editing long text (thousands of line), egui would previously re-layout the entire text on each edit. This could be slow. With this PR, we instead split the text into paragraphs (split on `\n`) and then cache each such paragraph. When editing text then, only the changed paragraph needs to be laid out again. Still, there is overhead from splitting the text, hashing each paragraph, and then joining the results, so the runtime complexity is still O(N). In our benchmark, editing a 2000 line string goes from ~8ms to ~300 ms, a speedup of ~25x. In the future, we could also consider laying out each paragraph in parallel, to speed up the initial layout of the text. ## Details This is an ~~almost complete~~ implementation of the approach described by emilk [in this comment](), excluding CoW semantics for `LayoutJob` (but including them for `Row`). It supersedes the previous unsuccessful attempt here: https://github.com/emilk/egui/pull/4000. Draft because: - [X] ~~Currently individual rows will have `ends_with_newline` always set to false. This breaks selection with Ctrl+A (and probably many other things)~~ - [X] ~~The whole block for doing the splitting and merging should probably become a function (I'll do that later).~~ - [X] ~~I haven't run the check script, the tests, and haven't made sure all of the examples build (although I assume they probably don't rely on Galley internals).~~ - [x] ~~Layout is sometimes incorrect (missing empty lines, wrapping sometimes makes text overlap).~~ - A lot of text-related code had to be changed so this needs to be properly tested to ensure no layout issues were introduced, especially relating to the now row-relative coordinate system of `Row`s. Also this requires that we're fine making these very breaking changes. It does significantly improve the performance of rendering large blocks of text (if they have many newlines), this is the test program I used to test it (adapted from ):
code ```rust use eframe::egui::{self, CentralPanel, TextEdit}; use std::fmt::Write; fn main() -> Result<(), eframe::Error> { let options = eframe::NativeOptions { ..Default::default() }; eframe::run_native( "editor big file test", options, Box::new(|_cc| Ok(Box::::new(MyApp::new()))), ) } struct MyApp { text: String, } impl MyApp { fn new() -> Self { let mut string = String::new(); for line_bytes in (0..50000).map(|_| (0u8..50)) { for byte in line_bytes { write!(string, " {byte:02x}").unwrap(); } write!(string, "\n").unwrap(); } println!("total bytes: {}", string.len()); MyApp { text: string } } } impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { CentralPanel::default().show(ctx, |ui| { let start = std::time::Instant::now(); egui::ScrollArea::vertical().show(ui, |ui| { let code_editor = TextEdit::multiline(&mut self.text) .code_editor() .desired_width(f32::INFINITY) .desired_rows(40); let response = code_editor.show(ui).response; if response.changed() { println!("total bytes now: {}", self.text.len()); } }); let end = std::time::Instant::now(); let time_to_update = end - start; if time_to_update.as_secs_f32() > 0.5 { println!("Long update took {:.3}s", time_to_update.as_secs_f32()) } }); } } ```
I think the way to proceed would be to make a new type, something like `PositionedRow`, that would wrap an `Arc` but have a separate `pos` ~~and `ends_with_newline`~~ (that would mean `Row` only holds a `size` instead of a `rect`). This type would of course have getters that would allow you to easily get a `Rect` from it and probably a `Deref` to the underlying `Row`. ~~I haven't done this yet because I wanted to get some opinions whether this would be an acceptable API first.~~ This is now implemented, but of course I'm still open to discussion about this approach and whether it's what we want to do. Breaking changes (currently): - The `Galley::rows` field has a different type. - There is now a `PlacedRow` wrapper for `Row`. - `Row` now uses a coordinate system relative to itself instead of the `Galley`. * Closes * [X] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 53 ++- Cargo.toml | 1 + .../egui/src/text_selection/accesskit_text.rs | 6 +- .../text_selection/label_text_selection.rs | 9 +- crates/egui/src/text_selection/visuals.rs | 9 +- crates/egui/src/widget_text.rs | 4 +- crates/egui/src/widgets/label.rs | 10 +- crates/egui_demo_lib/Cargo.toml | 1 + crates/egui_demo_lib/benches/benchmark.rs | 27 ++ crates/emath/src/pos2.rs | 14 +- crates/emath/src/rect.rs | 6 +- crates/epaint/Cargo.toml | 1 + crates/epaint/src/shape_transform.rs | 3 +- crates/epaint/src/shapes/shape.rs | 34 +- crates/epaint/src/shapes/text_shape.rs | 57 ++++ crates/epaint/src/stats.rs | 2 +- crates/epaint/src/tessellator.rs | 6 +- crates/epaint/src/text/fonts.rs | 323 +++++++++++++++++- crates/epaint/src/text/mod.rs | 2 +- crates/epaint/src/text/text_layout.rs | 194 ++++++----- crates/epaint/src/text/text_layout_types.rs | 189 ++++++++-- 21 files changed, 754 insertions(+), 197 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a04d32f1..4276896ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,6 +642,17 @@ dependencies = [ "piper", ] +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -896,6 +907,18 @@ dependencies = [ "env_logger", ] +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1331,6 +1354,7 @@ dependencies = [ "egui", "egui_extras", "egui_kittest", + "rand", "serde", "unicode_names2", ] @@ -1420,6 +1444,12 @@ dependencies = [ "serde", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "endi" version = "1.1.0" @@ -1520,6 +1550,7 @@ dependencies = [ "profiling", "rayon", "serde", + "similar-asserts", ] [[package]] @@ -2389,7 +2420,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3669,6 +3700,26 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console", + "similar", +] + [[package]] name = "simplecss" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index cb750bfda..a0051513d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ puffin_http = "0.16" raw-window-handle = "0.6.0" ron = "0.8" serde = { version = "1", features = ["derive"] } +similar-asserts = "1.4.2" thiserror = "1.0.37" type-map = "0.5.0" wasm-bindgen = "0.2" diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index d189498f6..de193e3b0 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -45,7 +45,7 @@ pub fn update_accesskit_for_text_widget( let row_id = parent_id.with(row_index); ctx.accesskit_node_builder(row_id, |builder| { builder.set_role(accesskit::Role::TextRun); - let rect = global_from_galley * row.rect; + let rect = global_from_galley * row.rect(); builder.set_bounds(accesskit::Rect { x0: rect.min.x.into(), y0: rect.min.y.into(), @@ -76,14 +76,14 @@ pub fn update_accesskit_for_text_widget( let old_len = value.len(); value.push(glyph.chr); character_lengths.push((value.len() - old_len) as _); - character_positions.push(glyph.pos.x - row.rect.min.x); + character_positions.push(glyph.pos.x - row.pos.x); character_widths.push(glyph.advance_width); } if row.ends_with_newline { value.push('\n'); character_lengths.push(1); - character_positions.push(row.rect.max.x - row.rect.min.x); + character_positions.push(row.size.x); character_widths.push(0.0); } word_lengths.push((character_lengths.len() - last_word_start) as _); diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index acd3db7d3..e24992a05 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -186,7 +186,10 @@ impl LabelSelectionState { if let epaint::Shape::Text(text_shape) = &mut shape.shape { let galley = Arc::make_mut(&mut text_shape.galley); for row_selection in row_selections { - if let Some(row) = galley.rows.get_mut(row_selection.row) { + if let Some(placed_row) = + galley.rows.get_mut(row_selection.row) + { + let row = Arc::make_mut(&mut placed_row.row); for vertex_index in row_selection.vertex_indices { if let Some(vertex) = row .visuals @@ -701,8 +704,8 @@ fn selected_text(galley: &Galley, cursor_range: &CCursorRange) -> String { } fn estimate_row_height(galley: &Galley) -> f32 { - if let Some(row) = galley.rows.first() { - row.rect.height() + if let Some(placed_row) = galley.rows.first() { + placed_row.height() } else { galley.size().y } diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index 32a040a89..deee5690b 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -31,11 +31,12 @@ pub fn paint_text_selection( let max = galley.layout_from_cursor(max); for ri in min.row..=max.row { - let row = &mut galley.rows[ri]; + let row = Arc::make_mut(&mut galley.rows[ri].row); + let left = if ri == min.row { row.x_offset(min.column) } else { - row.rect.left() + 0.0 }; let right = if ri == max.row { row.x_offset(max.column) @@ -45,10 +46,10 @@ pub fn paint_text_selection( } else { 0.0 }; - row.rect.right() + newline_size + row.size.x + newline_size }; - let rect = Rect::from_min_max(pos2(left, row.min_y()), pos2(right, row.max_y())); + let rect = Rect::from_min_max(pos2(left, 0.0), pos2(right, row.size.y)); let mesh = &mut row.visuals.mesh; // Time to insert the selection rectangle into the row mesh. diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 5ddafc4be..e66cb1bc8 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -671,8 +671,8 @@ impl WidgetText { Self::RichText(text) => text.font_height(fonts, style), Self::LayoutJob(job) => job.font_height(fonts), Self::Galley(galley) => { - if let Some(row) = galley.rows.first() { - row.height().round_ui() + if let Some(placed_row) = galley.rows.first() { + placed_row.height().round_ui() } else { galley.size().y.round_ui() } diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index c36b9fc60..3656af92b 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, vec2, Align, Direction, FontSelection, Galley, Pos2, Response, - Sense, Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, + epaint, pos2, text_selection, Align, Direction, FontSelection, Galley, Pos2, Response, Sense, + Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, }; use self::text_selection::LabelSelectionState; @@ -216,10 +216,10 @@ impl Label { let pos = pos2(ui.max_rect().left(), ui.cursor().top()); assert!(!galley.rows.is_empty(), "Galleys are never empty"); // collect a response from many rows: - let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y)); + let rect = galley.rows[0].rect().translate(pos.to_vec2()); let mut response = ui.allocate_rect(rect, sense); - for row in galley.rows.iter().skip(1) { - let rect = row.rect.translate(vec2(pos.x, pos.y)); + for placed_row in galley.rows.iter().skip(1) { + let rect = placed_row.rect().translate(pos.to_vec2()); response |= ui.allocate_rect(rect, sense); } (pos, galley, response) diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index 0e0299f11..77b8fdcb3 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -58,6 +58,7 @@ serde = { workspace = true, optional = true } criterion.workspace = true egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } egui = { workspace = true, features = ["default_fonts"] } +rand = "0.9" [[bench]] name = "benchmark" diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index cbcc4d88f..dab6bdd7b 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -1,7 +1,10 @@ +use std::fmt::Write as _; + use criterion::{criterion_group, criterion_main, Criterion}; use egui::epaint::TextShape; use egui_demo_lib::LOREM_IPSUM_LONG; +use rand::Rng as _; pub fn criterion_benchmark(c: &mut Criterion) { use egui::RawInput; @@ -128,6 +131,30 @@ pub fn criterion_benchmark(c: &mut Criterion) { }); }); + c.bench_function("text_layout_cached_many_lines_modified", |b| { + const NUM_LINES: usize = 2_000; + + let mut string = String::new(); + for _ in 0..NUM_LINES { + for i in 0..30_u8 { + write!(string, "{i:02X} ").unwrap(); + } + string.push('\n'); + } + + let mut rng = rand::rng(); + b.iter(|| { + fonts.begin_pass(pixels_per_point, max_texture_side); + + // Delete a random character, simulating a user making an edit in a long file: + let mut new_string = string.clone(); + let idx = rng.random_range(0..string.len()); + new_string.remove(idx); + + fonts.layout(new_string, font_id.clone(), text_color, wrap_width); + }); + }); + let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, text_color, wrap_width); let font_image_size = fonts.font_image_size(); let prepared_discs = fonts.texture_atlas().lock().prepared_discs(); diff --git a/crates/emath/src/pos2.rs b/crates/emath/src/pos2.rs index 1f4bd8642..62590b10f 100644 --- a/crates/emath/src/pos2.rs +++ b/crates/emath/src/pos2.rs @@ -1,5 +1,7 @@ -use std::fmt; -use std::ops::{Add, AddAssign, Sub, SubAssign}; +use std::{ + fmt, + ops::{Add, AddAssign, MulAssign, Sub, SubAssign}, +}; use crate::{lerp, Div, Mul, Vec2}; @@ -305,6 +307,14 @@ impl Mul for f32 { } } +impl MulAssign for Pos2 { + #[inline(always)] + fn mul_assign(&mut self, rhs: f32) { + self.x *= rhs; + self.y *= rhs; + } +} + impl Div for Pos2 { type Output = Self; diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 521b6f33f..00bed04f0 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -710,7 +710,11 @@ impl Rect { impl fmt::Debug for Rect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[{:?} - {:?}]", self.min, self.max) + if let Some(precision) = f.precision() { + write!(f, "[{1:.0$?} - {2:.0$?}]", precision, self.min, self.max) + } else { + write!(f, "[{:?} - {:?}]", self.min, self.max) + } } } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 720188872..b8b006d44 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -101,6 +101,7 @@ backtrace = { workspace = true, optional = true } [dev-dependencies] criterion.workspace = true +similar-asserts.workspace = true [[bench]] diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 469f2e521..57de14969 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -89,7 +89,8 @@ pub fn adjust_colors( if !galley.is_empty() { let galley = Arc::make_mut(galley); - for row in &mut galley.rows { + for placed_row in &mut galley.rows { + let row = Arc::make_mut(&mut placed_row.row); for vertex in &mut row.visuals.mesh.vertices { adjust_color(&mut vertex.color); } diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 7b38bda6d..a855d653a 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -456,39 +456,7 @@ impl Shape { rect_shape.blur_width *= transform.scaling; } Self::Text(text_shape) => { - let TextShape { - pos, - galley, - underline, - fallback_color: _, - override_text_color: _, - opacity_factor: _, - angle: _, - } = text_shape; - - *pos = transform * *pos; - underline.width *= transform.scaling; - - let Galley { - job: _, - rows, - elided: _, - rect, - mesh_bounds, - num_vertices: _, - num_indices: _, - pixels_per_point: _, - } = Arc::make_mut(galley); - - for row in rows { - row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds; - for v in &mut row.visuals.mesh.vertices { - v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y); - } - } - - *mesh_bounds = transform.scaling * *mesh_bounds; - *rect = transform.scaling * *rect; + text_shape.transform(transform); } Self::Mesh(mesh) => { Arc::make_mut(mesh).transform(transform); diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index ef549bd9c..e88213b93 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -89,6 +89,63 @@ impl TextShape { self.opacity_factor = opacity_factor; self } + + /// Move the shape by this many points, in-place. + pub fn transform(&mut self, transform: emath::TSTransform) { + let Self { + pos, + galley, + underline, + fallback_color: _, + override_text_color: _, + opacity_factor: _, + angle: _, + } = self; + + *pos = transform * *pos; + underline.width *= transform.scaling; + + let Galley { + job: _, + rows, + elided: _, + rect, + mesh_bounds, + num_vertices: _, + num_indices: _, + pixels_per_point: _, + } = Arc::make_mut(galley); + + *rect = transform.scaling * *rect; + *mesh_bounds = transform.scaling * *mesh_bounds; + + for text::PlacedRow { pos, row } in rows { + *pos *= transform.scaling; + + let text::Row { + section_index_at_start: _, + glyphs: _, // TODO(emilk): would it make sense to transform these? + size, + visuals, + ends_with_newline: _, + } = Arc::make_mut(row); + + *size *= transform.scaling; + + let text::RowVisuals { + mesh, + mesh_bounds, + glyph_index_start: _, + glyph_vertex_range: _, + } = visuals; + + *mesh_bounds = transform.scaling * *mesh_bounds; + + for v in &mut mesh.vertices { + v.pos *= transform.scaling; + } + } + } } impl From for Shape { diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index cb72d90e3..2acf1e93c 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -91,7 +91,7 @@ impl AllocInfo { + galley.rows.iter().map(Self::from_galley_row).sum() } - fn from_galley_row(row: &crate::text::Row) -> Self { + fn from_galley_row(row: &crate::text::PlacedRow) -> Self { Self::from_mesh(&row.visuals.mesh) + Self::from_slice(&row.glyphs) } diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 914b1cc34..2b24869ae 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -2033,11 +2033,13 @@ impl Tessellator { continue; } + let final_row_pos = galley_pos + row.pos.to_vec2(); + let mut row_rect = row.visuals.mesh_bounds; if *angle != 0.0 { row_rect = row_rect.rotate_bb(rotator); } - row_rect = row_rect.translate(galley_pos.to_vec2()); + row_rect = row_rect.translate(final_row_pos.to_vec2()); if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) { // culling individual lines of text is important, since a single `Shape::Text` @@ -2086,7 +2088,7 @@ impl Tessellator { }; Vertex { - pos: galley_pos + offset, + pos: final_row_pos + offset, uv: (uv.to_vec2() * uv_normalizer).to_pos2(), color, } diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index ccbf66f9c..bfa854680 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -4,7 +4,7 @@ use crate::{ mutex::{Mutex, MutexGuard}, text::{ font::{Font, FontImpl}, - Galley, LayoutJob, + Galley, LayoutJob, LayoutSection, }, TextureAtlas, }; @@ -617,7 +617,9 @@ pub struct FontsAndCache { impl FontsAndCache { fn layout_job(&mut self, job: LayoutJob) -> Arc { - self.galley_cache.layout(&mut self.fonts, job) + let allow_split_paragraphs = true; // Optimization for editing text with many paragraphs. + self.galley_cache + .layout(&mut self.fonts, job, allow_split_paragraphs) } } @@ -726,6 +728,12 @@ impl FontsImpl { struct CachedGalley { /// When it was last used last_used: u32, + + /// Hashes of all other entries this one depends on for quick re-layout. + /// Their `last_used`s should be updated alongside this one to make sure they're + /// not evicted. + children: Option>, + galley: Arc, } @@ -737,13 +745,18 @@ struct GalleyCache { } impl GalleyCache { - fn layout(&mut self, fonts: &mut FontsImpl, mut job: LayoutJob) -> Arc { + fn layout_internal( + &mut self, + fonts: &mut FontsImpl, + mut job: LayoutJob, + allow_split_paragraphs: bool, + ) -> (u64, Arc) { if job.wrap.max_width.is_finite() { // Protect against rounding errors in egui layout code. // Say the user asks to wrap at width 200.0. // The text layout wraps, and reports that the final width was 196.0 points. - // This than trickles up the `Ui` chain and gets stored as the width for a tooltip (say). + // This then trickles up the `Ui` chain and gets stored as the width for a tooltip (say). // On the next frame, this is then set as the max width for the tooltip, // and we end up calling the text layout code again, this time with a wrap width of 196.0. // Except, somewhere in the `Ui` chain with added margins etc, a rounding error was introduced, @@ -765,22 +778,176 @@ impl GalleyCache { let hash = crate::util::hash(&job); // TODO(emilk): even faster hasher? - match self.cache.entry(hash) { + let galley = match self.cache.entry(hash) { std::collections::hash_map::Entry::Occupied(entry) => { + // The job was found in cache - no need to re-layout. let cached = entry.into_mut(); cached.last_used = self.generation; - cached.galley.clone() - } - std::collections::hash_map::Entry::Vacant(entry) => { - let galley = super::layout(fonts, job.into()); - let galley = Arc::new(galley); - entry.insert(CachedGalley { - last_used: self.generation, - galley: galley.clone(), - }); + + let galley = cached.galley.clone(); + if let Some(children) = &cached.children { + // The point of `allow_split_paragraphs` is to split large jobs into paragraph, + // and then cache each paragraph individually. + // That way, if we edit a single paragraph, only that paragraph will be re-layouted. + // For that to work we need to keep all the child/paragraph + // galleys alive while the parent galley is alive: + for child_hash in children.clone().iter() { + if let Some(cached_child) = self.cache.get_mut(child_hash) { + cached_child.last_used = self.generation; + } + } + } + galley } + std::collections::hash_map::Entry::Vacant(entry) => { + 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); + debug_assert_eq!( + child_hashes.len(), + child_galleys.len(), + "Bug in `layout_each_paragraph_individuallly`" + ); + let galley = + Arc::new(Galley::concat(job, &child_galleys, fonts.pixels_per_point)); + + self.cache.insert( + hash, + CachedGalley { + last_used: self.generation, + children: Some(child_hashes.into()), + galley: galley.clone(), + }, + ); + galley + } else { + let galley = super::layout(fonts, job); + let galley = Arc::new(galley); + entry.insert(CachedGalley { + last_used: self.generation, + children: None, + galley: galley.clone(), + }); + galley + } + } + }; + + (hash, galley) + } + + fn layout( + &mut self, + fonts: &mut FontsImpl, + job: LayoutJob, + allow_split_paragraphs: bool, + ) -> Arc { + self.layout_internal(fonts, job, allow_split_paragraphs).1 + } + + /// Split on `\n` and lay out (and cache) each paragraph individually. + fn layout_each_paragraph_individuallly( + &mut self, + fonts: &mut FontsImpl, + job: &LayoutJob, + ) -> (Vec>, Vec) { + profiling::function_scope!(); + + let mut current_section = 0; + let mut start = 0; + let mut max_rows_remaining = job.wrap.max_rows; + let mut child_galleys = Vec::new(); + let mut child_hashes = Vec::new(); + + while start < job.text.len() { + let is_first_paragraph = start == 0; + let end = job.text[start..] + .find('\n') + .map_or(job.text.len(), |i| start + i + 1); + + let mut paragraph_job = LayoutJob { + text: job.text[start..end].to_owned(), + wrap: crate::text::TextWrapping { + max_rows: max_rows_remaining, + ..job.wrap + }, + sections: Vec::new(), + break_on_newline: job.break_on_newline, + halign: job.halign, + justify: job.justify, + first_row_min_height: if is_first_paragraph { + job.first_row_min_height + } else { + 0.0 + }, + round_output_to_gui: job.round_output_to_gui, + }; + + // Add overlapping sections: + for section in &job.sections[current_section..job.sections.len()] { + let LayoutSection { + leading_space, + byte_range: section_range, + format, + } = section; + + // `start` and `end` are the byte range of the current paragraph. + // How does the current section overlap with the paragraph range? + + if section_range.end <= start { + // The section is behind us + current_section += 1; + } else if end <= section_range.start { + break; // Haven't reached this one yet. + } else { + // Section range overlaps with paragraph range + debug_assert!( + section_range.start < section_range.end, + "Bad byte_range: {section_range:?}" + ); + let new_range = section_range.start.saturating_sub(start) + ..(section_range.end.at_most(end)).saturating_sub(start); + debug_assert!( + new_range.start <= new_range.end, + "Bad new section range: {new_range:?}" + ); + paragraph_job.sections.push(LayoutSection { + leading_space: if start <= new_range.start { + *leading_space + } else { + 0.0 + }, + byte_range: new_range, + format: format.clone(), + }); + } + } + + // TODO(emilk): we could lay out each paragraph in parallel to get a nice speedup on multicore machines. + let (hash, galley) = self.layout_internal(fonts, paragraph_job, false); + child_hashes.push(hash); + + // 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; + child_galleys.push(galley); + if elided { + break; + } + + start = end; } + + (child_galleys, child_hashes) } pub fn num_galleys_in_cache(&self) -> usize { @@ -797,6 +964,16 @@ impl GalleyCache { } } +/// If true, lay out and cache each paragraph (sections separated by newlines) individually. +/// +/// This makes it much faster to re-layout the full text when only a portion of it has changed since last frame, i.e. when editing somewhere in a file with thousands of lines/paragraphs. +fn should_cache_each_paragraph_individually(job: &LayoutJob) -> bool { + // We currently don't support this elided text, i.e. when `max_rows` is set. + // Most often, elided text is elided to one row, + // and so will always be fast to lay out. + job.break_on_newline && job.wrap.max_rows == usize::MAX && job.text.contains('\n') +} + // ---------------------------------------------------------------------------- struct FontImplCache { @@ -867,3 +1044,121 @@ impl FontImplCache { .clone() } } + +#[cfg(feature = "default_fonts")] +#[cfg(test)] +mod tests { + use core::f32; + + use super::*; + use crate::{text::TextFormat, Stroke}; + use ecolor::Color32; + use emath::Align; + + fn jobs() -> Vec { + vec![ + LayoutJob::simple( + String::default(), + FontId::new(14.0, FontFamily::Monospace), + Color32::WHITE, + f32::INFINITY, + ), + LayoutJob::simple( + "Simple test.".to_owned(), + FontId::new(14.0, FontFamily::Monospace), + Color32::WHITE, + f32::INFINITY, + ), + LayoutJob::simple( + "This some text that may be long.\nDet kanske också finns lite ÅÄÖ här.".to_owned(), + FontId::new(14.0, FontFamily::Proportional), + Color32::WHITE, + 50.0, + ), + { + let mut job = LayoutJob { + first_row_min_height: 20.0, + ..Default::default() + }; + job.append( + "1st paragraph has some leading space.\n", + 16.0, + TextFormat { + font_id: FontId::new(14.0, FontFamily::Proportional), + ..Default::default() + }, + ); + job.append( + "2nd paragraph has underline and strikthrough, and has some non-ASCII characters:\n ÅÄÖ.", + 0.0, + TextFormat { + font_id: FontId::new(15.0, FontFamily::Monospace), + underline: Stroke::new(1.0, Color32::RED), + strikethrough: Stroke::new(1.0, Color32::GREEN), + ..Default::default() + }, + ); + job.append( + "3rd paragraph is kind of boring, but has italics.\nAnd a newline", + 0.0, + TextFormat { + font_id: FontId::new(10.0, FontFamily::Proportional), + italics: true, + ..Default::default() + }, + ); + + job + }, + ] + } + + #[test] + fn test_split_paragraphs() { + for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] { + let max_texture_side = 4096; + let mut fonts = FontsImpl::new( + pixels_per_point, + max_texture_side, + FontDefinitions::default(), + ); + + for halign in [Align::Min, Align::Center, Align::Max] { + for justify in [false, true] { + for mut job in jobs() { + job.halign = halign; + job.justify = justify; + + let whole = GalleyCache::default().layout(&mut fonts, job.clone(), false); + + let split = GalleyCache::default().layout(&mut fonts, job.clone(), true); + + for (i, row) in whole.rows.iter().enumerate() { + println!( + "Whole row {i}: section_index_at_start={}, first glyph section_index: {:?}", + row.row.section_index_at_start, + row.row.glyphs.first().map(|g| g.section_index) + ); + } + for (i, row) in split.rows.iter().enumerate() { + println!( + "Split row {i}: section_index_at_start={}, first glyph section_index: {:?}", + row.row.section_index_at_start, + row.row.glyphs.first().map(|g| g.section_index) + ); + } + + // Don't compare for equaliity; but format with a specific precision and make sure we hit that. + // NOTE: we use a rather low precision, because as long as we're within a pixel I think it's good enough. + similar_asserts::assert_eq!( + format!("{:#.1?}", split), + format!("{:#.1?}", whole), + "pixels_per_point: {pixels_per_point:.2}, input text: '{}'", + job.text + ); + } + } + } + } + } +} diff --git a/crates/epaint/src/text/mod.rs b/crates/epaint/src/text/mod.rs index 3cb0e98cb..cf5c8ebfc 100644 --- a/crates/epaint/src/text/mod.rs +++ b/crates/epaint/src/text/mod.rs @@ -14,7 +14,7 @@ pub use { FontData, FontDefinitions, FontFamily, FontId, FontInsert, FontPriority, FontTweak, Fonts, FontsImpl, InsertFontFamily, }, - text_layout::layout, + text_layout::*, text_layout_types::*, }; diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index d1b2d5038..b2dba96fc 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1,11 +1,10 @@ -use std::ops::RangeInclusive; use std::sync::Arc; use emath::{pos2, vec2, Align, GuiRounding as _, NumExt, Pos2, Rect, Vec2}; use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex}; -use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals}; +use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals}; // ---------------------------------------------------------------------------- @@ -70,12 +69,14 @@ impl Paragraph { /// In most cases you should use [`crate::Fonts::layout_job`] instead /// since that memoizes the input, making subsequent layouting of the same text much faster. pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { + profiling::function_scope!(); + if job.wrap.max_rows == 0 { // Early-out: no text return Galley { job, rows: Default::default(), - rect: Rect::from_min_max(Pos2::ZERO, Pos2::ZERO), + rect: Rect::ZERO, mesh_bounds: Rect::NOTHING, num_vertices: 0, num_indices: 0, @@ -96,10 +97,11 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { let mut elided = false; let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided); if elided { - if let Some(last_row) = rows.last_mut() { + if let Some(last_placed) = rows.last_mut() { + let last_row = Arc::make_mut(&mut last_placed.row); replace_last_glyph_with_overflow_character(fonts, &job, last_row); if let Some(last) = last_row.glyphs.last() { - last_row.rect.max.x = last.max_x(); + last_row.size.x = last.max_x(); } } } @@ -108,12 +110,12 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { if justify || job.halign != Align::LEFT { let num_rows = rows.len(); - for (i, row) in rows.iter_mut().enumerate() { + for (i, placed_row) in rows.iter_mut().enumerate() { let is_last_row = i + 1 == num_rows; - let justify_row = justify && !row.ends_with_newline && !is_last_row; + let justify_row = justify && !placed_row.ends_with_newline && !is_last_row; halign_and_justify_row( point_scale, - row, + placed_row, job.halign, job.wrap.max_width, justify_row, @@ -188,17 +190,12 @@ fn layout_section( } } -/// We ignore y at this stage -fn rect_from_x_range(x_range: RangeInclusive) -> Rect { - Rect::from_x_y_ranges(x_range, 0.0..=0.0) -} - // Ignores the Y coordinate. fn rows_from_paragraphs( paragraphs: Vec, job: &LayoutJob, elided: &mut bool, -) -> Vec { +) -> Vec { let num_paragraphs = paragraphs.len(); let mut rows = vec![]; @@ -212,31 +209,35 @@ fn rows_from_paragraphs( let is_last_paragraph = (i + 1) == num_paragraphs; if paragraph.glyphs.is_empty() { - rows.push(Row { - section_index_at_start: paragraph.section_index_at_start, - glyphs: vec![], - visuals: Default::default(), - rect: Rect::from_min_size( - pos2(paragraph.cursor_x, 0.0), - vec2(0.0, paragraph.empty_paragraph_height), - ), - ends_with_newline: !is_last_paragraph, + rows.push(PlacedRow { + pos: Pos2::ZERO, + row: Arc::new(Row { + section_index_at_start: paragraph.section_index_at_start, + glyphs: vec![], + visuals: Default::default(), + size: vec2(0.0, paragraph.empty_paragraph_height), + ends_with_newline: !is_last_paragraph, + }), }); } else { let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x(); if paragraph_max_x <= job.effective_wrap_width() { // Early-out optimization: the whole paragraph fits on one row. - let paragraph_min_x = paragraph.glyphs[0].pos.x; - rows.push(Row { - section_index_at_start: paragraph.section_index_at_start, - glyphs: paragraph.glyphs, - visuals: Default::default(), - rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), - ends_with_newline: !is_last_paragraph, + rows.push(PlacedRow { + pos: pos2(0.0, f32::NAN), + row: Arc::new(Row { + section_index_at_start: paragraph.section_index_at_start, + glyphs: paragraph.glyphs, + visuals: Default::default(), + size: vec2(paragraph_max_x, 0.0), + ends_with_newline: !is_last_paragraph, + }), }); } else { line_break(¶graph, job, &mut rows, elided); - rows.last_mut().unwrap().ends_with_newline = !is_last_paragraph; + let placed_row = rows.last_mut().unwrap(); + let row = Arc::make_mut(&mut placed_row.row); + row.ends_with_newline = !is_last_paragraph; } } } @@ -244,7 +245,12 @@ fn rows_from_paragraphs( rows } -fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, elided: &mut bool) { +fn line_break( + paragraph: &Paragraph, + job: &LayoutJob, + out_rows: &mut Vec, + elided: &mut bool, +) { let wrap_width = job.effective_wrap_width(); // Keeps track of good places to insert row break if we exceed `wrap_width`. @@ -270,12 +276,15 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e { // Allow the first row to be completely empty, because we know there will be more space on the next row: // TODO(emilk): this records the height of this first row as zero, though that is probably fine since first_row_indentation usually comes with a first_row_min_height. - out_rows.push(Row { - section_index_at_start: paragraph.section_index_at_start, - glyphs: vec![], - visuals: Default::default(), - rect: rect_from_x_range(first_row_indentation..=first_row_indentation), - ends_with_newline: false, + out_rows.push(PlacedRow { + pos: pos2(0.0, f32::NAN), + row: Arc::new(Row { + section_index_at_start: paragraph.section_index_at_start, + glyphs: vec![], + visuals: Default::default(), + size: Vec2::ZERO, + ends_with_newline: false, + }), }); row_start_x += first_row_indentation; first_row_indentation = 0.0; @@ -291,15 +300,17 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e .collect(); let section_index_at_start = glyphs[0].section_index; - let paragraph_min_x = glyphs[0].pos.x; let paragraph_max_x = glyphs.last().unwrap().max_x(); - out_rows.push(Row { - section_index_at_start, - glyphs, - visuals: Default::default(), - rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), - ends_with_newline: false, + out_rows.push(PlacedRow { + pos: pos2(0.0, f32::NAN), + row: Arc::new(Row { + section_index_at_start, + glyphs, + visuals: Default::default(), + size: vec2(paragraph_max_x, 0.0), + ends_with_newline: false, + }), }); // Start a new row: @@ -333,12 +344,15 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e let paragraph_min_x = glyphs[0].pos.x; let paragraph_max_x = glyphs.last().unwrap().max_x(); - out_rows.push(Row { - section_index_at_start, - glyphs, - visuals: Default::default(), - rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x), - ends_with_newline: false, + out_rows.push(PlacedRow { + pos: pos2(paragraph_min_x, 0.0), + row: Arc::new(Row { + section_index_at_start, + glyphs, + visuals: Default::default(), + size: vec2(paragraph_max_x - paragraph_min_x, 0.0), + ends_with_newline: false, + }), }); } } @@ -500,11 +514,13 @@ fn replace_last_glyph_with_overflow_character( /// Ignores the Y coordinate. fn halign_and_justify_row( point_scale: PointScale, - row: &mut Row, + placed_row: &mut PlacedRow, halign: Align, wrap_width: f32, justify: bool, ) { + let row = Arc::make_mut(&mut placed_row.row); + if row.glyphs.is_empty() { return; } @@ -572,7 +588,8 @@ fn halign_and_justify_row( / (num_spaces_in_range as f32); } - let mut translate_x = target_min_x - original_min_x - extra_x_per_glyph * glyph_range.0 as f32; + placed_row.pos.x = point_scale.round_to_pixel(target_min_x); + let mut translate_x = -original_min_x - extra_x_per_glyph * glyph_range.0 as f32; for glyph in &mut row.glyphs { glyph.pos.x += translate_x; @@ -584,23 +601,23 @@ fn halign_and_justify_row( } // Note we ignore the leading/trailing whitespace here! - row.rect.min.x = target_min_x; - row.rect.max.x = target_max_x; + row.size.x = target_max_x - target_min_x; } /// Calculate the Y positions and tessellate the text. fn galley_from_rows( point_scale: PointScale, job: Arc, - mut rows: Vec, + mut rows: Vec, elided: bool, ) -> Galley { let mut first_row_min_height = job.first_row_min_height; let mut cursor_y = 0.0; - let mut min_x: f32 = 0.0; - let mut max_x: f32 = 0.0; - for row in &mut rows { - let mut max_row_height = first_row_min_height.max(row.rect.height()); + + for placed_row in &mut rows { + let mut max_row_height = first_row_min_height.max(placed_row.rect().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); @@ -611,8 +628,7 @@ fn galley_from_rows( for glyph in &mut row.glyphs { let format = &job.sections[glyph.section_index as usize].format; - glyph.pos.y = cursor_y - + glyph.font_impl_ascent + glyph.pos.y = glyph.font_impl_ascent // Apply valign to the different in height of the entire row, and the height of this `Font`: + format.valign.to_factor() * (max_row_height - glyph.line_height) @@ -624,53 +640,38 @@ fn galley_from_rows( glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y); } - row.rect.min.y = cursor_y; - row.rect.max.y = cursor_y + max_row_height; + placed_row.pos.y = cursor_y; + row.size.y = max_row_height; - min_x = min_x.min(row.rect.min.x); - max_x = max_x.max(row.rect.max.x); cursor_y += max_row_height; cursor_y = point_scale.round_to_pixel(cursor_y); // TODO(emilk): it would be better to do the calculations in pixels instead. } let format_summary = format_summary(&job); + let mut rect = Rect::ZERO; let mut mesh_bounds = Rect::NOTHING; let mut num_vertices = 0; let mut num_indices = 0; - for row in &mut rows { + for placed_row in &mut rows { + rect = rect.union(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); + + mesh_bounds = + mesh_bounds.union(row.visuals.mesh_bounds.translate(placed_row.pos.to_vec2())); num_vertices += row.visuals.mesh.vertices.len(); num_indices += row.visuals.mesh.indices.len(); - } - let mut rect = Rect::from_min_max(pos2(min_x, 0.0), pos2(max_x, cursor_y)); - - if job.round_output_to_gui { - for row in &mut rows { - row.rect = row.rect.round_ui(); - } - - let did_exceed_wrap_width_by_a_lot = rect.width() > job.wrap.max_width + 1.0; - - rect = rect.round_ui(); - - if did_exceed_wrap_width_by_a_lot { - // If the user picked a too aggressive wrap width (e.g. more narrow than any individual glyph), - // we should let the user know by reporting that our width is wider than the wrap width. - } else { - // Make sure we don't report being wider than the wrap width the user picked: - rect.max.x = rect - .max - .x - .at_most(rect.min.x + job.wrap.max_width) - .floor_ui(); + row.section_index_at_start = u32::MAX; // No longer in use. + for glyph in &mut row.glyphs { + glyph.section_index = u32::MAX; // No longer in use. } } - Galley { + let mut galley = Galley { job, rows, elided, @@ -679,7 +680,13 @@ fn galley_from_rows( num_vertices, num_indices, pixels_per_point: point_scale.pixels_per_point, + }; + + if galley.job.round_output_to_gui { + galley.round_output_to_gui(); } + + galley } #[derive(Default)] @@ -876,7 +883,7 @@ fn add_row_hline( let (stroke, mut y) = stroke_and_y(glyph); stroke.round_center_to_pixel(point_scale.pixels_per_point, &mut y); - if stroke == Stroke::NONE { + if stroke.is_empty() { end_line(line_start.take(), last_right_x); } else if let Some((existing_stroke, start)) = line_start { if existing_stroke == stroke && start.y == y { @@ -1130,6 +1137,7 @@ mod tests { vec!["# DNA…"] ); let row = &galley.rows[0]; - assert_eq!(row.rect.max.x, row.glyphs.last().unwrap().max_x()); + assert_eq!(row.pos, Pos2::ZERO); + assert_eq!(row.rect().max.x, row.glyphs.last().unwrap().max_x()); } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index b6d7ccf49..4bd15d3e3 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, NumExt, OrderedFloat, Pos2, Rect, Vec2}; +use emath::{pos2, vec2, Align, GuiRounding as _, NumExt, OrderedFloat, Pos2, Rect, Vec2}; /// Describes the task of laying out text. /// @@ -508,14 +508,14 @@ pub struct Galley { /// Contains the original string and style sections. pub job: Arc, - /// Rows of text, from top to bottom. + /// Rows of text, from top to bottom, and their offsets. /// /// The number of characters in all rows sum up to `job.text.chars().count()` /// unless [`Self::elided`] is `true`. /// /// Note that a paragraph (a piece of text separated with `\n`) /// can be split up into multiple rows. - pub rows: Vec, + pub rows: Vec, /// Set to true the text was truncated due to [`TextWrapping::max_rows`]. pub elided: bool, @@ -547,19 +547,50 @@ pub struct Galley { pub pixels_per_point: f32, } +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct PlacedRow { + /// The position of this [`Row`] relative to the galley. + /// + /// This is rounded to the closest _pixel_ in order to produce crisp, pixel-perfect text. + pub pos: Pos2, + + /// The underlying unpositioned [`Row`]. + pub row: Arc, +} + +impl PlacedRow { + /// Logical bounding rectangle on font heights etc. + /// Use this when drawing a selection or similar! + pub fn rect(&self) -> Rect { + Rect::from_min_size(self.pos, self.row.size) + } +} + +impl std::ops::Deref for PlacedRow { + type Target = Row; + + fn deref(&self) -> &Self::Target { + &self.row + } +} + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Row { - /// This is included in case there are no glyphs - pub section_index_at_start: u32, + /// This is included in case there are no glyphs. + /// + /// Only used during layout, then set to an invalid value in order to + /// enable the paragraph-concat optimization path without having to + /// adjust `section_index` when concatting. + pub(crate) section_index_at_start: u32, /// One for each `char`. pub glyphs: Vec, - /// Logical bounding rectangle based on font heights etc. - /// Use this when drawing a selection or similar! + /// Logical size based on font heights etc. /// Includes leading and trailing whitespace. - pub rect: Rect, + pub size: Vec2, /// The mesh, ready to be rendered. pub visuals: RowVisuals, @@ -613,7 +644,7 @@ pub struct Glyph { /// The character this glyph represents. pub chr: char, - /// Baseline position, relative to the galley. + /// Baseline position, relative to the row. /// Logical position: pos.y is the same for all chars of the same [`TextFormat`]. pub pos: Pos2, @@ -642,7 +673,11 @@ pub struct Glyph { pub uv_rect: UvRect, /// Index into [`LayoutJob::sections`]. Decides color etc. - pub section_index: u32, + /// + /// Only used during layout, then set to an invalid value in order to + /// enable the paragraph-concat optimization path without having to + /// adjust `section_index` when concatting. + pub(crate) section_index: u32, } impl Glyph { @@ -683,22 +718,7 @@ impl Row { self.glyphs.len() + (self.ends_with_newline as usize) } - #[inline] - pub fn min_y(&self) -> f32 { - self.rect.top() - } - - #[inline] - pub fn max_y(&self) -> f32 { - self.rect.bottom() - } - - #[inline] - pub fn height(&self) -> f32 { - self.rect.height() - } - - /// Closest char at the desired x coordinate. + /// Closest char at the desired x coordinate in row-relative coordinates. /// Returns something in the range `[0, char_count_excluding_newline()]`. pub fn char_at(&self, desired_x: f32) -> usize { for (i, glyph) in self.glyphs.iter().enumerate() { @@ -713,9 +733,26 @@ impl Row { if let Some(glyph) = self.glyphs.get(column) { glyph.pos.x } else { - self.rect.right() + self.size.x } } + + #[inline] + pub fn height(&self) -> f32 { + self.size.y + } +} + +impl PlacedRow { + #[inline] + pub fn min_y(&self) -> f32 { + self.rect().top() + } + + #[inline] + pub fn max_y(&self) -> f32 { + self.rect().bottom() + } } impl Galley { @@ -734,6 +771,92 @@ impl Galley { pub fn size(&self) -> Vec2 { self.rect.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) + let rounded_size = placed_row.row.size.round_ui(); + if placed_row.row.size != rounded_size { + Arc::make_mut(&mut placed_row.row).size = rounded_size; + } + } + + let rect = &mut self.rect; + + let did_exceed_wrap_width_by_a_lot = rect.width() > self.job.wrap.max_width + 1.0; + + *rect = rect.round_ui(); + + if did_exceed_wrap_width_by_a_lot { + // If the user picked a too aggressive wrap width (e.g. more narrow than any individual glyph), + // we should let the user know by reporting that our width is wider than the wrap width. + } else { + // Make sure we don't report being wider than the wrap width the user picked: + rect.max.x = rect + .max + .x + .at_most(rect.min.x + self.job.wrap.max_width) + .floor_ui(); + } + } + + /// Append each galley under the previous one. + pub fn concat(job: Arc, galleys: &[Arc], pixels_per_point: f32) -> Self { + profiling::function_scope!(); + + let mut merged_galley = Self { + job, + rows: Vec::new(), + elided: false, + rect: Rect::ZERO, + mesh_bounds: Rect::NOTHING, + num_vertices: 0, + num_indices: 0, + pixels_per_point, + }; + + for (i, galley) in galleys.iter().enumerate() { + let current_y_offset = merged_galley.rect.height(); + + 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(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(), + } + })); + + merged_galley.num_vertices += galley.num_vertices; + merged_galley.num_indices += galley.num_indices; + // 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; + } + + if merged_galley.job.round_output_to_gui { + merged_galley.round_output_to_gui(); + } + + merged_galley + } } impl AsRef for Galley { @@ -765,7 +888,7 @@ impl Galley { /// Zero-width rect past the last character. fn end_pos(&self) -> Rect { if let Some(row) = self.rows.last() { - let x = row.rect.right(); + let x = row.rect().right(); Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y())) } else { // Empty galley @@ -816,11 +939,15 @@ impl Galley { let mut ccursor_index = 0; for row in &self.rows { - let is_pos_within_row = row.min_y() <= pos.y && pos.y <= row.max_y(); - let y_dist = (row.min_y() - pos.y).abs().min((row.max_y() - pos.y).abs()); + let min_y = row.min_y(); + let max_y = row.max_y(); + + let is_pos_within_row = min_y <= pos.y && pos.y <= max_y; + let y_dist = (min_y - pos.y).abs().min((max_y - pos.y).abs()); if is_pos_within_row || y_dist < best_y_dist { best_y_dist = y_dist; - let column = row.char_at(pos.x); + // char_at is `Row` not `PlacedRow` relative which means we have to subtract the pos. + let column = row.char_at(pos.x - row.pos.x); let prefer_next_row = column < row.char_count_excluding_newline(); cursor = CCursor { index: ccursor_index + column, From d78fc39386e28ec7a18bd48a28a688177f090bd4 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 3 Apr 2025 09:26:49 +0200 Subject: [PATCH 010/388] Use lychee link checker instead of linkinator (#5868) Seems like linkinator doesn't find any files: https://github.com/emilk/egui/pull/5853#issuecomment-2765526298 This will check all links in .md files (except CHANGELOG.md) and in toml files --- .github/workflows/spelling_and_links.yml | 22 +++++++++++++--------- ARCHITECTURE.md | 2 +- README.md | 4 ++-- crates/eframe/README.md | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.github/workflows/spelling_and_links.yml b/.github/workflows/spelling_and_links.yml index d7b32b007..8fb16ae27 100644 --- a/.github/workflows/spelling_and_links.yml +++ b/.github/workflows/spelling_and_links.yml @@ -15,16 +15,20 @@ jobs: - name: Check spelling of entire workspace uses: crate-ci/typos@master - linkinator: - name: linkinator + lychee: + name: lychee runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: jprochazk/linkinator-action@main - with: - linksToSkip: "https://crates.io/crates/.*, http://localhost:.*" # Avoid crates.io rate-limiting - retry: true - retryErrors: true - retryErrorsCount: 5 - retryErrorsJitter: 2000 + - name: Don't check CHANGELOG.md files + # This is really stupid but lychee doesn't have a way of excluding files via GLOB: + # https://github.com/lycheeverse/lychee/issues/1608 + + # We need to exclude CHANGELOG.md since we don't want to have a CI failure everytime some contributor decides + # to change their username. + run: rm -r */**/CHANGELOG.md CHANGELOG.md + - name: Link Checker + uses: lycheeverse/lychee-action@v2 + with: + args: "'**/*.md' '**/*.toml' --exclude localhost --exclude reddit.com" # I guess reddit doesn't like github action IPs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 000f2b7ac..51d6d41d1 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -51,7 +51,7 @@ Thin wrapper around `egui_demo_lib` so we can compile it to a web site or a nati Depends on `egui_demo_lib` + `eframe`. ### `egui_kittest` -A test harness for egui based on [kittest](https://github.com/rerun/kittest) and [AccessKit](https://github.com/AccessKit/accesskit/). +A test harness for egui based on [kittest](https://github.com/rerun-io/kittest) and [AccessKit](https://github.com/AccessKit/accesskit/). ### Other integrations diff --git a/README.md b/README.md index 63e5f3ada..087d2aad5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Latest version](https://img.shields.io/crates/v/egui.svg)](https://crates.io/crates/egui) [![Documentation](https://docs.rs/egui/badge.svg)](https://docs.rs/egui) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) -[![Build Status](https://github.com/emilk/egui/workflows/CI/badge.svg)](https://github.com/emilk/egui/actions?workflow=CI) +[![Build Status](https://github.com/emilk/egui/workflows/Rust/badge.svg)](https://github.com/emilk/egui/actions/workflows/rust.yml) [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-MIT) [![Apache](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-APACHE) [![Discord](https://img.shields.io/discord/900275882684477440?label=egui%20discord)](https://discord.gg/JFcEma9bJq) @@ -353,7 +353,7 @@ Notable contributions by: * [@AsmPrgmC3](https://github.com/AsmPrgmC3): [Proper sRGBA blending for web](https://github.com/emilk/egui/pull/650) * [@AlexApps99](https://github.com/AlexApps99): [`egui_glow`](https://github.com/emilk/egui/pull/685) * [@mankinskin](https://github.com/mankinskin): [Context menus](https://github.com/emilk/egui/pull/543) -* [@t18b219k](https://github.com/t18b219k): [Port glow painter to web](https://github.com/emilk/egui/pull/868) +* [@KentaTheBugMaker](https://github.com/KentaTheBugMaker): [Port glow painter to web](https://github.com/emilk/egui/pull/868) * [@danielkeller](https://github.com/danielkeller): [`Context` refactor](https://github.com/emilk/egui/pull/1050) * [@MaximOsipenko](https://github.com/MaximOsipenko): [`Context` lock refactor](https://github.com/emilk/egui/pull/2625) * [@mwcampbell](https://github.com/mwcampbell): [AccessKit](https://github.com/AccessKit/accesskit) [integration](https://github.com/emilk/egui/pull/2294) diff --git a/crates/eframe/README.md b/crates/eframe/README.md index 5ffd427d0..6f3f44009 100644 --- a/crates/eframe/README.md +++ b/crates/eframe/README.md @@ -26,7 +26,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib 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 can opt-in to the using [`egui_wgpu`](https://github.com/emilk/egui/tree/master/crates/egui_wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. +You can opt-in to the using [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. ## Alternatives `eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others. From fe631ff9ea999209b8109482e10356dbf357c709 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 6 Apr 2025 12:45:20 -0400 Subject: [PATCH 011/388] Use `TextBuffer` for `layouter` in `TextEdit` instead of `&str` (#5712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change allows `layouter` to use the `TextBuffer` instead of `&str` in the closure. It is necessary when layout decisions depend on more than just the raw string content, such as metadata stored in the concrete type implementing `TextBuffer`. In [our use case](https://github.com/damus-io/notedeck/pull/723), we needed this to support mention highlighting when a user selects a mention. Since mentions can contain spaces, determining mention boundaries from the `&str` alone is impossible. Instead, we use the `TextBuffer` implementation to retrieve the correct bounds. See the video below for a demonstration: https://github.com/user-attachments/assets/3cba2906-5546-4b52-b728-1da9c56a83e1 # Breaking change This PR introduces a breaking change to the `layouter` function in `TextEdit`. Previous API: ```rust pub fn layouter(mut self, layouter: &'t mut dyn FnMut(&Ui, &str, f32) -> Arc) -> Self ``` New API: ```rust pub fn layouter(mut self, layouter: &'t mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc) -> Self ``` ## Impact on Existing Code • Any existing usage of `layouter` will **no longer compile**. • Callers must update their closures to use `&dyn TextBuffer` instead of `&str`. ## Migration Guide Before: ```rust let mut layouter = |ui: &Ui, text: &str, wrap_width: f32| {     let layout_job = my_highlighter(text);     layout_job.wrap.max_width = wrap_width;     ui.fonts(|f| f.layout_job(layout_job)) }; ``` After: ```rust let mut layouter = |ui: &Ui, text: &dyn TextBuffer, wrap_width: f32| { let layout_job = my_highlighter(text.as_str()); layout_job.wrap.max_width = wrap_width; ui.fonts(|f| f.layout_job(layout_job)) }; ``` --- * There is not an issue for this change. * [x] I have followed the instructions in the PR template Signed-off-by: kernelkind --- crates/egui/src/widgets/text_edit/builder.rs | 23 ++++++---- .../egui/src/widgets/text_edit/text_buffer.rs | 45 +++++++++++++++++++ crates/egui_demo_lib/src/demo/code_editor.rs | 4 +- .../src/easy_mark/easy_mark_editor.rs | 4 +- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index d73ecd3f6..6a4244496 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -19,6 +19,8 @@ use crate::{ use super::{TextEditOutput, TextEditState}; +type LayouterFn<'t> = &'t mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc; + /// A text region that the user can edit the contents of. /// /// See also [`Ui::text_edit_singleline`] and [`Ui::text_edit_multiline`]. @@ -71,7 +73,7 @@ pub struct TextEdit<'t> { id_salt: Option, font_selection: FontSelection, text_color: Option, - layouter: Option<&'t mut dyn FnMut(&Ui, &str, f32) -> Arc>, + layouter: Option>, password: bool, frame: bool, margin: Margin, @@ -261,8 +263,8 @@ impl<'t> TextEdit<'t> { /// # egui::__run_test_ui(|ui| { /// # let mut my_code = String::new(); /// # fn my_memoized_highlighter(s: &str) -> egui::text::LayoutJob { Default::default() } - /// let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| { - /// let mut layout_job: egui::text::LayoutJob = my_memoized_highlighter(string); + /// let mut layouter = |ui: &egui::Ui, buf: &dyn egui::TextBuffer, wrap_width: f32| { + /// let mut layout_job: egui::text::LayoutJob = my_memoized_highlighter(buf.as_str()); /// layout_job.wrap.max_width = wrap_width; /// ui.fonts(|f| f.layout_job(layout_job)) /// }; @@ -270,7 +272,10 @@ impl<'t> TextEdit<'t> { /// # }); /// ``` #[inline] - pub fn layouter(mut self, layouter: &'t mut dyn FnMut(&Ui, &str, f32) -> Arc) -> Self { + pub fn layouter( + mut self, + layouter: &'t mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc, + ) -> Self { self.layouter = Some(layouter); self @@ -510,8 +515,8 @@ impl TextEdit<'_> { }; let font_id_clone = font_id.clone(); - let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| { - let text = mask_if_password(password, text); + let mut default_layouter = move |ui: &Ui, text: &dyn TextBuffer, wrap_width: f32| { + let text = mask_if_password(password, text.as_str()); let layout_job = if multiline { LayoutJob::simple(text, font_id_clone.clone(), text_color, wrap_width) } else { @@ -522,7 +527,7 @@ impl TextEdit<'_> { let layouter = layouter.unwrap_or(&mut default_layouter); - let mut galley = layouter(ui, text.as_str(), wrap_width); + let mut galley = layouter(ui, text, wrap_width); let desired_inner_width = if clip_text { wrap_width // visual clipping with scroll in singleline input. @@ -879,7 +884,7 @@ fn events( state: &mut TextEditState, text: &mut dyn TextBuffer, galley: &mut Arc, - layouter: &mut dyn FnMut(&Ui, &str, f32) -> Arc, + layouter: &mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc, id: Id, wrap_width: f32, multiline: bool, @@ -1094,7 +1099,7 @@ fn events( any_change = true; // Layout again to avoid frame delay, and to keep `text` and `galley` in sync. - *galley = layouter(ui, text.as_str(), wrap_width); + *galley = layouter(ui, text, wrap_width); // Set cursor_range using new galley: cursor_range = new_ccursor_range; diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index ccf3a0958..6cf7da15a 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -172,6 +172,39 @@ pub trait TextBuffer { self.delete_selected(&CCursorRange::two(min, max)) } } + + /// Returns a unique identifier for the implementing type. + /// + /// This is useful for downcasting from this trait to the implementing type. + /// Here is an example usage: + /// ``` + /// use egui::TextBuffer; + /// use std::any::TypeId; + /// + /// struct ExampleBuffer {} + /// + /// impl TextBuffer for ExampleBuffer { + /// fn is_mutable(&self) -> bool { unimplemented!() } + /// fn as_str(&self) -> &str { unimplemented!() } + /// fn insert_text(&mut self, text: &str, char_index: usize) -> usize { unimplemented!() } + /// fn delete_char_range(&mut self, char_range: std::ops::Range) { unimplemented!() } + /// + /// // Implement it like the following: + /// fn type_id(&self) -> TypeId { + /// TypeId::of::() + /// } + /// } + /// + /// // Example downcast: + /// pub fn downcast_example(buffer: &dyn TextBuffer) -> Option<&ExampleBuffer> { + /// if buffer.type_id() == TypeId::of::() { + /// unsafe { Some(&*(buffer as *const dyn TextBuffer as *const ExampleBuffer)) } + /// } else { + /// None + /// } + /// } + /// ``` + fn type_id(&self) -> std::any::TypeId; } impl TextBuffer for String { @@ -218,6 +251,10 @@ impl TextBuffer for String { fn take(&mut self) -> String { std::mem::take(self) } + + fn type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::() + } } impl TextBuffer for Cow<'_, str> { @@ -248,6 +285,10 @@ impl TextBuffer for Cow<'_, str> { fn take(&mut self) -> String { std::mem::take(self).into_owned() } + + fn type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::>() + } } /// Immutable view of a `&str`! @@ -265,4 +306,8 @@ impl TextBuffer for &str { } fn delete_char_range(&mut self, _ch_range: Range) {} + + fn type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::<&str>() + } } diff --git a/crates/egui_demo_lib/src/demo/code_editor.rs b/crates/egui_demo_lib/src/demo/code_editor.rs index fe39e1be8..2d67f7d46 100644 --- a/crates/egui_demo_lib/src/demo/code_editor.rs +++ b/crates/egui_demo_lib/src/demo/code_editor.rs @@ -76,12 +76,12 @@ impl crate::View for CodeEditor { }); }); - let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| { + let mut layouter = |ui: &egui::Ui, buf: &dyn egui::TextBuffer, wrap_width: f32| { let mut layout_job = egui_extras::syntax_highlighting::highlight( ui.ctx(), ui.style(), &theme, - string, + buf.as_str(), language, ); layout_job.wrap.max_width = wrap_width; 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 9e730fac5..a0fcdbbf2 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 @@ -80,8 +80,8 @@ impl EasyMarkEditor { } = self; let response = if self.highlight_editor { - let mut layouter = |ui: &egui::Ui, easymark: &str, wrap_width: f32| { - let mut layout_job = highlighter.highlight(ui.style(), easymark); + let mut layouter = |ui: &egui::Ui, easymark: &dyn TextBuffer, wrap_width: f32| { + let mut layout_job = highlighter.highlight(ui.style(), easymark.as_str()); layout_job.wrap.max_width = wrap_width; ui.fonts(|f| f.layout_job(layout_job)) }; From 4445497546266f2499d63dba0c0e037c49c5af68 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 8 Apr 2025 10:16:29 +0100 Subject: [PATCH 012/388] `Scene`: Set transform layer before calling user content (#5884) This changes the `Scene` behaviour to call `set_transform_layer` prior to calling the user content fn, rather than after. ### Motivation This provides a simple way for the user to access the `TSTransform` that will be applied to the `Scene` within the user content function, e.g. ```rust ui.ctx().layer_transform_to_global(ui.layer_id()) ``` Previously getting the transform like this still kind of worked, but resulted in the user content lagging behind the actual scene position by a single frame, which looks a bit strange. With this PR, the user content using the transform no longer lags by a frame, and matches the scene's transform perfectly. Accessing the `TSTransform` of the `Scene` within the user content function is useful for the case where the user may want to instantiate new `Ui` sublayers that also track the scene (by default, sublayers do *not* apply the same transform as the scene, likely the cause of #5682). With these changes, the user can have sublayers track the scene like so: ```rust let scene_layer = ui.layer_id(); let sub_layer = egui::LayerId::new(scene_layer.order, self.id); ui.ctx().set_sublayer(scene_layer, sub_layer); let scene_transform = ui.ctx().layer_transform_to_global(scene_layer).unwrap(); ui.ctx().set_transform_layer(sub_layer, scene_transform); ``` ### Tested with - `egui_demo_app` scene example. - Local `egui_graph` demo example. --- * Closes * [x] I have followed the instructions in the PR template --- crates/egui/src/containers/scene.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index fff4136f2..5b8d97410 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -160,17 +160,17 @@ impl Scene { // Set a correct global clip rect: local_ui.set_clip_rect(to_global.inverse() * outer_rect); + // Tell egui to apply the transform on the layer: + local_ui + .ctx() + .set_transform_layer(scene_layer_id, *to_global); + // Add the actual contents to the area: let ret = add_contents(&mut local_ui); // This ensures we catch clicks/drags/pans anywhere on the background. local_ui.force_set_min_rect((to_global.inverse() * outer_rect).round_ui()); - // Tell egui to apply the transform on the layer: - local_ui - .ctx() - .set_transform_layer(scene_layer_id, *to_global); - InnerResponse { response: pan_response, inner: ret, From 565966b3c9a904346adaa23db2d54f605a5a906c Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 8 Apr 2025 12:32:47 +0300 Subject: [PATCH 013/388] Fix a broken link in ARCHITECTURE.md (#5853) A quick fix for a broken link: https://github.com/rerun/kittest -> https://github.com/rerun-io/kittest * [X] I have followed the instructions in the PR template From 36e007bd8cf69566dd597775224b40b38ef773fb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 8 Apr 2025 12:36:43 +0300 Subject: [PATCH 014/388] Add overline option for Table rows (#5637) * Closes no issue, I just needed this for an app and figured it could be useful. * [x] I have followed the instructions in the PR template This PR adds an `overline` option for `egui_extras::TableRow`, which is useful for visually grouping rows. The overline consumes no layout space. A screenshot of the demo app, showing every 7th row getting an overline. Screenshot 2025-01-25 at 14 40 08 --------- Co-authored-by: Emil Ernerfeldt --- crates/egui_demo_lib/src/demo/table_demo.rs | 6 ++++++ .../egui_demo_lib/tests/snapshots/demos/Table.png | 4 ++-- crates/egui_extras/src/layout.rs | 9 +++++++++ crates/egui_extras/src/table.rs | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 5c6f42d96..17e19d01d 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -13,6 +13,7 @@ enum DemoType { pub struct TableDemo { demo: DemoType, striped: bool, + overline: bool, resizable: bool, clickable: bool, num_rows: usize, @@ -28,6 +29,7 @@ impl Default for TableDemo { Self { demo: DemoType::Manual, striped: true, + overline: true, resizable: true, clickable: true, num_rows: 10_000, @@ -65,6 +67,7 @@ impl crate::View for TableDemo { ui.vertical(|ui| { ui.horizontal(|ui| { ui.checkbox(&mut self.striped, "Striped"); + ui.checkbox(&mut self.overline, "Overline some rows"); ui.checkbox(&mut self.resizable, "Resizable columns"); ui.checkbox(&mut self.clickable, "Clickable rows"); }); @@ -212,6 +215,7 @@ impl TableDemo { let row_height = if is_thick { 30.0 } else { 18.0 }; body.row(row_height, |mut row| { row.set_selected(self.selection.contains(&row_index)); + row.set_overline(self.overline && row_index % 7 == 3); row.col(|ui| { ui.label(row_index.to_string()); @@ -247,6 +251,7 @@ impl TableDemo { }; row.set_selected(self.selection.contains(&row_index)); + row.set_overline(self.overline && row_index % 7 == 3); row.col(|ui| { ui.label(row_index.to_string()); @@ -280,6 +285,7 @@ impl TableDemo { }; row.set_selected(self.selection.contains(&row_index)); + row.set_overline(self.overline && row_index % 7 == 3); row.col(|ui| { ui.label(row_index.to_string()); diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 6189f25c0..c11788f40 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13115759157beb57febcff4be6f1710340736108b520e9ad3efb04be3cedcf7b -size 68767 +oid sha256:9446da28768cae0b489e0f6243410a8b3acf0ca2a0b70690d65d2a6221bc25b9 +size 30517 diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index fb84169f3..fb62cec40 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -33,6 +33,7 @@ pub(crate) struct StripLayoutFlags { pub(crate) striped: bool, pub(crate) hovered: bool, pub(crate) selected: bool, + pub(crate) overline: bool, /// Used when we want to accruately measure the size of this cell. pub(crate) sizing_pass: bool, @@ -232,6 +233,14 @@ impl<'l> StripLayout<'l> { child_ui.style_mut().visuals.override_text_color = Some(stroke_color); } + if flags.overline { + child_ui.painter().hline( + max_rect.x_range(), + max_rect.top(), + child_ui.visuals().widgets.noninteractive.bg_stroke, + ); + } + add_cell_contents(&mut child_ui); child_ui diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 5fca1f9df..172f1bee5 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -507,6 +507,7 @@ impl<'a> TableBuilder<'a> { striped: false, hovered: false, selected: false, + overline: false, response: &mut response, }); layout.allocate_rect(); @@ -990,6 +991,7 @@ impl<'a> TableBody<'a> { striped: self.striped && self.row_index % 2 == 0, hovered: self.hovered_row_index == Some(self.row_index), selected: false, + overline: false, response: &mut response, }); self.capture_hover_state(&response, self.row_index); @@ -1071,6 +1073,7 @@ impl<'a> TableBody<'a> { striped: self.striped && (row_index + self.row_index) % 2 == 0, hovered: self.hovered_row_index == Some(row_index), selected: false, + overline: false, response: &mut response, }); self.capture_hover_state(&response, row_index); @@ -1152,6 +1155,7 @@ impl<'a> TableBody<'a> { striped: self.striped && (row_index + self.row_index) % 2 == 0, hovered: self.hovered_row_index == Some(row_index), selected: false, + overline: false, response: &mut response, }); self.capture_hover_state(&response, row_index); @@ -1173,6 +1177,7 @@ impl<'a> TableBody<'a> { height: row_height, striped: self.striped && (row_index + self.row_index) % 2 == 0, hovered: self.hovered_row_index == Some(row_index), + overline: false, selected: false, response: &mut response, }); @@ -1260,6 +1265,7 @@ pub struct TableRow<'a, 'b> { striped: bool, hovered: bool, selected: bool, + overline: bool, response: &'b mut Option, } @@ -1297,6 +1303,7 @@ impl TableRow<'_, '_> { striped: self.striped, hovered: self.hovered, selected: self.selected, + overline: self.overline, sizing_pass: auto_size_this_frame || self.layout.ui.is_sizing_pass(), }; @@ -1333,6 +1340,13 @@ impl TableRow<'_, '_> { self.hovered = hovered; } + /// Set the overline state for this row. The overline is a line above the row, + /// usable for e.g. visually grouping rows. + #[inline] + pub fn set_overline(&mut self, overline: bool) { + self.overline = overline; + } + /// Returns a union of the [`Response`]s of the cells added to the row up to this point. /// /// You need to add at least one row to the table before calling this function. From c6bd30642a78d5ff244b064642c053f62967ef1b Mon Sep 17 00:00:00 2001 From: giuseppeferrari <70653210+giuseppeferrari@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:38:13 +0200 Subject: [PATCH 015/388] Avoid infinite extension of ColorTest inside a Window (#5698) Current implementation of ColorTest infinitely expand horizontally at each redraw if included in a Window. The effect can be see replacing the Panel in the ColorTestApp::update with a Window: ``` egui::CentralPanel::default().show(ctx, |ui| { egui::Window::new("Colors").vscroll(true).show(ctx, |ui| { if frame.is_web() { ui.label( "NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.", ); ui.separator(); } egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { self.color_test.ui(ui); }); self.color_test.ui(ui); }); ``` The cause is the is the _pixel_test_strokes_ function that, at each redraw, tries to expand the target rectangle of 2.0 points in each direction. --- crates/egui_demo_lib/src/rendering_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index 14714cca7..ddf07f04f 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -469,7 +469,7 @@ fn pixel_test_strokes(ui: &mut Ui) { let thickness_points = thickness_pixels / pixels_per_point; let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32; let size_pixels = vec2(ui.min_size().x, num_squares as f32 + thickness_pixels * 2.0); - let size_points = size_pixels / pixels_per_point + Vec2::splat(2.0); + let size_points = size_pixels / pixels_per_point; let (response, painter) = ui.allocate_painter(size_points, Sense::hover()); let mut cursor_pixel = Pos2::new( From 7055141c18c11e858f63aab2c36fe7595a1929fb Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 9 Apr 2025 12:11:46 +0200 Subject: [PATCH 016/388] Update failing snapshot tests (#5894) For some reason the pipeline in https://github.com/emilk/egui/pull/5698 succeeded even though the snapshots should have been updated. This updates the snapshots. --- .../egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png | 4 ++-- .../egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png | 4 ++-- .../egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png | 4 ++-- .../egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png | 4 ++-- .../egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png | 4 ++-- .../egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) 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 1cef15b72..51b8d8540 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:5a1f0d0759458017127d93278b89278af20fdc57c7747652ac6554f24cc708f2 -size 552939 +oid sha256:9f6cf5b14056522d06f0cb1e56bafd7e5ab7a9033eb358748d43d748bb0ceef1 +size 553177 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 f1005193c..3e73d0abb 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:7948ced20e3f62b8356fc978ca7b12f8522b7c15716409cc661c0ebd2f12047f -size 770062 +oid sha256:fd3bd1f64995db34a14dbc860ae8b8e269073ed7b8f10d10ce8f99b613cfc999 +size 769357 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 9e1b69fee..4b9a5194e 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:334f52bfee27f9c467de739696fd7ce7c48ec9013e315dc4b2e61eee58f11287 -size 907997 +oid sha256:f12e6145f3a1c3fda6dede3daeb0e52ed2bffb35531d823133224a477798a14a +size 907800 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 bd093a19c..c5f324368 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:d31f018cdabf92966b5636d9aef7f11ef1a0383884867e819e7ec99c0474e872 -size 1025013 +oid sha256:05bdcfd2c34b6d7badede14f5495dce34e5e9cfe421314f40dcea15e9f865736 +size 1024735 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 b9a40fab3..8e4481d06 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:e18f7ddadd53e16d04f191268747f244c98d0ea0f4cb9c0ea299f5da7affbc58 -size 1139723 +oid sha256:8365c89f6b823f01464a9310bab7717bf25305b335cdeecf21711c7dca9f053f +size 1140082 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 935dcc33d..de8c8b321 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:09a49cb9da7269bec6eef30c46a0bc85df6538d6e31dc0d0ff2758dbee45f3d8 -size 1291804 +oid sha256:b38021057ec6b5bb39c41bd4afaf5e9ff38687216d52d5bba8cbf7b6fdfe9a4f +size 1291518 From a8e0c56a8f38b8c26264733bc659977ba4178338 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 13 Apr 2025 19:12:22 +0200 Subject: [PATCH 017/388] Update crossbeam-channel to resolve RUSTSEC --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4276896ba..e9df7a2ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1004,9 +1004,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] From 0f1d6c281894647b2fdd1fcb311eb930ab1a269c Mon Sep 17 00:00:00 2001 From: Christopher Cerne Date: Mon, 14 Apr 2025 05:13:17 -0400 Subject: [PATCH 018/388] Support SVG Text Rendering in egui_extras (#5979) **Added** * Create `svg_text` feature flag to support text rendering & loading of system fonts. **Changed** * Updates `resvg` to `0.45`. * Adds `usvg::Options` field to the `SvgLoader` structure. * Change function signatures to support passing `usvg::Options` to downstream `load_svg_bytes_with_size`. **Additional Info** * I used this PR as a reference: https://github.com/emilk/egui/pull/4659. @xNWP can you see if this adequately resolves your concern from your original PR? * Closes https://github.com/emilk/egui/issues/5977 (we may want to open another issue for my other thoughts in this issue) * Also, I would like to thank @xNWP and their original PR for being a good reference for this one. * [x] I have followed the instructions in the PR template --- Cargo.lock | 177 ++++++++++++++----- crates/egui_extras/Cargo.toml | 5 +- crates/egui_extras/src/image.rs | 81 +++++---- crates/egui_extras/src/loaders/svg_loader.rs | 26 ++- 4 files changed, 205 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9df7a2ee..6d1d01b1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,6 +959,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -1662,6 +1671,28 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "fontconfig-parser" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2261,9 +2292,9 @@ dependencies = [ [[package]] name = "imagesize" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "immutable-chunkmap" @@ -2394,11 +2425,12 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.9.5" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" dependencies = [ "arrayvec", + "smallvec", ] [[package]] @@ -2423,6 +2455,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libredox" version = "0.1.3" @@ -3366,12 +3404,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rctree" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" - [[package]] name = "redox_syscall" version = "0.4.1" @@ -3438,9 +3470,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "resvg" -version = "0.37.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +checksum = "dd43d1c474e9dadf09a8fdf22d713ba668b499b5117b9b9079500224e26b5b29" dependencies = [ "log", "pico-args", @@ -3511,9 +3543,9 @@ dependencies = [ [[package]] name = "roxmltree" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rustc-demangle" @@ -3578,6 +3610,24 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.18" @@ -3731,9 +3781,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -3864,9 +3914,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svgtypes" -version = "0.13.0" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ "kurbo", "siphasher", @@ -4107,6 +4157,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "0.6.8" @@ -4160,6 +4225,9 @@ name = "ttf-parser" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" +dependencies = [ + "core_maths", +] [[package]] name = "type-map" @@ -4187,18 +4255,54 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.1.14" @@ -4267,46 +4371,29 @@ dependencies = [ [[package]] name = "usvg" -version = "0.37.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" -dependencies = [ - "base64 0.21.7", - "log", - "pico-args", - "usvg-parser", - "usvg-tree", - "xmlwriter", -] - -[[package]] -name = "usvg-parser" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +checksum = "2ac8e0e3e4696253dc06167990b3fe9a2668ab66270adf949a464db4088cb354" dependencies = [ + "base64 0.22.1", "data-url", "flate2", + "fontdb", "imagesize", "kurbo", "log", + "pico-args", "roxmltree", + "rustybuzz", "simplecss", "siphasher", - "svgtypes", - "usvg-tree", -] - -[[package]] -name = "usvg-tree" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" -dependencies = [ - "rctree", "strict-num", "svgtypes", "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", ] [[package]] diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index 7099d95d8..2e7b1bd2f 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -62,6 +62,9 @@ serde = ["egui/serde", "enum-map/serde", "dep:serde"] ## Support loading svg images. svg = ["resvg"] +## Support rendering text in svg images. +svg_text = ["svg", "resvg/text", "resvg/system-fonts"] + ## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect). syntect = ["dep:syntect"] @@ -101,7 +104,7 @@ syntect = { version = "5", optional = true, default-features = false, features = ] } # svg feature -resvg = { version = "0.37", optional = true, default-features = false } +resvg = { version = "0.45", optional = true, default-features = false } # http feature ehttp = { version = "0.5", optional = true, default-features = false } diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index 46d9df170..d7aa3126b 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -63,8 +63,12 @@ impl RetainedImage { /// # Errors /// On invalid image #[cfg(feature = "svg")] - pub fn from_svg_bytes(debug_name: impl Into, svg_bytes: &[u8]) -> Result { - Self::from_svg_bytes_with_size(debug_name, svg_bytes, None) + pub fn from_svg_bytes( + debug_name: impl Into, + svg_bytes: &[u8], + options: &resvg::usvg::Options<'_>, + ) -> Result { + Self::from_svg_bytes_with_size(debug_name, svg_bytes, None, options) } /// Pass in the str of an SVG that you've loaded. @@ -72,8 +76,12 @@ impl RetainedImage { /// # Errors /// On invalid image #[cfg(feature = "svg")] - pub fn from_svg_str(debug_name: impl Into, svg_str: &str) -> Result { - Self::from_svg_bytes(debug_name, svg_str.as_bytes()) + pub fn from_svg_str( + debug_name: impl Into, + svg_str: &str, + options: &resvg::usvg::Options<'_>, + ) -> Result { + Self::from_svg_bytes(debug_name, svg_str.as_bytes(), options) } /// Pass in the bytes of an SVG that you've loaded @@ -86,10 +94,11 @@ impl RetainedImage { debug_name: impl Into, svg_bytes: &[u8], size_hint: Option, + options: &resvg::usvg::Options<'_>, ) -> Result { Ok(Self::from_color_image( debug_name, - load_svg_bytes_with_size(svg_bytes, size_hint)?, + load_svg_bytes_with_size(svg_bytes, size_hint, options)?, )) } @@ -227,8 +236,11 @@ pub fn load_image_bytes(image_bytes: &[u8]) -> Result Result { - load_svg_bytes_with_size(svg_bytes, None) +pub fn load_svg_bytes( + svg_bytes: &[u8], + options: &resvg::usvg::Options<'_>, +) -> Result { + load_svg_bytes_with_size(svg_bytes, None, options) } /// Load an SVG and rasterize it into an egui image with a scaling parameter. @@ -241,48 +253,47 @@ pub fn load_svg_bytes(svg_bytes: &[u8]) -> Result { pub fn load_svg_bytes_with_size( svg_bytes: &[u8], size_hint: Option, + options: &resvg::usvg::Options<'_>, ) -> Result { use resvg::tiny_skia::{IntSize, Pixmap}; - use resvg::usvg::{Options, Tree, TreeParsing}; + use resvg::usvg::{Transform, Tree}; profiling::function_scope!(); - let opt = Options::default(); + let rtree = Tree::from_data(svg_bytes, options).map_err(|err| err.to_string())?; - let mut rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?; - - let mut size = rtree.size.to_int_size(); - match size_hint { - None => (), - Some(SizeHint::Size(w, h)) => { - size = size.scale_to( - IntSize::from_wh(w, h).ok_or_else(|| format!("Failed to scale SVG to {w}x{h}"))?, - ); - } - Some(SizeHint::Height(h)) => { - size = size - .scale_to_height(h) - .ok_or_else(|| format!("Failed to scale SVG to height {h}"))?; - } - Some(SizeHint::Width(w)) => { - size = size - .scale_to_width(w) - .ok_or_else(|| format!("Failed to scale SVG to width {w}"))?; - } + let size = rtree.size().to_int_size(); + let scaled_size = match size_hint { + None => size, + Some(SizeHint::Size(w, h)) => size.scale_to( + IntSize::from_wh(w, h).ok_or_else(|| format!("Failed to scale SVG to {w}x{h}"))?, + ), + Some(SizeHint::Height(h)) => size + .scale_to_height(h) + .ok_or_else(|| format!("Failed to scale SVG to height {h}"))?, + Some(SizeHint::Width(w)) => size + .scale_to_width(w) + .ok_or_else(|| format!("Failed to scale SVG to width {w}"))?, Some(SizeHint::Scale(z)) => { let z_inner = z.into_inner(); - size = size - .scale_by(z_inner) - .ok_or_else(|| format!("Failed to scale SVG by {z_inner}"))?; + size.scale_by(z_inner) + .ok_or_else(|| format!("Failed to scale SVG by {z_inner}"))? } }; - let (w, h) = (size.width(), size.height()); + + let (w, h) = (scaled_size.width(), scaled_size.height()); let mut pixmap = Pixmap::new(w, h).ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?; - rtree.size = size.to_size(); - resvg::Tree::from_usvg(&rtree).render(Default::default(), &mut pixmap.as_mut()); + resvg::render( + &rtree, + Transform::from_scale( + w as f32 / size.width() as f32, + h as f32 / size.height() as f32, + ), + &mut pixmap.as_mut(), + ); let image = egui::ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data()); diff --git a/crates/egui_extras/src/loaders/svg_loader.rs b/crates/egui_extras/src/loaders/svg_loader.rs index 2a3f15569..4c33281fe 100644 --- a/crates/egui_extras/src/loaders/svg_loader.rs +++ b/crates/egui_extras/src/loaders/svg_loader.rs @@ -10,9 +10,9 @@ use egui::{ type Entry = Result, String>; -#[derive(Default)] pub struct SvgLoader { cache: Mutex, SizeHint), Entry>>, + options: resvg::usvg::Options<'static>, } impl SvgLoader { @@ -27,6 +27,22 @@ fn is_supported(uri: &str) -> bool { ext == "svg" } +impl Default for SvgLoader { + fn default() -> Self { + // opt is mutated when `svg_text` feature flag is enabled + #[allow(unused_mut)] + let mut options = resvg::usvg::Options::default(); + + #[cfg(feature = "svg_text")] + options.fontdb_mut().load_system_fonts(); + + Self { + cache: Mutex::new(HashMap::default()), + options, + } + } +} + impl ImageLoader for SvgLoader { fn id(&self) -> &str { Self::ID @@ -48,8 +64,12 @@ impl ImageLoader for SvgLoader { match ctx.try_load_bytes(uri) { Ok(BytesPoll::Ready { bytes, .. }) => { log::trace!("started loading {uri:?}"); - let result = crate::image::load_svg_bytes_with_size(&bytes, Some(size_hint)) - .map(Arc::new); + let result = crate::image::load_svg_bytes_with_size( + &bytes, + Some(size_hint), + &self.options, + ) + .map(Arc::new); log::trace!("finished loading {uri:?}"); cache.insert((Cow::Owned(uri.to_owned()), size_hint), result.clone()); match result { From 501905b60df88ff84fe6ea9a0d51543b6cc4638f Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 16 Apr 2025 18:58:58 +0200 Subject: [PATCH 019/388] Add tests for layout and visuals of most egui widgets (#6752) This is mostly in preparation for #5830 where I want to ensure that I don't introduce any regressions --- Cargo.lock | 10 + crates/egui_kittest/src/lib.rs | 64 ++- tests/egui_tests/Cargo.toml | 15 + .../tests/snapshots/layout/button.png | 3 + .../tests/snapshots/layout/button_image.png | 3 + .../layout/button_image_shortcut.png | 3 + .../tests/snapshots/layout/checkbox.png | 3 + .../snapshots/layout/checkbox_checked.png | 3 + .../tests/snapshots/layout/drag_value.png | 3 + .../tests/snapshots/layout/radio.png | 3 + .../tests/snapshots/layout/radio_checked.png | 3 + .../snapshots/layout/selectable_value.png | 3 + .../layout/selectable_value_selected.png | 3 + .../tests/snapshots/layout/slider.png | 3 + .../tests/snapshots/layout/text_edit.png | 3 + .../tests/snapshots/visuals/button.png | 3 + .../tests/snapshots/visuals/button_image.png | 3 + .../visuals/button_image_shortcut.png | 3 + .../button_image_shortcut_selected.png | 3 + .../tests/snapshots/visuals/checkbox.png | 3 + .../snapshots/visuals/checkbox_checked.png | 3 + .../tests/snapshots/visuals/drag_value.png | 3 + .../tests/snapshots/visuals/radio.png | 3 + .../tests/snapshots/visuals/radio_checked.png | 3 + .../snapshots/visuals/selectable_value.png | 3 + .../visuals/selectable_value_selected.png | 3 + .../tests/snapshots/visuals/slider.png | 3 + .../tests/snapshots/visuals/text_edit.png | 3 + tests/egui_tests/tests/test_widgets.rs | 370 ++++++++++++++++++ 29 files changed, 518 insertions(+), 16 deletions(-) create mode 100644 tests/egui_tests/Cargo.toml create mode 100644 tests/egui_tests/tests/snapshots/layout/button.png create mode 100644 tests/egui_tests/tests/snapshots/layout/button_image.png create mode 100644 tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png create mode 100644 tests/egui_tests/tests/snapshots/layout/checkbox.png create mode 100644 tests/egui_tests/tests/snapshots/layout/checkbox_checked.png create mode 100644 tests/egui_tests/tests/snapshots/layout/drag_value.png create mode 100644 tests/egui_tests/tests/snapshots/layout/radio.png create mode 100644 tests/egui_tests/tests/snapshots/layout/radio_checked.png create mode 100644 tests/egui_tests/tests/snapshots/layout/selectable_value.png create mode 100644 tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png create mode 100644 tests/egui_tests/tests/snapshots/layout/slider.png create mode 100644 tests/egui_tests/tests/snapshots/layout/text_edit.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/button.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/button_image.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/checkbox.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/drag_value.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/radio.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/radio_checked.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/selectable_value.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/slider.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/text_edit.png create mode 100644 tests/egui_tests/tests/test_widgets.rs diff --git a/Cargo.lock b/Cargo.lock index 6d1d01b1d..c2d246045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1423,6 +1423,16 @@ dependencies = [ "wgpu", ] +[[package]] +name = "egui_tests" +version = "0.31.1" +dependencies = [ + "egui", + "egui_extras", + "egui_kittest", + "image", +] + [[package]] name = "ehttp" version = "0.5.0" diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 59bd5c056..38521d101 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -274,6 +274,7 @@ impl<'a, State> Harness<'a, State> { /// /// See also: /// - [`Harness::try_run`]. + /// - [`Harness::try_run_realtime`]. /// - [`Harness::run_ok`]. /// - [`Harness::step`]. /// - [`Harness::run_steps`]. @@ -287,6 +288,27 @@ impl<'a, State> Harness<'a, State> { } } + fn _try_run(&mut self, sleep: bool) -> Result { + let mut steps = 0; + loop { + steps += 1; + self.step(); + // We only care about immediate repaints + if self.root_viewport_output().repaint_delay != Duration::ZERO { + break; + } else if sleep { + std::thread::sleep(Duration::from_secs_f32(self.step_dt)); + } + if steps > self.max_steps { + return Err(ExceededMaxStepsError { + max_steps: self.max_steps, + repaint_causes: self.ctx.repaint_causes(), + }); + } + } + Ok(steps) + } + /// Run until /// - all animations are done /// - no more repaints are requested @@ -302,23 +324,9 @@ impl<'a, State> Harness<'a, State> { /// - [`Harness::run_ok`]. /// - [`Harness::step`]. /// - [`Harness::run_steps`]. + /// - [`Harness::try_run_realtime`]. pub fn try_run(&mut self) -> Result { - let mut steps = 0; - loop { - steps += 1; - self.step(); - // We only care about immediate repaints - if self.root_viewport_output().repaint_delay != Duration::ZERO { - break; - } - if steps > self.max_steps { - return Err(ExceededMaxStepsError { - max_steps: self.max_steps, - repaint_causes: self.ctx.repaint_causes(), - }); - } - } - Ok(steps) + self._try_run(false) } /// Run until @@ -333,10 +341,34 @@ impl<'a, State> Harness<'a, State> { /// - [`Harness::try_run`]. /// - [`Harness::step`]. /// - [`Harness::run_steps`]. + /// - [`Harness::try_run_realtime`]. pub fn run_ok(&mut self) -> Option { self.try_run().ok() } + /// Run multiple frames, sleeping for [`HarnessBuilder::with_step_dt`] between frames. + /// + /// This is useful to e.g. wait for an async operation to complete (e.g. loading of images). + /// Runs until + /// - all animations are done + /// - no more repaints are requested + /// - the maximum number of steps is reached (See [`HarnessBuilder::with_max_steps`]) + /// + /// Returns the number of steps that were run. + /// + /// # Errors + /// Returns an error if the maximum number of steps is exceeded. + /// + /// See also: + /// - [`Harness::run`]. + /// - [`Harness::run_ok`]. + /// - [`Harness::step`]. + /// - [`Harness::run_steps`]. + /// - [`Harness::try_run`]. + pub fn try_run_realtime(&mut self) -> Result { + self._try_run(true) + } + /// Run a number of steps. /// Equivalent to calling [`Harness::step`] x times. pub fn run_steps(&mut self, steps: usize) { diff --git a/tests/egui_tests/Cargo.toml b/tests/egui_tests/Cargo.toml new file mode 100644 index 000000000..ee2531831 --- /dev/null +++ b/tests/egui_tests/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "egui_tests" +edition.workspace = true +license.workspace = true +rust-version.workspace = true +version.workspace = true + +[dev-dependencies] +egui = { workspace = true, default-features = true } +egui_kittest = { workspace = true, features = ["snapshot", "wgpu"] } +egui_extras = { workspace = true, features = ["image"]} +image = { workspace = true, features = ["png"] } + +[lints] +workspace = true diff --git a/tests/egui_tests/tests/snapshots/layout/button.png b/tests/egui_tests/tests/snapshots/layout/button.png new file mode 100644 index 000000000..7232c72c8 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccd7bdd86e587bcf0577c92e10ed7c3c35195e37df109a84554ceb30a434768d +size 315482 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png new file mode 100644 index 000000000..737f0670c --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/button_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:975c279d6da2a2cb000df72bf5d9f3bdd200bb20adc00e29e8fd9ed4d2c6f6b1 +size 340923 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png new file mode 100644 index 000000000..7dbda11d9 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad14068e60fa678ee749925dd3713ee2b12a83ec1bca9c413bdeb9bc27d8ac20 +size 407795 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox.png b/tests/egui_tests/tests/snapshots/layout/checkbox.png new file mode 100644 index 000000000..b8c014727 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/checkbox.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:996e02c1c10a0c76fa295160d117aceb764ef506608b151bafbdf263106dbe57 +size 385129 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png new file mode 100644 index 000000000..66ae8115f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaf9b032037d0708894e568cc8e256b32be9cfb586eaffdc6167143b85562b37 +size 415016 diff --git a/tests/egui_tests/tests/snapshots/layout/drag_value.png b/tests/egui_tests/tests/snapshots/layout/drag_value.png new file mode 100644 index 000000000..a9a64c558 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/drag_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:043be3ece0697ea7114b7bd743e5c958610ae38ac359b6f8120886edff8541d8 +size 239522 diff --git a/tests/egui_tests/tests/snapshots/layout/radio.png b/tests/egui_tests/tests/snapshots/layout/radio.png new file mode 100644 index 000000000..1e1a1faf3 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/radio.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f7fbeeba8ae9e34c5400727690ac7941e2711f72f2dc23e3342cb06904e4a35 +size 335775 diff --git a/tests/egui_tests/tests/snapshots/layout/radio_checked.png b/tests/egui_tests/tests/snapshots/layout/radio_checked.png new file mode 100644 index 000000000..323426ee9 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/radio_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96ae7be40161b0b42959b44c8f72b62fd2cd4b3b463fc7d5bcd02ead445edca1 +size 355550 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value.png b/tests/egui_tests/tests/snapshots/layout/selectable_value.png new file mode 100644 index 000000000..42794b19e --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4991fdf58542ca14162cbd7f59b6a30d6c3d752a1215cc1890359bc3a1eb23c9 +size 388912 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png new file mode 100644 index 000000000..554bbbf41 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac8cbcdeed098d52009be77c8815931553d979f5aaf0baf0a9296daf6373605 +size 402699 diff --git a/tests/egui_tests/tests/snapshots/layout/slider.png b/tests/egui_tests/tests/snapshots/layout/slider.png new file mode 100644 index 000000000..9017347f2 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/slider.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:605091767a73a934981d10d0ed59ff561772ed61e7691303b75b35ae01163ecc +size 336722 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit.png b/tests/egui_tests/tests/snapshots/layout/text_edit.png new file mode 100644 index 000000000..fae07202c --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/text_edit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:465e34d94bf734a2a7a1e8e4a71ce64c908c737a7c4fa2a6f812351f2aaa6808 +size 233018 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png new file mode 100644 index 000000000..8c8e9630c --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99f64e581b97df6694cb7c85ee7728a955e3c1a851ab660e8b6091eee1885bbe +size 9719 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png new file mode 100644 index 000000000..c71c2aeb0 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d39ec25b91f5f5d68305d2cb7cc0285d715fe30ccbd66369efbe7327d1899b52 +size 10753 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png new file mode 100644 index 000000000..42f8ff02a --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46d86987ba895ead9b28efcc37e1b4374f34eedebac83d1db9eaa8e5a3202ee3 +size 13203 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 new file mode 100644 index 000000000..3ff34c6be --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09c5904877c8895d3ad41b7082019ef87db40c6a91ad47401bb9b8ac79a62bdc +size 12914 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png new file mode 100644 index 000000000..a0e6e18d7 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cd5e9ad416c3a0b6824debc343f196e6db90509fd201c60c7c1f9b022f37c1d +size 12322 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png new file mode 100644 index 000000000..40852f3c2 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cf99a3d28f73d4a72c0e616dc54198663b94bf5cffda694cf4eb4dee01be8 +size 13445 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png new file mode 100644 index 000000000..dbe3c13b6 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/drag_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e86a37c7b259a6bad61897545d927d75e8307916dc78d256e4d33c410fcd6876 +size 7306 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png new file mode 100644 index 000000000..9c14f3032 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:583fa78f79b39522a44c871642114ead9ed1d177bb8a3807d2c9e2cd89bf0b44 +size 11076 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png new file mode 100644 index 000000000..a42ad5012 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1a172cfadc91467529e5546e686673be73ba0071a55d55abc7a41fb1d07214d +size 11700 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png new file mode 100644 index 000000000..c2cbd7334 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eed80e11dd3ba478217cf004654934214b522ea666074e023dda9a323473615a +size 12452 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png new file mode 100644 index 000000000..81f995515 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ef91dfedc74cae59099bce32b2e42cb04649e84442e8010282a9c1ff2a7f2c8 +size 12469 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png new file mode 100644 index 000000000..6c8348559 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/slider.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1892358a4552af3f529141d314cd18e4cf55a629d870798278a5470e3e0a8a94 +size 11030 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png new file mode 100644 index 000000000..5f2a64b8d --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7300a0b88d4fdb6c1e543bfaf50e8964b2f84aaaf8197267b671d0cf3c8da30a +size 7033 diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs new file mode 100644 index 000000000..264b0052f --- /dev/null +++ b/tests/egui_tests/tests/test_widgets.rs @@ -0,0 +1,370 @@ +use egui::load::SizedTexture; +use egui::{ + include_image, Align, Button, Color32, ColorImage, Direction, DragValue, Event, Grid, Layout, + PointerButton, Pos2, Response, Slider, Stroke, StrokeKind, TextWrapMode, TextureHandle, + TextureOptions, Ui, UiBuilder, Vec2, Widget, +}; +use egui_kittest::kittest::{by, Node, Queryable}; +use egui_kittest::{Harness, SnapshotResult, SnapshotResults}; + +#[test] +fn widget_tests() { + let mut results = SnapshotResults::new(); + + test_widget("button", |ui| ui.button("Button"), &mut results); + test_widget( + "button_image", + |ui| { + Button::image_and_text( + include_image!("../../../crates/eframe/data/icon.png"), + "Button", + ) + .ui(ui) + }, + &mut results, + ); + test_widget( + "button_image_shortcut", + |ui| { + Button::image_and_text( + include_image!("../../../crates/eframe/data/icon.png"), + "Open", + ) + .shortcut_text("⌘O") + .ui(ui) + }, + &mut results, + ); + results.add(VisualTests::test("button_image_shortcut_selected", |ui| { + Button::image_and_text( + include_image!("../../../crates/eframe/data/icon.png"), + "Open", + ) + .shortcut_text("⌘O") + .selected(true) + .ui(ui) + })); + + test_widget( + "selectable_value", + |ui| ui.selectable_label(false, "Selectable"), + &mut results, + ); + test_widget( + "selectable_value_selected", + |ui| ui.selectable_label(true, "Selectable"), + &mut results, + ); + + test_widget( + "checkbox", + |ui| ui.checkbox(&mut false, "Checkbox"), + &mut results, + ); + test_widget( + "checkbox_checked", + |ui| ui.checkbox(&mut true, "Checkbox"), + &mut results, + ); + test_widget("radio", |ui| ui.radio(false, "Radio"), &mut results); + test_widget("radio_checked", |ui| ui.radio(true, "Radio"), &mut results); + + test_widget( + "drag_value", + |ui| DragValue::new(&mut 12.0).ui(ui), + &mut results, + ); + + test_widget( + "text_edit", + |ui| { + ui.spacing_mut().text_edit_width = 45.0; + ui.text_edit_singleline(&mut "Hi!".to_owned()) + }, + &mut results, + ); + + test_widget( + "slider", + |ui| { + ui.spacing_mut().slider_width = 45.0; + Slider::new(&mut 12.0, 0.0..=100.0).ui(ui) + }, + &mut results, + ); +} + +fn test_widget(name: &str, mut w: impl FnMut(&mut Ui) -> Response, results: &mut SnapshotResults) { + results.add(test_widget_layout(name, &mut w)); + results.add(VisualTests::test(name, &mut w)); +} + +fn test_widget_layout(name: &str, mut w: impl FnMut(&mut Ui) -> Response) -> SnapshotResult { + let test_size = Vec2::new(110.0, 45.0); + + struct Row { + main_dir: Direction, + main_align: Align, + main_justify: bool, + } + + struct Col { + cross_align: Align, + cross_justify: bool, + } + + let mut rows = Vec::new(); + let mut cols = Vec::new(); + + for main_justify in [false, true] { + for main_dir in [ + Direction::LeftToRight, + Direction::TopDown, + Direction::RightToLeft, + Direction::BottomUp, + ] { + for main_align in [Align::Min, Align::Center, Align::Max] { + rows.push(Row { + main_dir, + main_align, + main_justify, + }); + } + } + } + + for cross_justify in [false, true] { + for cross_align in [Align::Min, Align::Center, Align::Max] { + cols.push(Col { + cross_align, + cross_justify, + }); + } + } + + let mut harness = Harness::builder().build_ui(|ui| { + egui_extras::install_image_loaders(ui.ctx()); + + { + let mut wrap_test_size = test_size; + wrap_test_size.x /= 3.0; + ui.heading("Wrapping"); + + let modes = [ + TextWrapMode::Extend, + TextWrapMode::Truncate, + TextWrapMode::Wrap, + ]; + Grid::new("wrapping") + .spacing(Vec2::new(test_size.x / 2.0, 4.0)) + .show(ui, |ui| { + for mode in &modes { + ui.label(format!("{mode:?}")); + } + ui.end_row(); + + for mode in &modes { + let (_, rect) = ui.allocate_space(wrap_test_size); + + let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect)); + child_ui.style_mut().wrap_mode = Some(*mode); + w(&mut child_ui); + + ui.painter().rect_stroke( + rect, + 0.0, + Stroke::new(1.0, Color32::WHITE), + StrokeKind::Outside, + ); + } + }); + } + + ui.heading("Layout"); + Grid::new("layout").striped(true).show(ui, |ui| { + ui.label(""); + for col in &cols { + ui.label(format!( + "cross_align: {:?}\ncross_justify:{:?}", + col.cross_align, col.cross_justify + )); + } + ui.end_row(); + + for row in &rows { + ui.label(format!( + "main_dir: {:?}\nmain_align: {:?}\nmain_justify: {:?}", + row.main_dir, row.main_align, row.main_justify + )); + for col in &cols { + let layout = Layout { + main_dir: row.main_dir, + main_align: row.main_align, + main_justify: row.main_justify, + cross_align: col.cross_align, + cross_justify: col.cross_justify, + main_wrap: false, + }; + + let (_, rect) = ui.allocate_space(test_size); + + let mut child_ui = ui.new_child(UiBuilder::new().layout(layout).max_rect(rect)); + w(&mut child_ui); + + ui.painter().rect_stroke( + rect, + 0.0, + Stroke::new(1.0, Color32::WHITE), + StrokeKind::Outside, + ); + } + + ui.end_row(); + } + }); + }); + + harness.fit_contents(); + harness.try_snapshot(&format!("layout/{name}")) +} + +/// Utility to create a snapshot test of the different states of a egui widget. +/// This renders each state to a texture to work around the fact only a single widget can be +/// hovered / pressed / focused at a time. +struct VisualTests<'a> { + name: String, + w: &'a mut dyn FnMut(&mut Ui) -> Response, + results: Vec<(String, ColorImage)>, +} + +impl<'a> VisualTests<'a> { + pub fn test(name: &str, mut w: impl FnMut(&mut Ui) -> Response) -> SnapshotResult { + let mut vis = VisualTests::new(name, &mut w); + vis.add_default_states(); + vis.render() + } + + pub fn new(name: &str, w: &'a mut dyn FnMut(&mut Ui) -> Response) -> Self { + Self { + name: name.to_owned(), + w, + results: Vec::new(), + } + } + + fn add_default_states(&mut self) { + self.add("idle", |_| {}); + self.add_node("hover", |node| { + node.hover(); + }); + self.add("pressed", |harness| { + harness.get_next().hover(); + let rect = harness.get_next().bounding_box().unwrap(); + let pos = Pos2::new( + ((rect.x0 + rect.x1) / 2.0) as f32, + ((rect.y0 + rect.y1) / 2.0) as f32, + ); + harness.input_mut().events.push(Event::PointerButton { + button: PointerButton::Primary, + pos, + pressed: true, + modifiers: Default::default(), + }); + }); + self.add_node("focussed", |node| { + node.focus(); + }); + self.add_disabled(); + } + + fn single_test(&mut self, f: impl FnOnce(&mut Harness<'_>), enabled: bool) -> ColorImage { + let mut harness = Harness::builder().with_step_dt(0.05).build_ui(|ui| { + egui_extras::install_image_loaders(ui.ctx()); + ui.add_enabled_ui(enabled, |ui| { + (self.w)(ui); + }); + }); + + harness.fit_contents(); + + // Wait for images to load + harness.try_run_realtime().ok(); + + f(&mut harness); + + harness.step(); + + let image = harness.render().expect("Failed to render harness"); + + ColorImage::from_rgba_unmultiplied( + [image.width() as usize, image.height() as usize], + image.as_ref(), + ) + } + + pub fn add(&mut self, name: &str, test: impl FnOnce(&mut Harness<'_>)) { + let image = self.single_test(test, true); + self.results.push((name.to_owned(), image)); + } + + pub fn add_disabled(&mut self) { + let image = self.single_test(|_| {}, false); + self.results.push(("disabled".to_owned(), image)); + } + + pub fn add_node(&mut self, name: &str, test: impl FnOnce(&Node<'_>)) { + self.add(name, |harness| { + let node = harness.get_next(); + test(&node); + }); + } + + pub fn render(self) -> SnapshotResult { + let mut results = Some(self.results); + let mut images: Option> = None; + + let mut harness = Harness::new_ui(|ui| { + let results = images.get_or_insert_with(|| { + results + .take() + .unwrap() + .into_iter() + .map(|(name, image)| { + let size = Vec2::new(image.width() as f32, image.height() as f32); + let texture_handle = + ui.ctx() + .load_texture(name.clone(), image, TextureOptions::default()); + let texture = SizedTexture::new(texture_handle.id(), size); + (name.clone(), texture_handle, texture) + }) + .collect() + }); + + Grid::new("results").show(ui, |ui| { + for (name, _, image) in results { + ui.label(&*name); + + ui.scope(|ui| { + ui.image(*image); + }); + + ui.end_row(); + } + }); + }); + + harness.fit_contents(); + + harness.try_snapshot(&format!("visuals/{}", self.name)) + } +} + +trait HarnessExt { + fn get_next(&self) -> Node<'_>; +} + +impl HarnessExt for Harness<'_> { + fn get_next(&self) -> Node<'_> { + self.get_all(by()).next().unwrap() + } +} From 009bfe5adadb4a0edea623eb4b58feadd403ffa4 Mon Sep 17 00:00:00 2001 From: Marek Bernat Date: Tue, 22 Apr 2025 11:28:34 +0200 Subject: [PATCH 020/388] Add a `Slider::update_while_editing(bool)` API (#5978) * Closes #5976 * [x] I have followed the instructions in the PR template - The `scripts/check.sh` fails in `cargo-deny` because of a security vulnerability in `crossbeam-channel` but this is also present on `master`. https://github.com/user-attachments/assets/a964c968-bb76-4e56-88e1-d1e3d51a401a --- crates/egui/src/widgets/slider.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index e8b026ff5..0f7eb0495 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -117,6 +117,7 @@ pub struct Slider<'a> { custom_parser: Option>, trailing_fill: Option, handle_shape: Option, + update_while_editing: bool, } impl<'a> Slider<'a> { @@ -167,6 +168,7 @@ impl<'a> Slider<'a> { custom_parser: None, trailing_fill: None, handle_shape: None, + update_while_editing: true, } } @@ -641,6 +643,16 @@ impl<'a> Slider<'a> { let normalized = normalized_from_value(value, self.range(), &self.spec); lerp(position_range, normalized as f32) } + + /// Update the value on each key press when text-editing the value. + /// + /// Default: `true`. + /// If `false`, the value will only be updated when user presses enter or deselects the value. + #[inline] + pub fn update_while_editing(mut self, update: bool) -> Self { + self.update_while_editing = update; + self + } } impl Slider<'_> { @@ -900,7 +912,8 @@ impl Slider<'_> { .min_decimals(self.min_decimals) .max_decimals_opt(self.max_decimals) .suffix(self.suffix.clone()) - .prefix(self.prefix.clone()); + .prefix(self.prefix.clone()) + .update_while_editing(self.update_while_editing); match self.clamping { SliderClamping::Never => {} From 114460c1de6a7d879b5a2e6c3dd9ea401f3a7b17 Mon Sep 17 00:00:00 2001 From: mkalte Date: Tue, 22 Apr 2025 11:38:17 +0200 Subject: [PATCH 021/388] Change popup memory to be per-viewport (#6753) Starting with 77244cd4c5ebb924beaec8f89604a2881106b27c the popup open-state is cleaned up per memory pass. This becomes problematic for implementations that share memory between viewports (i.e. all of them, as far as i understand it), because each viewport gets a context pass, and thus a memory pass, which cleans out popup open state. To illustrate my issue, i have modifed the multiple viewport example to include a popup menu for the labels: https://gist.github.com/mkalte666/4ecd6b658003df4c6d532ae2060c7595 (changes not included in this pr). Then, when i try to use the popups, i get this: https://github.com/user-attachments/assets/7d04b872-5396-4823-bf30-824122925528 Immediate viewports just break popup handling in general, while deferred viewports kinda work, or dont. In this example ill be honest, it kind of still did, sometimes. In my more complex app with multiple viewports (where i encountered this first) it also just broke - even when just showing root and one nother. Probably to do with the order wgpu vs glow draws the viewports? Im not sure. In any case: This commit adds `Memory::popup` (now `Memory::popups`) to the per-viewport state of memory, including viewport aware cleanup, and adding it to the list of things cleaned if a viewport goes away. This results in the expected behavior: https://github.com/user-attachments/assets/fd1f74b7-d3b2-4edc-8dc4-2ad3cfa2160e I will note that with this, changing focus does not cause a popup to be closed, which is consistent with current behavior on a single app. Hope this helps ~Malte * Closes * [x] I have followed the instructions in the PR template * [x] ~~I have run check.sh locally~~ CI on the fork, including checks, went through. --- crates/egui/src/memory/mod.rs | 47 ++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 0f588ca53..b8cd782c1 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -87,15 +87,6 @@ pub struct Memory { #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) viewport_id: ViewportId, - /// Which popup-window is open (if any)? - /// Could be a combo box, color picker, menu, etc. - /// Optionally stores the position of the popup (usually this would be the position where - /// the user clicked). - /// If position is [`None`], the popup position will be calculated based on some configuration - /// (e.g. relative to some other widget). - #[cfg_attr(feature = "persistence", serde(skip))] - popup: Option, - #[cfg_attr(feature = "persistence", serde(skip))] everything_is_visible: bool, @@ -116,6 +107,15 @@ pub struct Memory { #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) focus: ViewportIdMap, + + /// Which popup-window is open on a viewport (if any)? + /// Could be a combo box, color picker, menu, etc. + /// Optionally stores the position of the popup (usually this would be the position where + /// the user clicked). + /// If position is [`None`], the popup position will be calculated based on some configuration + /// (e.g. relative to some other widget). + #[cfg_attr(feature = "persistence", serde(skip))] + popups: ViewportIdMap, } impl Default for Memory { @@ -130,7 +130,7 @@ impl Default for Memory { viewport_id: Default::default(), areas: Default::default(), to_global: Default::default(), - popup: Default::default(), + popups: Default::default(), everything_is_visible: Default::default(), add_fonts: Default::default(), }; @@ -790,6 +790,7 @@ impl Memory { // Cleanup self.interactions.retain(|id, _| viewports.contains(id)); self.areas.retain(|id, _| viewports.contains(id)); + self.popups.retain(|id, _| viewports.contains(id)); self.areas.entry(self.viewport_id).or_default(); @@ -809,11 +810,11 @@ impl Memory { self.focus_mut().end_pass(used_ids); // Clean up abandoned popups. - if let Some(popup) = &mut self.popup { + if let Some(popup) = self.popups.get_mut(&self.viewport_id) { if popup.open_this_frame { popup.open_this_frame = false; } else { - self.popup = None; + self.popups.remove(&self.viewport_id); } } } @@ -1107,19 +1108,23 @@ impl OpenPopup { impl Memory { /// Is the given popup open? pub fn is_popup_open(&self, popup_id: Id) -> bool { - self.popup.is_some_and(|state| state.id == popup_id) || self.everything_is_visible() + self.popups + .get(&self.viewport_id) + .is_some_and(|state| state.id == popup_id) + || self.everything_is_visible() } /// Is any popup open? pub fn any_popup_open(&self) -> bool { - self.popup.is_some() || self.everything_is_visible() + self.popups.contains_key(&self.viewport_id) || self.everything_is_visible() } /// 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. pub fn open_popup(&mut self, popup_id: Id) { - self.popup = Some(OpenPopup::new(popup_id, None)); + self.popups + .insert(self.viewport_id, OpenPopup::new(popup_id, None)); } /// Popups must call this every frame while open. @@ -1128,7 +1133,7 @@ impl Memory { /// called. For example, when a context menu is open and the underlying widget stops /// being rendered. pub fn keep_popup_open(&mut self, popup_id: Id) { - if let Some(state) = self.popup.as_mut() { + if let Some(state) = self.popups.get_mut(&self.viewport_id) { if state.id == popup_id { state.open_this_frame = true; } @@ -1137,18 +1142,20 @@ impl Memory { /// Open the popup and remember its position. pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into>) { - self.popup = Some(OpenPopup::new(popup_id, pos.into())); + self.popups + .insert(self.viewport_id, OpenPopup::new(popup_id, pos.into())); } /// Get the position for this popup. pub fn popup_position(&self, id: Id) -> Option { - self.popup + self.popups + .get(&self.viewport_id) .and_then(|state| if state.id == id { state.pos } else { None }) } /// Close any currently open popup. pub fn close_all_popups(&mut self) { - self.popup = None; + self.popups.clear(); } /// Close the given popup, if it is open. @@ -1156,7 +1163,7 @@ impl Memory { /// See also [`Self::close_all_popups`] if you want to close any / all currently open popups. pub fn close_popup(&mut self, popup_id: Id) { if self.is_popup_open(popup_id) { - self.popup = None; + self.popups.remove(&self.viewport_id); } } From cd318f0e55c43163aa26e4739042bb95b198d46d Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 22 Apr 2025 12:42:24 +0300 Subject: [PATCH 022/388] feat: Add `Scene::drag_pan_buttons` option. Allows specifying which pointer buttons pan the scene by dragging. (#5892) This adds an option for specifying the set of pointer buttons that can be used to pan the scene via clicking and dragging. The original behaviour where all buttons can pan the scene by default is maintained. Addresses part of #5891. --- Edit: It looks like the failing test is unrelated and also appears on master: https://github.com/emilk/egui/actions/runs/14330259861/job/40164414607. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/containers/mod.rs | 2 +- crates/egui/src/containers/scene.rs | 47 +++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index 3bd89e0db..31898838e 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -29,7 +29,7 @@ pub use { panel::{CentralPanel, SidePanel, TopBottomPanel}, popup::*, resize::Resize, - scene::Scene, + scene::{DragPanButtons, Scene}, scroll_area::ScrollArea, sides::Sides, tooltip::*, diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index 5b8d97410..d023a4d3b 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -3,7 +3,8 @@ use core::f32; use emath::{GuiRounding, Pos2}; use crate::{ - emath::TSTransform, InnerResponse, LayerId, Rangef, Rect, Response, Sense, Ui, UiBuilder, Vec2, + emath::TSTransform, InnerResponse, LayerId, PointerButton, Rangef, Rect, Response, Sense, Ui, + UiBuilder, Vec2, }; /// Creates a transformation that fits a given scene rectangle into the available screen size. @@ -45,6 +46,30 @@ fn fit_to_rect_in_scene( pub struct Scene { zoom_range: Rangef, max_inner_size: Vec2, + drag_pan_buttons: DragPanButtons, +} + +/// Specifies which pointer buttons can be used to pan the scene by dragging. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct DragPanButtons(u8); + +bitflags::bitflags! { + impl DragPanButtons: u8 { + /// [PointerButton::Primary] + const PRIMARY = 1 << 0; + + /// [PointerButton::Secondary] + const SECONDARY = 1 << 1; + + /// [PointerButton::Middle] + const MIDDLE = 1 << 2; + + /// [PointerButton::Extra1] + const EXTRA_1 = 1 << 3; + + /// [PointerButton::Extra2] + const EXTRA_2 = 1 << 4; + } } impl Default for Scene { @@ -52,6 +77,7 @@ impl Default for Scene { Self { zoom_range: Rangef::new(f32::EPSILON, 1.0), max_inner_size: Vec2::splat(1000.0), + drag_pan_buttons: DragPanButtons::all(), } } } @@ -82,6 +108,15 @@ impl Scene { self } + /// Specify which pointer buttons can be used to pan by clicking and dragging. + /// + /// By default, this is `DragPanButtons::all()`. + #[inline] + pub fn drag_pan_buttons(mut self, flags: DragPanButtons) -> Self { + self.drag_pan_buttons = flags; + self + } + /// `scene_rect` contains the view bounds of the inner [`Ui`]. /// /// `scene_rect` will be mutated by any panning/zooming done by the user. @@ -179,7 +214,15 @@ impl Scene { /// Helper function to handle pan and zoom interactions on a response. pub fn register_pan_and_zoom(&self, ui: &Ui, resp: &mut Response, to_global: &mut TSTransform) { - if resp.dragged() { + let dragged = self.drag_pan_buttons.iter().any(|button| match button { + DragPanButtons::PRIMARY => resp.dragged_by(PointerButton::Primary), + DragPanButtons::SECONDARY => resp.dragged_by(PointerButton::Secondary), + DragPanButtons::MIDDLE => resp.dragged_by(PointerButton::Middle), + DragPanButtons::EXTRA_1 => resp.dragged_by(PointerButton::Extra1), + DragPanButtons::EXTRA_2 => resp.dragged_by(PointerButton::Extra2), + _ => false, + }); + if dragged { to_global.translation += to_global.scaling * resp.drag_delta(); resp.mark_changed(); } From 61e883be25c824ec82764150135f2e9ebf938f8b Mon Sep 17 00:00:00 2001 From: Sven Niederberger <73159570+s-nie@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:52:20 +0200 Subject: [PATCH 023/388] Revert "Add `OutputCommand::SetPointerPosition` to set mouse position" (#5867) Reverts emilk/egui#5776 I noticed that this is already a `ViewportCommand`. Sorry for not seeing that earlier. --- crates/eframe/src/web/app_runner.rs | 3 --- crates/egui-winit/src/lib.rs | 3 --- crates/egui/src/context.rs | 5 ----- crates/egui/src/data/output.rs | 3 --- crates/egui_demo_lib/src/demo/tests/cursor_test.rs | 8 -------- 5 files changed, 22 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 835b7f008..76ae1761f 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -336,9 +336,6 @@ impl AppRunner { egui::OutputCommand::OpenUrl(open_url) => { super::open_url(&open_url.url, open_url.new_tab); } - egui::OutputCommand::SetPointerPosition(_) => { - // Not supported on the web. - } } } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index e541273fa..fe4d2945d 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -856,9 +856,6 @@ impl State { egui::OutputCommand::OpenUrl(open_url) => { open_url_in_browser(&open_url.url); } - egui::OutputCommand::SetPointerPosition(egui::Pos2 { x, y }) => { - let _ = window.set_cursor_position(winit::dpi::LogicalPosition { x, y }); - } } } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 44af99feb..6ec79f879 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1494,11 +1494,6 @@ impl Context { self.send_cmd(crate::OutputCommand::CopyImage(image)); } - /// Set the mouse cursor position (if the platform supports it). - pub fn set_pointer_position(&self, position: Pos2) { - self.send_cmd(crate::OutputCommand::SetPointerPosition(position)); - } - /// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`). /// /// Can be used to get the text for [`crate::Button::shortcut_text`]. diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index dcc03242e..2fdaec1e1 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -95,9 +95,6 @@ pub enum OutputCommand { /// Open this url in a browser. OpenUrl(OpenUrl), - - /// Set the mouse cursor position (if the platform supports it). - SetPointerPosition(emath::Pos2), } /// The non-rendering part of what egui emits each frame. diff --git a/crates/egui_demo_lib/src/demo/tests/cursor_test.rs b/crates/egui_demo_lib/src/demo/tests/cursor_test.rs index 6927e1af9..78214d5eb 100644 --- a/crates/egui_demo_lib/src/demo/tests/cursor_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/cursor_test.rs @@ -16,14 +16,6 @@ impl crate::Demo for CursorTest { impl crate::View for CursorTest { fn ui(&mut self, ui: &mut egui::Ui) { - if ui - .button("Center pointer in window") - .on_hover_text("The platform may not support this.") - .clicked() - { - let position = ui.ctx().available_rect().center(); - ui.ctx().set_pointer_position(position); - } ui.vertical_centered_justified(|ui| { ui.heading("Hover to switch cursor icon:"); for &cursor_icon in &egui::CursorIcon::ALL { From a0f072ab1ec7c0748586193c0e35612ae4d0d944 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:00:22 +0900 Subject: [PATCH 024/388] Fix bug in pointer movement detection (#5329) Fix: Popups do not appear in certain situations. * Closes #5080 * Related #5107 The root cause is that `last_move_time` is not updated in certain situations (slow situations?). --- crates/egui/src/input_state/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index bc32528d1..a5fc83ee3 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -941,6 +941,7 @@ impl PointerState { press_origin.distance(pos) > self.input_options.max_click_dist; } + self.last_move_time = time; self.pointer_events.push(PointerEvent::Moved(pos)); } Event::PointerButton { From 69b9f0eede499c64288e7008dfc1b72300dcb7c3 Mon Sep 17 00:00:00 2001 From: MStarha <59487310+MStarha@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:44:10 +0200 Subject: [PATCH 025/388] Rework `TextEdit` arrow navigation to handle Unicode graphemes (#5812) * [x] I have followed the instructions in the PR template Previously, navigating text in `TextEdit` with Ctrl + left/right arrow would jump inside words that contained combining characters (i.e. diacritics). This PR introduces new dependency of `unicode-segmentation` to handle grapheme encoding. The new implementation ignores whitespace and other separators such as `-` (dash) between words, but respects `_` (underscore). --------- Co-authored-by: lucasmerlin --- Cargo.lock | 1 + Cargo.toml | 1 + crates/egui/Cargo.toml | 1 + .../src/text_selection/text_cursor_state.rs | 84 +++++++++++++++---- .../egui/src/widgets/text_edit/text_buffer.rs | 8 +- 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2d246045..4411be0c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1285,6 +1285,7 @@ dependencies = [ "profiling", "ron", "serde", + "unicode-segmentation", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a0051513d..d272498f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ serde = { version = "1", features = ["derive"] } similar-asserts = "1.4.2" thiserror = "1.0.37" type-map = "0.5.0" +unicode-segmentation = "1.12.0" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = "0.3.73" diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index e00202d16..e7641ef31 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -87,6 +87,7 @@ ahash.workspace = true bitflags.workspace = true nohash-hasher.workspace = true profiling.workspace = true +unicode-segmentation.workspace = true #! ### Optional dependencies accesskit = { workspace = true, optional = true } diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index baf7b0463..aaa3beb9b 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -1,6 +1,7 @@ //! Text cursor changes/interaction, without modifying the text. use epaint::text::{cursor::CCursor, Galley}; +use unicode_segmentation::UnicodeSegmentation; use crate::{epaint, NumExt, Rect, Response, Ui}; @@ -166,7 +167,7 @@ fn select_line_at(text: &str, ccursor: CCursor) -> CCursorRange { pub fn ccursor_next_word(text: &str, ccursor: CCursor) -> CCursor { CCursor { - index: next_word_boundary_char_index(text.chars(), ccursor.index), + index: next_word_boundary_char_index(text, ccursor.index), prefer_next_row: false, } } @@ -180,9 +181,10 @@ fn ccursor_next_line(text: &str, ccursor: CCursor) -> CCursor { pub fn ccursor_previous_word(text: &str, ccursor: CCursor) -> CCursor { let num_chars = text.chars().count(); + let reversed: String = text.graphemes(true).rev().collect(); CCursor { index: num_chars - - next_word_boundary_char_index(text.chars().rev(), num_chars - ccursor.index), + - next_word_boundary_char_index(&reversed, num_chars - ccursor.index).min(num_chars), prefer_next_row: true, } } @@ -196,22 +198,25 @@ fn ccursor_previous_line(text: &str, ccursor: CCursor) -> CCursor { } } -fn next_word_boundary_char_index(it: impl Iterator, mut index: usize) -> usize { - let mut it = it.skip(index); - if let Some(_first) = it.next() { - index += 1; - - if let Some(second) = it.next() { - index += 1; - for next in it { - if is_word_char(next) != is_word_char(second) { - break; - } - index += 1; - } +fn next_word_boundary_char_index(text: &str, index: usize) -> usize { + for word in text.split_word_bound_indices() { + // Splitting considers contiguous whitespace as one word, such words must be skipped, + // this handles cases for example ' abc' (a space and a word), the cursor is at the beginning + // (before space) - this jumps at the end of 'abc' (this is consistent with text editors + // or browsers) + let ci = char_index_from_byte_index(text, word.0); + if ci > index && !skip_word(word.1) { + return ci; } } - index + + char_index_from_byte_index(text, text.len()) +} + +fn skip_word(text: &str) -> bool { + // skip words that contain anything other than alphanumeric characters and underscore + // (i.e. whitespace, dashes, etc.) + !text.chars().any(|c| !is_word_char(c)) } fn next_line_boundary_char_index(it: impl Iterator, mut index: usize) -> usize { @@ -233,7 +238,7 @@ fn next_line_boundary_char_index(it: impl Iterator, mut index: usiz } pub fn is_word_char(c: char) -> bool { - c.is_ascii_alphanumeric() || c == '_' + c.is_alphanumeric() || c == '_' } fn is_linebreak(c: char) -> bool { @@ -270,6 +275,16 @@ pub fn byte_index_from_char_index(s: &str, char_index: usize) -> usize { s.len() } +pub fn char_index_from_byte_index(input: &str, byte_index: usize) -> usize { + for (ci, (bi, _)) in input.char_indices().enumerate() { + if bi == byte_index { + return ci; + } + } + + input.char_indices().last().map_or(0, |(i, _)| i + 1) +} + pub fn slice_char_range(s: &str, char_range: std::ops::Range) -> &str { assert!( char_range.start <= char_range.end, @@ -293,3 +308,38 @@ pub fn cursor_rect(galley: &Galley, cursor: &CCursor, row_height: f32) -> Rect { cursor_pos } + +#[cfg(test)] +mod test { + use crate::text_selection::text_cursor_state::next_word_boundary_char_index; + + #[test] + fn test_next_word_boundary_char_index() { + // ASCII only + let text = "abc d3f g_h i-j"; + assert_eq!(next_word_boundary_char_index(text, 1), 3); + assert_eq!(next_word_boundary_char_index(text, 3), 7); + assert_eq!(next_word_boundary_char_index(text, 9), 11); + assert_eq!(next_word_boundary_char_index(text, 12), 13); + assert_eq!(next_word_boundary_char_index(text, 13), 15); + assert_eq!(next_word_boundary_char_index(text, 15), 15); + + assert_eq!(next_word_boundary_char_index("", 0), 0); + assert_eq!(next_word_boundary_char_index("", 1), 0); + + // Unicode graphemes, some of which consist of multiple Unicode characters, + // !!! Unicode character is not always what is tranditionally considered a character, + // the values below are correct despite not seeming that way on the first look, + // handling of and around emojis is kind of weird and is not consistent across + // text editors and browsers + let text = "❤️👍 skvělá knihovna 👍❤️"; + assert_eq!(next_word_boundary_char_index(text, 0), 2); + assert_eq!(next_word_boundary_char_index(text, 2), 3); // this does not skip the space between thumbs-up and 'skvělá' + assert_eq!(next_word_boundary_char_index(text, 6), 10); + assert_eq!(next_word_boundary_char_index(text, 9), 10); + assert_eq!(next_word_boundary_char_index(text, 12), 19); + assert_eq!(next_word_boundary_char_index(text, 15), 19); + assert_eq!(next_word_boundary_char_index(text, 19), 20); + assert_eq!(next_word_boundary_char_index(text, 20), 21); + } +} diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index 6cf7da15a..ebf33b097 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -8,8 +8,8 @@ use epaint::{ use crate::{ text::CCursorRange, text_selection::text_cursor_state::{ - byte_index_from_char_index, ccursor_next_word, ccursor_previous_word, find_line_start, - slice_char_range, + byte_index_from_char_index, ccursor_next_word, ccursor_previous_word, + char_index_from_byte_index, find_line_start, slice_char_range, }, }; @@ -48,6 +48,10 @@ pub trait TextBuffer { byte_index_from_char_index(self.as_str(), char_index) } + fn char_index_from_byte_index(&self, char_index: usize) -> usize { + char_index_from_byte_index(self.as_str(), char_index) + } + /// Clears all characters in this buffer fn clear(&mut self) { self.delete_char_range(0..self.as_str().len()); From 6f910f60e9740e2a28a164c04475f7ea853673bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bennet=20Ble=C3=9Fmann?= <3877590+Skgland@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:50:34 +0200 Subject: [PATCH 026/388] Protect against NaN in hit-test code (#6851) * Closes * [x] I have followed the instructions in the PR template --- crates/egui/src/hit_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index e79172129..2f0edcfaf 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -65,7 +65,7 @@ pub fn hit_test( .filter(|layer| layer.order.allow_interaction()) .flat_map(|&layer_id| widgets.get_layer(layer_id)) .filter(|&w| { - if w.interact_rect.is_negative() { + if w.interact_rect.is_negative() || w.interact_rect.any_nan() { return false; } From 3dd8d34257095af976305e64f4751716a0fc23b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Thu, 24 Apr 2025 15:39:01 +0200 Subject: [PATCH 027/388] CI: Install wasm-opt 123 from the GitHub release of Binaryen (#6849) * Prerequisite of https://github.com/emilk/egui/pull/6848 --- .github/workflows/deploy_web_demo.yml | 9 ++++++--- .github/workflows/preview_build.yml | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index 6c9d80962..d92150013 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -46,9 +46,12 @@ jobs: with: prefix-key: "web-demo-" - - name: "Install wasmopt / binaryen" - run: | - sudo apt-get update && sudo apt-get install binaryen + - name: Install wasm-opt + uses: sigoden/install-binary@v1 + with: + repo: WebAssembly/binaryen + tag: version_123 + name: wasm-opt - run: | scripts/build_demo_web.sh --release diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 932569c48..d1d19e620 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -25,9 +25,12 @@ jobs: with: prefix-key: "pr-preview-" - - name: "Install wasmopt / binaryen" - run: | - sudo apt-get update && sudo apt-get install binaryen + - name: Install wasm-opt + uses: sigoden/install-binary@v1 + with: + repo: WebAssembly/binaryen + tag: version_123 + name: wasm-opt - run: | scripts/build_demo_web.sh --release From fdb9aa282a3313b4857faa10a44ea03b44c49c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Thu, 24 Apr 2025 17:00:29 +0200 Subject: [PATCH 028/388] Raise MSRV to 1.84 (#6848) Prerequisite of https://github.com/emilk/egui/pull/6744. See: https://github.com/gfx-rs/wgpu/pull/7218, https://github.com/gfx-rs/wgpu/pull/7425 Please be aware that Rust 1.84 enables some (more) WASM extensions by default, and ships with an `std` built with them enabled: https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features/ According to `rustc +1.84 --print=cfg --target wasm32-unknown-unknown`, these are: `multivalue`, `mutable-globals`, `reference-types`, and `sign-ext`. (c.f. `rustc +1.84 --print=cfg --target wasm32-unknown-unknown -C target-cpu=mvp` enabling none.) For reference: https://webassembly.org/features/ ---- If support is desired for ancient/esoteric browsers that don't have these implemented, there are two ways to get around this: - Target `wasm32v1-none` instead, but that's a `no-std` target, and I suppose a lot of dependencies don't work that way (e.g. https://github.com/gfx-rs/wgpu/issues/6826) - Using the `-Ctarget-cpu=mvp` and `-Zbuild-std=panic_abort,std` flags, and the `RUSTC_BOOTSTRAP=1` escape hatch to allow using the latter with non-`nightly` toolchains - until https://github.com/rust-lang/wg-cargo-std-aware is stabilized. (For reference: https://github.com/ruffle-rs/ruffle/pull/18528/files#diff-fb2896d189d77b35ace9a079c1ba9b55777d16e0f11ce79f776475a451b1825a) I don't think either of these is particularly advantageous, so I suggest just accepting that browsers will have to have some extensions implemented to run `egui`. --- .github/workflows/deploy_web_demo.yml | 2 +- .github/workflows/preview_build.yml | 2 +- .github/workflows/rust.yml | 16 ++++++++-------- Cargo.toml | 2 +- clippy.toml | 2 +- crates/egui/src/lib.rs | 2 +- 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/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 +- 33 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index d92150013..bdae9bca4 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.81.0 + toolchain: 1.84.0 override: true - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index d1d19e620..8550cbeed 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.81.0 + toolchain: 1.84.0 targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index eadce83be..e75835db7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,7 +5,7 @@ name: Rust env: RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings - NIGHTLY_VERSION: nightly-2024-09-11 + NIGHTLY_VERSION: nightly-2025-04-22 jobs: fmt-crank-check-test: @@ -18,7 +18,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: 1.84.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.81.0 + toolchain: 1.84.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.81.0" + rust-version: "1.84.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -170,7 +170,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: 1.84.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -189,7 +189,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: 1.84.0 targets: aarch64-apple-ios - name: Set up cargo cache @@ -208,7 +208,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: 1.84.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 @@ -232,7 +232,7 @@ jobs: lfs: true - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: 1.84.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index d272498f7..a7a5ede7b 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.81" +rust-version = "1.84" version = "0.31.1" diff --git a/clippy.toml b/clippy.toml index f349943a9..24a804037 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.81" +msrv = "1.84" allow-unwrap-in-tests = true diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 0d8459808..f8842a1cd 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.81.0 or later to use `egui`. +//! You need to have rust 1.84.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/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index c3050c32d..f20af6faf 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 3ed54df78..98824d3a4 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index afaeb4117..da29460c7 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index f64a48803..d7db7450a 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index 7e765e4ec..a866ae4d9 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index 423179aaa..e6571fa04 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index b0e89c8a6..b98ea27d2 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 0cb873729..816686979 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index ddeaea9dc..f24b7021b 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.81" +rust-version = "1.84" 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 1e4b553db..0ad5ff844 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 541b220f7..dd980888b 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index db1d7906d..57f0269e9 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index c013e7b43..d605e25e0 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index d57f17193..00d1d591f 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index d5efc23df..995baa431 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index d26d8a6cb..df39fd456 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.81" +rust-version = "1.84" publish = false [package.metadata.cargo-machete] diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index b5a22ce7b..0288328fa 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index c18a6029d..f89af84c0 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 3ae28d693..ff1a7538b 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/rust-toolchain b/rust-toolchain index 0eefd31bc..6c3ca5854 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.81.0" +channel = "1.84.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/check.sh b/scripts/check.sh index 1a4eec5da..f232dfbb6 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.81.0 install --quiet typos-cli +# cargo +1.84.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 2d34d64fc..17d2a8cd6 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.81" +msrv = "1.84" allow-unwrap-in-tests = true diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index 9f1aeca52..0f5e8f50a 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index 15cb21640..7187c599a 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml index a44f2cc0b..280a40c1b 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/tests/test_ui_stack/Cargo.toml b/tests/test_ui_stack/Cargo.toml index dd8bdb5de..e4dd35f4d 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.81" +rust-version = "1.84" publish = false [lints] diff --git a/tests/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml index 2636b5177..e8c8bc754 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.81" +rust-version = "1.84" publish = false [lints] From f9245954ebfb599fc77a5a33aa7c90504399faba Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 24 Apr 2025 17:32:50 +0200 Subject: [PATCH 029/388] Enable more clippy lints (#6853) * Follows https://github.com/emilk/egui/pull/6848 --- Cargo.toml | 10 ++++++++- crates/ecolor/src/rgba.rs | 3 +-- crates/eframe/src/epi.rs | 10 ++++----- crates/eframe/src/lib.rs | 4 ++-- crates/eframe/src/native/app_icon.rs | 10 ++++----- crates/eframe/src/native/epi_integration.rs | 6 +++--- .../eframe/src/native/event_loop_context.rs | 2 +- crates/eframe/src/native/file_storage.rs | 4 ++-- crates/eframe/src/native/glow_integration.rs | 20 +++++++++--------- crates/eframe/src/native/run.rs | 3 +-- crates/eframe/src/native/wgpu_integration.rs | 5 ++--- crates/eframe/src/web/app_runner.rs | 4 ++-- crates/eframe/src/web/events.rs | 6 +++--- crates/eframe/src/web/mod.rs | 2 +- crates/eframe/src/web/web_logger.rs | 2 +- crates/eframe/src/web/web_painter_glow.rs | 5 +++-- crates/eframe/src/web/web_painter_wgpu.rs | 4 ++-- crates/eframe/src/web/web_runner.rs | 6 +++--- crates/egui-wgpu/src/capture.rs | 5 +++-- crates/egui-wgpu/src/lib.rs | 2 +- crates/egui-wgpu/src/renderer.rs | 6 +++--- crates/egui-wgpu/src/setup.rs | 2 +- crates/egui-wgpu/src/winit.rs | 2 +- crates/egui-winit/src/clipboard.rs | 2 +- crates/egui/src/cache/cache_trait.rs | 2 +- crates/egui/src/containers/area.rs | 6 +++--- .../egui/src/containers/collapsing_header.rs | 2 +- crates/egui/src/containers/combo_box.rs | 6 +++--- crates/egui/src/containers/menu.rs | 3 ++- crates/egui/src/containers/old_popup.rs | 6 +++--- crates/egui/src/containers/panel.rs | 2 +- crates/egui/src/containers/popup.rs | 2 +- crates/egui/src/containers/resize.rs | 4 ++-- crates/egui/src/containers/scene.rs | 2 +- crates/egui/src/containers/scroll_area.rs | 4 ++-- crates/egui/src/context.rs | 21 ++++++++----------- crates/egui/src/data/input.rs | 2 +- crates/egui/src/data/output.rs | 14 ++++++------- crates/egui/src/grid.rs | 6 +++--- crates/egui/src/id.rs | 2 +- crates/egui/src/input_state/mod.rs | 4 ++-- crates/egui/src/introspection.rs | 2 +- crates/egui/src/layout.rs | 2 +- crates/egui/src/load.rs | 2 +- crates/egui/src/load/bytes_loader.rs | 2 +- crates/egui/src/load/texture_loader.rs | 4 ++-- crates/egui/src/menu.rs | 4 ++-- crates/egui/src/os.rs | 2 +- crates/egui/src/painter.rs | 6 +++--- crates/egui/src/pass_state.rs | 2 +- crates/egui/src/style.rs | 7 ++++--- .../text_selection/label_text_selection.rs | 2 +- .../src/text_selection/text_cursor_state.rs | 4 ++-- crates/egui/src/ui.rs | 8 +++---- crates/egui/src/ui_builder.rs | 2 +- crates/egui/src/ui_stack.rs | 2 +- crates/egui/src/util/id_type_map.rs | 2 +- crates/egui/src/viewport.rs | 1 - crates/egui/src/widgets/button.rs | 6 ++---- crates/egui/src/widgets/checkbox.rs | 4 ++-- crates/egui/src/widgets/color_picker.rs | 2 +- crates/egui/src/widgets/drag_value.rs | 6 +++--- crates/egui/src/widgets/hyperlink.rs | 4 ++-- crates/egui/src/widgets/progress_bar.rs | 4 ++-- crates/egui/src/widgets/radio_button.rs | 2 +- crates/egui/src/widgets/selected_label.rs | 4 +++- crates/egui/src/widgets/slider.rs | 4 ++-- crates/egui/src/widgets/text_edit/builder.rs | 6 +++--- crates/egui/src/widgets/text_edit/state.rs | 2 +- .../egui_demo_app/src/apps/custom3d_glow.rs | 2 +- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 2 +- crates/egui_demo_app/src/backend_panel.rs | 2 +- crates/egui_demo_app/src/lib.rs | 2 +- crates/egui_demo_app/src/main.rs | 2 +- crates/egui_demo_app/src/web.rs | 2 +- crates/egui_demo_app/src/wrap_app.rs | 2 +- crates/egui_demo_app/tests/test_demo_app.rs | 2 +- crates/egui_demo_lib/src/demo/code_example.rs | 2 +- .../src/demo/demo_app_windows.rs | 6 +++--- crates/egui_demo_lib/src/demo/font_book.rs | 2 +- .../src/demo/interactive_container.rs | 2 +- crates/egui_demo_lib/src/demo/modals.rs | 6 +++--- crates/egui_demo_lib/src/demo/paint_bezier.rs | 2 +- crates/egui_demo_lib/src/demo/password.rs | 1 - crates/egui_demo_lib/src/demo/screenshot.rs | 2 +- crates/egui_demo_lib/src/demo/scrolling.rs | 4 ++-- .../src/demo/tests/tessellation_test.rs | 2 +- crates/egui_demo_lib/src/demo/text_edit.rs | 2 +- .../egui_demo_lib/src/demo/toggle_switch.rs | 2 +- .../egui_demo_lib/src/demo/widget_gallery.rs | 4 ++-- crates/egui_extras/src/datepicker/mod.rs | 2 +- crates/egui_extras/src/datepicker/popup.rs | 2 +- crates/egui_extras/src/layout.rs | 2 +- crates/egui_extras/src/lib.rs | 2 +- .../egui_extras/src/loaders/image_loader.rs | 2 +- crates/egui_extras/src/loaders/svg_loader.rs | 2 +- crates/egui_extras/src/loaders/webp_loader.rs | 2 +- crates/egui_extras/src/syntax_highlighting.rs | 9 ++++---- crates/egui_glow/examples/pure_glow.rs | 18 ++++++++-------- crates/egui_glow/src/lib.rs | 2 +- crates/egui_glow/src/painter.rs | 4 +--- crates/egui_glow/src/shader_version.rs | 3 +-- crates/egui_glow/src/vao.rs | 1 - crates/egui_kittest/src/snapshot.rs | 9 ++++---- crates/egui_kittest/tests/menu.rs | 2 +- crates/egui_kittest/tests/popup.rs | 2 +- crates/egui_kittest/tests/regression_tests.rs | 4 ++-- crates/egui_kittest/tests/tests.rs | 2 +- crates/emath/src/lib.rs | 2 +- crates/emath/src/numeric.rs | 4 ++-- crates/emath/src/ordered_float.rs | 2 +- crates/emath/src/smart_aim.rs | 2 +- crates/epaint/src/lib.rs | 2 +- crates/epaint/src/shapes/shape.rs | 2 +- crates/epaint/src/tessellator.rs | 6 +++--- crates/epaint/src/text/font.rs | 8 +++---- crates/epaint/src/text/fonts.rs | 1 - crates/epaint/src/text/text_layout.rs | 2 +- crates/epaint/src/text/text_layout_types.rs | 6 +++--- crates/epaint/src/texture_handle.rs | 6 +++--- examples/custom_style/src/main.rs | 2 +- examples/puffin_profiler/src/main.rs | 2 +- tests/egui_tests/tests/test_widgets.rs | 4 ++-- tests/test_inline_glow_paint/src/main.rs | 2 +- 124 files changed, 243 insertions(+), 244 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a7a5ede7b..80fee9f74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,6 +134,7 @@ broken_intra_doc_links = "warn" # See also clippy.toml [workspace.lints.clippy] +allow_attributes = "warn" as_ptr_cast_mut = "warn" await_holding_lock = "warn" bool_to_int_with_if = "warn" @@ -195,13 +196,13 @@ macro_use_imports = "warn" manual_assert = "warn" manual_clamp = "warn" manual_instant_elapsed = "warn" +manual_is_power_of_two = "warn" manual_is_variant_and = "warn" manual_let_else = "warn" manual_ok_or = "warn" manual_string_new = "warn" map_err_ignore = "warn" map_flatten = "warn" -map_unwrap_or = "warn" match_bool = "warn" match_on_vec_items = "warn" match_same_arms = "warn" @@ -222,11 +223,13 @@ needless_for_each = "warn" needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" negative_feature_names = "warn" +non_zero_suggestions = "warn" nonstandard_macro_braces = "warn" option_as_ref_cloned = "warn" option_option = "warn" path_buf_push_overwrite = "warn" print_stderr = "warn" +pathbuf_init_then_push = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" pub_underscore_fields = "warn" @@ -240,6 +243,7 @@ ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" same_functions_in_if_condition = "warn" semicolon_if_nothing_returned = "warn" +set_contains_or_insert = "warn" single_char_pattern = "warn" single_match_else = "warn" str_split_at_newline = "warn" @@ -252,6 +256,7 @@ string_to_string = "warn" suspicious_command_arg_space = "warn" suspicious_xor_used_as_pow = "warn" todo = "warn" +too_long_first_doc_paragraph = "warn" trailing_empty_array = "warn" trait_duplication_in_bounds = "warn" tuple_array_conversions = "warn" @@ -261,6 +266,7 @@ unimplemented = "warn" uninhabited_references = "warn" uninlined_format_args = "warn" unnecessary_box_returns = "warn" +unnecessary_literal_bound = "warn" unnecessary_safety_doc = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" @@ -268,6 +274,7 @@ unnested_or_patterns = "warn" unused_peekable = "warn" unused_rounding = "warn" unused_self = "warn" +unused_trait_names = "warn" use_self = "warn" useless_transmute = "warn" verbose_file_reads = "warn" @@ -286,6 +293,7 @@ assigning_clones = "allow" # No please let_underscore_must_use = "allow" let_underscore_untyped = "allow" manual_range_contains = "allow" # this one is just worse imho +map_unwrap_or = "allow" # so is this one self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 significant_drop_tightening = "allow" # Too many false positives wildcard_imports = "allow" # `use crate::*` is useful to avoid merge conflicts when adding/removing imports diff --git a/crates/ecolor/src/rgba.rs b/crates/ecolor/src/rgba.rs index 99fab41cc..3e40af2b0 100644 --- a/crates/ecolor/src/rgba.rs +++ b/crates/ecolor/src/rgba.rs @@ -33,12 +33,11 @@ pub(crate) fn f32_hash(state: &mut H, f: f32) { } else if f.is_nan() { state.write_u8(1); } else { - use std::hash::Hash; + use std::hash::Hash as _; f.to_bits().hash(state); } } -#[allow(clippy::derived_hash_with_manual_eq)] impl std::hash::Hash for Rgba { #[inline] fn hash(&self, state: &mut H) { diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 562311dc6..fd08fc1ee 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -91,7 +91,7 @@ pub struct CreationContext<'s> { pub(crate) raw_display_handle: Result, } -#[allow(unsafe_code)] +#[expect(unsafe_code)] #[cfg(not(target_arch = "wasm32"))] impl HasWindowHandle for CreationContext<'_> { fn window_handle(&self) -> Result, HandleError> { @@ -100,7 +100,7 @@ impl HasWindowHandle for CreationContext<'_> { } } -#[allow(unsafe_code)] +#[expect(unsafe_code)] #[cfg(not(target_arch = "wasm32"))] impl HasDisplayHandle for CreationContext<'_> { fn display_handle(&self) -> Result, HandleError> { @@ -662,7 +662,7 @@ pub struct Frame { #[cfg(not(target_arch = "wasm32"))] assert_not_impl_any!(Frame: Clone); -#[allow(unsafe_code)] +#[expect(unsafe_code)] #[cfg(not(target_arch = "wasm32"))] impl HasWindowHandle for Frame { fn window_handle(&self) -> Result, HandleError> { @@ -671,7 +671,7 @@ impl HasWindowHandle for Frame { } } -#[allow(unsafe_code)] +#[expect(unsafe_code)] #[cfg(not(target_arch = "wasm32"))] impl HasDisplayHandle for Frame { fn display_handle(&self) -> Result, HandleError> { @@ -703,7 +703,7 @@ impl Frame { /// True if you are in a web environment. /// /// Equivalent to `cfg!(target_arch = "wasm32")` - #[allow(clippy::unused_self)] + #[expect(clippy::unused_self)] pub fn is_web(&self) -> bool { cfg!(target_arch = "wasm32") } diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 7b342a4c2..ec27b05a0 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -69,7 +69,7 @@ //! #[wasm_bindgen] //! impl WebHandle { //! /// Installs a panic hook, then returns. -//! #[allow(clippy::new_without_default)] +//! #[expect(clippy::new_without_default)] //! #[wasm_bindgen(constructor)] //! pub fn new() -> Self { //! // Redirect [`log`] message to `console.log` and friends: @@ -236,7 +236,7 @@ pub mod icon_data; /// This function can fail if we fail to set up a graphics context. #[cfg(not(target_arch = "wasm32"))] #[cfg(any(feature = "glow", feature = "wgpu"))] -#[allow(clippy::needless_pass_by_value)] +#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)] pub fn run_native( app_name: &str, mut native_options: NativeOptions, diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 8591ba2a8..1f1bf52f2 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -47,7 +47,7 @@ enum AppIconStatus { NotSetTryAgain, /// We successfully set the icon and it should be visible now. - #[allow(dead_code)] // Not used on Linux + #[allow(dead_code, clippy::allow_attributes)] // Not used on Linux Set, } @@ -71,13 +71,13 @@ fn set_title_and_icon(_title: &str, _icon_data: Option<&IconData>) -> AppIconSta #[cfg(target_os = "macos")] return set_title_and_icon_mac(_title, _icon_data); - #[allow(unreachable_code)] + #[allow(unreachable_code, clippy::allow_attributes)] AppIconStatus::NotSetIgnored } /// Set icon for Windows applications. #[cfg(target_os = "windows")] -#[allow(unsafe_code)] +#[expect(unsafe_code)] fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { use crate::icon_data::IconDataExt as _; use winapi::um::winuser; @@ -198,12 +198,12 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { /// Set icon & app title for `MacOS` applications. #[cfg(target_os = "macos")] -#[allow(unsafe_code)] +#[expect(unsafe_code)] fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconStatus { use crate::icon_data::IconDataExt as _; profiling::function_scope!(); - use objc2::ClassType; + use objc2::ClassType as _; use objc2_app_kit::{NSApplication, NSImage}; use objc2_foundation::{NSData, NSString}; diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 03b5f2dcd..aaa8c596a 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -136,7 +136,7 @@ pub fn create_storage(_app_name: &str) -> Option> { None } -#[allow(clippy::unnecessary_wraps)] +#[expect(clippy::unnecessary_wraps)] pub fn create_storage_with_file(_file: impl Into) -> Option> { #[cfg(feature = "persistence")] return Some(Box::new( @@ -169,7 +169,7 @@ pub struct EpiIntegration { } impl EpiIntegration { - #[allow(clippy::too_many_arguments)] + #[expect(clippy::too_many_arguments)] pub fn new( egui_ctx: egui::Context, window: &winit::window::Window, @@ -326,7 +326,7 @@ impl EpiIntegration { } } - #[allow(clippy::unused_self)] + #[allow(clippy::unused_self, clippy::allow_attributes)] pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) { #[cfg(feature = "persistence")] if let Some(storage) = self.frame.storage_mut() { diff --git a/crates/eframe/src/native/event_loop_context.rs b/crates/eframe/src/native/event_loop_context.rs index f1262f853..810db8e1f 100644 --- a/crates/eframe/src/native/event_loop_context.rs +++ b/crates/eframe/src/native/event_loop_context.rs @@ -27,7 +27,7 @@ impl Drop for EventLoopGuard { } // Helper function to safely use the current event loop -#[allow(unsafe_code)] +#[expect(unsafe_code)] pub fn with_current_event_loop(f: F) -> Option where F: FnOnce(&ActiveEventLoop) -> R, diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 7fde2d288..fa89b9059 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - io::Write, + io::Write as _, path::{Path, PathBuf}, }; @@ -42,7 +42,7 @@ pub fn storage_dir(app_id: &str) -> Option { // Adapted from // https://github.com/rust-lang/cargo/blob/6e11c77384989726bb4f412a0e23b59c27222c34/crates/home/src/windows.rs#L19-L37 #[cfg(all(windows, not(target_vendor = "uwp")))] -#[allow(unsafe_code)] +#[expect(unsafe_code)] fn roaming_appdata() -> Option { use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 94b254682..946210c86 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -11,13 +11,13 @@ use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; use egui_winit::ActionRequested; use glutin::{ - config::GlConfig, - context::NotCurrentGlContext, - display::GetGlDisplay, - prelude::{GlDisplay, PossiblyCurrentGlContext}, - surface::GlSurface, + config::GlConfig as _, + context::NotCurrentGlContext as _, + display::GetGlDisplay as _, + prelude::{GlDisplay as _, PossiblyCurrentGlContext as _}, + surface::GlSurface as _, }; -use raw_window_handle::HasWindowHandle; +use raw_window_handle::HasWindowHandle as _; use winit::{ event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}, window::{Window, WindowId}, @@ -139,7 +139,7 @@ impl<'app> GlowWinitApp<'app> { } } - #[allow(unsafe_code)] + #[expect(unsafe_code)] fn create_glutin_windowed_context( egui_ctx: &egui::Context, event_loop: &ActiveEventLoop, @@ -901,7 +901,7 @@ fn change_gl_context( } impl GlutinWindowContext { - #[allow(unsafe_code)] + #[expect(unsafe_code)] unsafe fn new( egui_ctx: &egui::Context, viewport_builder: ViewportBuilder, @@ -1094,7 +1094,7 @@ impl GlutinWindowContext { } /// Create a surface, window, and winit integration for the viewport, if missing. - #[allow(unsafe_code)] + #[expect(unsafe_code)] pub(crate) fn initialize_window( &mut self, viewport_id: ViewportId, @@ -1566,6 +1566,6 @@ fn save_screenshot_and_exit( }); log::info!("Screenshot saved to {path:?}."); - #[allow(clippy::exit)] + #[expect(clippy::exit)] std::process::exit(0); } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 641459b52..9bcb49686 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -163,7 +163,6 @@ impl WinitAppWrapper { log::debug!("Exiting with return code 0"); - #[allow(clippy::exit)] std::process::exit(0); } } @@ -317,7 +316,7 @@ impl ApplicationHandler for WinitAppWrapper { #[cfg(not(target_os = "ios"))] fn run_and_return(event_loop: &mut EventLoop, winit_app: impl WinitApp) -> Result { - use winit::platform::run_on_demand::EventLoopExtRunOnDemand; + use winit::platform::run_on_demand::EventLoopExtRunOnDemand as _; log::trace!("Entering the winit event loop (run_app_on_demand)…"); diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 60484cdee..96e93300e 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -15,7 +15,7 @@ use winit::{ window::{Window, WindowId}, }; -use ahash::{HashMap, HashSet, HashSetExt}; +use ahash::{HashMap, HashSet, HashSetExt as _}; use egui::{ DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, @@ -182,7 +182,6 @@ impl<'app> WgpuWinitApp<'app> { builder: ViewportBuilder, ) -> crate::Result<&mut WgpuWinitRunning<'app>> { profiling::function_scope!(); - #[allow(unsafe_code, unused_mut, unused_unsafe)] let mut painter = pollster::block_on(egui_wgpu::winit::Painter::new( egui_ctx.clone(), self.native_options.wgpu_options.clone(), @@ -236,7 +235,7 @@ impl<'app> WgpuWinitApp<'app> { }); } - #[allow(unused_mut)] // used for accesskit + #[allow(unused_mut, clippy::allow_attributes)] // used for accesskit let mut egui_winit = egui_winit::State::new( egui_ctx.clone(), ViewportId::ROOT, diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 76ae1761f..675c57bec 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -2,10 +2,10 @@ use egui::{TexturesDelta, UserData, ViewportCommand}; use crate::{epi, App}; -use super::{now_sec, text_agent::TextAgent, web_painter::WebPainter, NeedRepaint}; +use super::{now_sec, text_agent::TextAgent, web_painter::WebPainter as _, NeedRepaint}; pub struct AppRunner { - #[allow(dead_code)] + #[allow(dead_code, clippy::allow_attributes)] pub(crate) web_options: crate::WebOptions, pub(crate) frame: epi::Frame, egui_ctx: egui::Context, diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 32fba9ef0..1782a6797 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -4,7 +4,7 @@ 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, JsValue, WebRunner, + theme_from_dark_mode, translate_key, AppRunner, Closure, JsCast as _, JsValue, WebRunner, DEBUG_RESIZE, }; @@ -163,7 +163,7 @@ fn install_keydown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), J ) } -#[allow(clippy::needless_pass_by_value)] // So that we can pass it directly to `add_event_listener` +#[expect(clippy::needless_pass_by_value)] // So that we can pass it directly to `add_event_listener` pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { let has_focus = runner.input.raw.focused; if !has_focus { @@ -261,7 +261,7 @@ fn install_keyup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV runner_ref.add_event_listener(target, "keyup", on_keyup) } -#[allow(clippy::needless_pass_by_value)] // So that we can pass it directly to `add_event_listener` +#[expect(clippy::needless_pass_by_value)] // So that we can pass it directly to `add_event_listener` pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { let modifiers = modifiers_from_kb_event(&event); runner.input.raw.modifiers = modifiers; diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index c67fa69e6..2bdd3af63 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -281,7 +281,7 @@ fn create_clipboard_item(mime: &str, bytes: &[u8]) -> Result &str { if let Some(i) = file_path.rfind("/src/") { if let Some(prev_slash) = file_path[..i].rfind('/') { diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index 876a6d78e..ca6e11bf5 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -1,7 +1,7 @@ use egui::{Event, UserData, ViewportId}; use egui_glow::glow; use std::sync::Arc; -use wasm_bindgen::JsCast; +use wasm_bindgen::JsCast as _; use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; @@ -27,7 +27,8 @@ impl WebPainterGlow { ) -> Result { let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options.webgl_context_option)?; - #[allow(clippy::arc_with_non_send_sync)] + + #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm let gl = std::sync::Arc::new(gl); let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering) diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index f018c7316..3cfc53f1c 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -23,7 +23,7 @@ pub(crate) struct WebPainterWgpu { } impl WebPainterWgpu { - #[allow(unused)] // only used if `wgpu` is the only active feature. + #[expect(unused)] // only used if `wgpu` is the only active feature. pub fn render_state(&self) -> Option { self.render_state.clone() } @@ -55,7 +55,7 @@ impl WebPainterWgpu { }) } - #[allow(unused)] // only used if `wgpu` is the only active feature. + #[expect(unused)] // only used if `wgpu` is the only active feature. pub async fn new( ctx: egui::Context, canvas: web_sys::HtmlCanvasElement, diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 4c43fcb69..099be7aeb 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -37,7 +37,7 @@ pub struct WebRunner { impl WebRunner { /// Will install a panic handler that will catch and log any panics - #[allow(clippy::new_without_default)] + #[expect(clippy::new_without_default)] pub fn new() -> Self { let panic_handler = PanicHandler::install(); @@ -280,7 +280,7 @@ struct TargetEvent { closure: Closure, } -#[allow(unused)] +#[expect(unused)] struct IntervalHandle { handle: i32, closure: Closure, @@ -289,7 +289,7 @@ struct IntervalHandle { enum EventToUnsubscribe { TargetEvent(TargetEvent), - #[allow(unused)] + #[expect(unused)] IntervalHandle(IntervalHandle), } diff --git a/crates/egui-wgpu/src/capture.rs b/crates/egui-wgpu/src/capture.rs index 40cf5484f..b4072a8b1 100644 --- a/crates/egui-wgpu/src/capture.rs +++ b/crates/egui-wgpu/src/capture.rs @@ -4,6 +4,7 @@ use std::sync::{mpsc, Arc}; use wgpu::{BindGroupLayout, MultisampleState, StoreOp}; /// A texture and a buffer for reading the rendered frame back to the cpu. +/// /// The texture is required since [`wgpu::TextureUsages::COPY_SRC`] is not an allowed /// flag for the surface texture on all platforms. This means that anytime we want to /// capture the frame, we first render it to this texture, and then we can copy it to @@ -125,7 +126,7 @@ impl CaptureState { // It would be more efficient to reuse the Buffer, e.g. via some kind of ring buffer, but // for most screenshot use cases this should be fine. When taking many screenshots (e.g. for a video) // it might make sense to revisit this and implement a more efficient solution. - #[allow(clippy::arc_with_non_send_sync)] + #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm let buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("egui_screen_capture_buffer"), size: (self.padding.padded_bytes_per_row * self.texture.height()) as u64, @@ -184,7 +185,7 @@ impl CaptureState { tx: CaptureSender, viewport_id: ViewportId, ) { - #[allow(clippy::arc_with_non_send_sync)] + #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm let buffer = Arc::new(buffer); let buffer_clone = buffer.clone(); let buffer_slice = buffer_clone.slice(..); diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index e19c26574..5df19db85 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -242,7 +242,7 @@ impl RenderState { // On wasm, depending on feature flags, wgpu objects may or may not implement sync. // It doesn't make sense to switch to Rc for that special usecase, so simply disable the lint. - #[allow(clippy::arc_with_non_send_sync)] + #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm Ok(Self { adapter, #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 5a5c953bb..0dc746320 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, PaintCallbackInfo, Primitive, Vertex}; +use epaint::{emath::NumExt as _, PaintCallbackInfo, Primitive, Vertex}; use wgpu::util::DeviceExt as _; @@ -749,7 +749,7 @@ impl Renderer { /// /// The texture must have the format [`wgpu::TextureFormat::Rgba8UnormSrgb`]. /// Any compare function supplied in the [`wgpu::SamplerDescriptor`] will be ignored. - #[allow(clippy::needless_pass_by_value)] // false positive + #[expect(clippy::needless_pass_by_value)] // false positive pub fn register_native_texture_with_sampler_options( &mut self, device: &wgpu::Device, @@ -796,7 +796,7 @@ impl Renderer { /// [`wgpu::SamplerDescriptor`] options. /// /// This allows applications to reuse [`epaint::TextureId`]s created with custom sampler options. - #[allow(clippy::needless_pass_by_value)] // false positive + #[expect(clippy::needless_pass_by_value)] // false positive pub fn update_egui_texture_from_wgpu_texture_with_sampler_options( &mut self, device: &wgpu::Device, diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index 1f70b6bb7..2499d006b 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -48,7 +48,7 @@ impl WgpuSetup { pub async fn new_instance(&self) -> wgpu::Instance { match self { Self::CreateNew(create_new) => { - #[allow(unused_mut)] + #[allow(unused_mut, clippy::allow_attributes)] let mut backends = create_new.instance_descriptor.backends; // Don't try WebGPU if we're not in a secure context. diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index cc3a7db2c..e0eafee71 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -575,7 +575,7 @@ impl Painter { .retain(|id, _| active_viewports.contains(id)); } - #[allow(clippy::needless_pass_by_ref_mut, clippy::unused_self)] + #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)] pub fn destroy(&mut self) { // TODO(emilk): something here? } diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index c8adcad21..a0221294e 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -161,7 +161,7 @@ fn init_smithay_clipboard( if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle { log::trace!("Initializing smithay clipboard…"); - #[allow(unsafe_code)] + #[expect(unsafe_code)] Some(unsafe { smithay_clipboard::Clipboard::new(display.display.as_ptr()) }) } else { #[cfg(feature = "wayland")] diff --git a/crates/egui/src/cache/cache_trait.rs b/crates/egui/src/cache/cache_trait.rs index 73cb61f38..fc00a9880 100644 --- a/crates/egui/src/cache/cache_trait.rs +++ b/crates/egui/src/cache/cache_trait.rs @@ -1,5 +1,5 @@ /// A cache, storing some value for some length of time. -#[allow(clippy::len_without_is_empty)] +#[expect(clippy::len_without_is_empty)] pub trait CacheTrait: 'static + Send + Sync { /// Call once per frame to evict cache. fn update(&mut self); diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 55cdb0755..0d21e8bdd 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, Order, Pos2, Rect, - Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState, + emath, pos2, Align2, Context, Id, InnerResponse, LayerId, Layout, NumExt as _, Order, Pos2, + Rect, Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState, }; /// State of an [`Area`] that is persisted between frames. @@ -602,7 +602,7 @@ impl Prepared { self.move_response.id } - #[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`. + #[expect(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`. pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response { let Self { info: _, diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index c34e56294..66e024f0b 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -1,7 +1,7 @@ use std::hash::Hash; use crate::{ - emath, epaint, pos2, remap, remap_clamp, vec2, Context, Id, InnerResponse, NumExt, Rect, + 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, }; diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 7a5a160f9..5544e759d 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -2,11 +2,11 @@ use epaint::Shape; use crate::{ epaint, style::StyleModifier, style::WidgetVisuals, vec2, Align2, Context, Id, InnerResponse, - NumExt, Painter, Popup, PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, + NumExt as _, Painter, Popup, PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType, }; -#[allow(unused_imports)] // Documentation +#[expect(unused_imports)] // Documentation use crate::style::Spacing; /// A function that paints the [`ComboBox`] icon @@ -297,7 +297,7 @@ impl ComboBox { } } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn combo_box_dyn<'c, R>( ui: &mut Ui, button_id: Id, diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index 7511d07d1..228bbd86d 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -1,7 +1,7 @@ use crate::style::StyleModifier; use crate::{ Button, Color32, Context, Frame, Id, InnerResponse, Layout, Popup, PopupCloseBehavior, - Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget, WidgetText, + Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget as _, WidgetText, }; use emath::{vec2, Align, RectAlign, Vec2}; use epaint::Stroke; @@ -159,6 +159,7 @@ impl MenuState { } /// Horizontal menu bar where you can add [`MenuButton`]s. +/// /// 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`]. diff --git a/crates/egui/src/containers/old_popup.rs b/crates/egui/src/containers/old_popup.rs index fc4da4e23..3e5de650f 100644 --- a/crates/egui/src/containers/old_popup.rs +++ b/crates/egui/src/containers/old_popup.rs @@ -4,7 +4,7 @@ use crate::containers::tooltip::Tooltip; use crate::{ Align, Context, Id, LayerId, Layout, Popup, PopupAnchor, PopupCloseBehavior, Pos2, Rect, - Response, Ui, Widget, WidgetText, + Response, Ui, Widget as _, WidgetText, }; use emath::RectAlign; // ---------------------------------------------------------------------------- @@ -19,7 +19,7 @@ use emath::RectAlign; /// /// ``` /// # egui::__run_test_ui(|ui| { -/// # #[allow(deprecated)] +/// # #[expect(deprecated)] /// if ui.ui_contains_pointer() { /// egui::show_tooltip(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), |ui| { /// ui.label("Helpful text"); @@ -177,7 +177,7 @@ pub fn popup_below_widget( /// } /// let below = egui::AboveOrBelow::Below; /// let close_on_click_outside = egui::PopupCloseBehavior::CloseOnClickOutside; -/// # #[allow(deprecated)] +/// # #[expect(deprecated)] /// egui::popup_above_or_below_widget(ui, popup_id, &response, below, close_on_click_outside, |ui| { /// ui.set_min_width(200.0); // if you want to control the size /// ui.label("Some more info, or things you can select:"); diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 6fd19e186..db0e8a7f9 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -18,7 +18,7 @@ use emath::GuiRounding as _; use crate::{ - lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt, + lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef, Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, }; diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 7159a0525..47e800d22 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -466,7 +466,7 @@ impl<'a> Popup<'a> { }; RectAlign::find_best_align( - #[allow(clippy::iter_on_empty_collections)] + #[expect(clippy::iter_on_empty_collections)] once(self.rect_align).chain( self.alternative_aligns // Need the empty slice so the iters have the same type so we can unwrap_or diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 0df9a1136..fe3861646 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, Rect, Response, Sense, Shape, Ui, - UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, + pos2, vec2, Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense, + Shape, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, }; #[derive(Clone, Copy, Debug)] diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index d023a4d3b..e5a350327 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -1,6 +1,6 @@ use core::f32; -use emath::{GuiRounding, Pos2}; +use emath::{GuiRounding as _, Pos2}; use crate::{ emath::TSTransform, InnerResponse, LayerId, PointerButton, Rangef, Rect, Response, Sense, Ui, diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index c430b6ea9..b2df2100b 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,8 +1,8 @@ #![allow(clippy::needless_range_loop)] use crate::{ - emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, Context, Id, NumExt, Pos2, Rangef, - Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, + emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, Context, Id, NumExt as _, Pos2, + Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, }; #[derive(Clone, Copy, Debug)] diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 6ec79f879..47be4f3d3 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -34,9 +34,10 @@ use crate::{ viewport::ViewportClass, Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, - ModifierNames, NumExt, Order, Painter, RawInput, Response, RichText, ScrollArea, Sense, Style, - TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, ViewportCommand, ViewportId, - ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, Widget, WidgetRect, WidgetText, + ModifierNames, 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")] @@ -314,7 +315,7 @@ impl std::fmt::Display for RepaintCause { impl RepaintCause { /// Capture the file and line number of the call site. - #[allow(clippy::new_without_default)] + #[expect(clippy::new_without_default)] #[track_caller] pub fn new() -> Self { let caller = Location::caller(); @@ -327,7 +328,6 @@ impl RepaintCause { /// Capture the file and line number of the call site, /// as well as add a reason. - #[allow(clippy::new_without_default)] #[track_caller] pub fn new_reason(reason: impl Into>) -> Self { let caller = Location::caller(); @@ -1160,7 +1160,6 @@ impl Context { /// /// `allow_focus` should usually be true, unless you call this function multiple times with the /// same widget, then `allow_focus` should only be true once (like in [`Ui::new`] (true) and [`Ui::remember_min_rect`] (false)). - #[allow(clippy::too_many_arguments)] pub(crate) fn create_widget(&self, w: WidgetRect, allow_focus: bool) -> Response { let interested_in_focus = w.enabled && w.sense.is_focusable() @@ -1189,7 +1188,7 @@ impl Context { self.check_for_id_clash(w.id, w.rect, "widget"); } - #[allow(clippy::let_and_return)] + #[allow(clippy::let_and_return, clippy::allow_attributes)] let res = self.get_response(w); #[cfg(feature = "accesskit")] @@ -2350,7 +2349,6 @@ 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(); - #[cfg_attr(not(feature = "accesskit"), allow(unused_mut))] let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); #[cfg(feature = "accesskit")] @@ -2651,7 +2649,7 @@ impl Context { /// Is an egui context menu open? /// /// This only works with the old, deprecated [`crate::menu`] API. - #[allow(deprecated)] + #[expect(deprecated)] #[deprecated = "Use `is_popup_open` instead"] pub fn is_context_menu_open(&self) -> bool { self.data(|d| { @@ -3205,7 +3203,7 @@ impl Context { } }); - #[allow(deprecated)] + #[expect(deprecated)] ui.horizontal(|ui| { ui.label(format!( "{} menu bars", @@ -3263,7 +3261,7 @@ impl Context { /// the function is still called, but with no other effect. /// /// No locks are held while the given closure is called. - #[allow(clippy::unused_self, clippy::let_and_return)] + #[allow(clippy::unused_self, clippy::let_and_return, clippy::allow_attributes)] #[inline] pub fn with_accessibility_parent(&self, _id: Id, f: impl FnOnce() -> R) -> R { // TODO(emilk): this isn't thread-safe - another thread can call this function between the push/pop calls @@ -3596,7 +3594,6 @@ impl Context { /// * Set the window attributes (position, size, …) based on [`ImmediateViewport::builder`]. /// * Call [`Context::run`] with [`ImmediateViewport::viewport_ui_cb`]. /// * Handle the output from [`Context::run`], including rendering - #[allow(clippy::unused_self)] pub fn set_immediate_viewport_renderer( callback: impl for<'a> Fn(&Self, ImmediateViewport<'a>) + 'static, ) { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index cc8725248..9f15f1c43 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -1233,7 +1233,7 @@ pub struct EventFilter { pub escape: bool, } -#[allow(clippy::derivable_impls)] // let's be explicit +#[expect(clippy::derivable_impls)] // let's be explicit impl Default for EventFilter { fn default() -> Self { Self { diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 2fdaec1e1..ab9044cb6 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -252,7 +252,7 @@ pub struct OpenUrl { } impl OpenUrl { - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn same_tab(url: impl ToString) -> Self { Self { url: url.to_string(), @@ -260,7 +260,7 @@ impl OpenUrl { } } - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn new_tab(url: impl ToString) -> Self { Self { url: url.to_string(), @@ -607,7 +607,7 @@ impl WidgetInfo { } } - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self { Self { enabled, @@ -617,7 +617,7 @@ impl WidgetInfo { } /// checkboxes, radio-buttons etc - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self { Self { enabled, @@ -635,7 +635,7 @@ impl WidgetInfo { } } - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self { let label = label.to_string(); Self { @@ -646,7 +646,7 @@ impl WidgetInfo { } } - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn text_edit( enabled: bool, prev_text_value: impl ToString, @@ -670,7 +670,7 @@ impl WidgetInfo { } } - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn text_selection_changed( enabled: bool, text_selection: std::ops::RangeInclusive, diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 9ad386cc6..dbc22a09d 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, Painter, Rect, Region, Style, Ui, - UiBuilder, Vec2, + vec2, Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style, + Ui, UiBuilder, Vec2, }; #[cfg(debug_assertions)] @@ -184,7 +184,7 @@ impl GridLayout { Rect::from_min_size(cursor.min, size).round_ui() } - #[allow(clippy::unused_self)] + #[expect(clippy::unused_self)] pub(crate) fn align_size_within_rect(&self, size: Vec2, frame: Rect) -> Rect { // TODO(emilk): allow this alignment to be customized Align2::LEFT_CENTER diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index ddc1a59b7..7bcef8dc2 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -59,7 +59,7 @@ impl Id { /// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument. pub fn with(self, child: impl std::hash::Hash) -> Self { - use std::hash::{BuildHasher, Hasher}; + use std::hash::{BuildHasher as _, Hasher as _}; let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher(); hasher.write_u64(self.0.get()); child.hash(&mut hasher); diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index a5fc83ee3..a5d6951ce 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -5,7 +5,7 @@ use crate::data::input::{ TouchDeviceId, ViewportInfo, NUM_POINTER_BUTTONS, }; use crate::{ - emath::{vec2, NumExt, Pos2, Rect, Vec2}, + emath::{vec2, NumExt as _, Pos2, Rect, Vec2}, util::History, }; use std::{ @@ -344,7 +344,7 @@ impl InputState { let is_zoom = modifiers.ctrl || modifiers.mac_cmd || modifiers.command; - #[allow(clippy::collapsible_else_if)] + #[expect(clippy::collapsible_else_if)] if is_zoom { if is_smooth { smooth_scroll_delta_for_zoom += delta.y; diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index e21ddb3a5..8826eca79 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, Rect, Response, Sense, Shape, Slider, TextStyle, TextWrapMode, Ui, Widget, + NumExt as _, Rect, Response, Sense, Shape, Slider, TextStyle, TextWrapMode, Ui, Widget, }; pub fn font_family_ui(ui: &mut Ui, font_family: &mut FontFamily) { diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 91f39fb2d..a0665246b 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -1,7 +1,7 @@ use emath::GuiRounding as _; use crate::{ - emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2}, + emath::{pos2, vec2, Align2, NumExt as _, Pos2, Rect, Vec2}, Align, }; const INFINITY: f32 = f32::INFINITY; diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 26950eb2a..6e30d4ca5 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -64,7 +64,7 @@ use std::{ use ahash::HashMap; -use emath::{Float, OrderedFloat}; +use emath::{Float as _, OrderedFloat}; use epaint::{mutex::Mutex, textures::TextureOptions, ColorImage, TextureHandle, TextureId, Vec2}; use crate::Context; diff --git a/crates/egui/src/load/bytes_loader.rs b/crates/egui/src/load/bytes_loader.rs index 9c70f54e1..5f3e0d3bc 100644 --- a/crates/egui/src/load/bytes_loader.rs +++ b/crates/egui/src/load/bytes_loader.rs @@ -28,7 +28,7 @@ impl DefaultBytesLoader { } impl BytesLoader for DefaultBytesLoader { - fn id(&self) -> &str { + fn id(&self) -> &'static str { generate_loader_id!(DefaultBytesLoader) } diff --git a/crates/egui/src/load/texture_loader.rs b/crates/egui/src/load/texture_loader.rs index a1170257c..4e9dc1e16 100644 --- a/crates/egui/src/load/texture_loader.rs +++ b/crates/egui/src/load/texture_loader.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use super::{ - BytesLoader, Context, HashMap, ImagePoll, Mutex, SizeHint, SizedTexture, TextureHandle, + BytesLoader as _, Context, HashMap, ImagePoll, Mutex, SizeHint, SizedTexture, TextureHandle, TextureLoadResult, TextureLoader, TextureOptions, TexturePoll, }; @@ -11,7 +11,7 @@ pub struct DefaultTextureLoader { } impl TextureLoader for DefaultTextureLoader { - fn id(&self) -> &str { + fn id(&self) -> &'static str { crate::generate_loader_id!(DefaultTextureLoader) } diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index ef84451db..8de1fe951 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -23,8 +23,8 @@ use super::{ use crate::{ epaint, vec2, widgets::{Button, ImageButton}, - Align2, Area, Color32, Frame, Key, LayerId, Layout, NumExt, Order, Stroke, Style, TextWrapMode, - UiKind, WidgetText, + Align2, Area, Color32, Frame, Key, LayerId, Layout, NumExt as _, Order, Stroke, Style, + TextWrapMode, UiKind, WidgetText, }; use epaint::mutex::RwLock; use std::sync::Arc; diff --git a/crates/egui/src/os.rs b/crates/egui/src/os.rs index 766ecf55a..e7497160b 100644 --- a/crates/egui/src/os.rs +++ b/crates/egui/src/os.rs @@ -1,5 +1,5 @@ /// An `enum` of common operating systems. -#[allow(clippy::upper_case_acronyms)] // `Ios` looks too ugly +#[expect(clippy::upper_case_acronyms)] // `Ios` looks too ugly #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum OperatingSystem { /// Unknown OS - could be wasm diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 22a0a0a9d..732d78ee8 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -294,7 +294,7 @@ impl Painter { /// ## Debug painting impl Painter { - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn debug_rect(&self, rect: Rect, color: Color32, text: impl ToString) { self.rect( rect, @@ -320,7 +320,7 @@ impl Painter { /// Text with a background. /// /// See also [`Context::debug_text`]. - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn debug_text( &self, pos: Pos2, @@ -497,7 +497,7 @@ impl Painter { /// [`Self::layout`] or [`Self::layout_no_wrap`]. /// /// Returns where the text ended up. - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn text( &self, pos: Pos2, diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index 49d6c7587..1f629253c 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -3,7 +3,7 @@ use ahash::HashMap; use crate::{id::IdSet, style, Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects}; #[cfg(debug_assertions)] -use crate::{pos2, Align2, Color32, FontId, NumExt, Painter}; +use crate::{pos2, Align2, Color32, FontId, NumExt as _, Painter}; /// Reset at the start of each frame. #[derive(Clone, Debug, Default)] diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 38d403749..7b73edbb9 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -747,7 +747,8 @@ impl ScrollStyle { // ---------------------------------------------------------------------------- -/// Scroll animation configuration, used when programmatically scrolling somewhere (e.g. with `[crate::Ui::scroll_to_cursor]`) +/// Scroll animation configuration, used when programmatically scrolling somewhere (e.g. with `[crate::Ui::scroll_to_cursor]`). +/// /// The animation duration is calculated based on the distance to be scrolled via `[ScrollAnimation::points_per_second]` /// and can be clamped to a min / max duration via `[ScrollAnimation::duration]`. #[derive(Copy, Clone, Debug, PartialEq)] @@ -1260,7 +1261,7 @@ pub fn default_text_styles() -> BTreeMap { impl Default for Style { fn default() -> Self { - #[allow(deprecated)] + #[expect(deprecated)] Self { override_font_id: None, override_text_style: None, @@ -1562,7 +1563,7 @@ use crate::{ impl Style { pub fn ui(&mut self, ui: &mut crate::Ui) { - #[allow(deprecated)] + #[expect(deprecated)] let Self { override_font_id, override_text_style, diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index e24992a05..9ce8fbd5b 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -616,7 +616,7 @@ impl LabelSelectionState { let old_primary = old_selection.map(|s| s.primary); let new_primary = self.selection.as_ref().map(|s| s.primary); if let Some(new_primary) = new_primary { - let primary_changed = old_primary.map_or(true, |old| { + let primary_changed = old_primary.is_none_or(|old| { old.widget_id != new_primary.widget_id || old.ccursor != new_primary.ccursor }); if primary_changed && new_primary.widget_id == widget_id { diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index aaa3beb9b..298d8abfb 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 unicode_segmentation::UnicodeSegmentation; +use unicode_segmentation::UnicodeSegmentation as _; -use crate::{epaint, NumExt, Rect, Response, Ui}; +use crate::{epaint, NumExt as _, Rect, Response, Ui}; use super::CCursorRange; diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 0e8509bb8..4174ca961 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -98,7 +98,7 @@ pub struct Ui { sizing_pass: bool, /// Indicates whether this Ui belongs to a Menu. - #[allow(deprecated)] + #[expect(deprecated)] menu_state: Option>>, /// The [`UiStack`] for this [`Ui`]. @@ -666,7 +666,7 @@ impl Ui { /// /// This is determined first by [`Style::wrap_mode`], and then by the layout of this [`Ui`]. pub fn wrap_mode(&self) -> TextWrapMode { - #[allow(deprecated)] + #[expect(deprecated)] if let Some(wrap_mode) = self.style.wrap_mode { wrap_mode } @@ -3015,7 +3015,7 @@ impl Ui { self.close_kind(UiKind::Menu); } - #[allow(deprecated)] + #[expect(deprecated)] pub(crate) fn set_menu_state( &mut self, menu_state: Option>>, @@ -3156,7 +3156,7 @@ impl Drop for Ui { /// Show this rectangle to the user if certain debug options are set. #[cfg(debug_assertions)] fn register_rect(ui: &Ui, rect: Rect) { - use emath::{Align2, GuiRounding}; + use emath::{Align2, GuiRounding as _}; let debug = ui.style().debug; diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index 4d225ae4c..4aade7d64 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -1,7 +1,7 @@ use std::{hash::Hash, sync::Arc}; use crate::close_tag::ClosableTag; -#[allow(unused_imports)] // Used for doclinks +#[expect(unused_imports)] // Used for doclinks use crate::Ui; use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo}; diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 64a776ee6..0122f5681 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -258,7 +258,7 @@ impl UiStack { // these methods act on the entire stack impl UiStack { /// Return an iterator that walks the stack from this node to the root. - #[allow(clippy::iter_without_into_iter)] + #[expect(clippy::iter_without_into_iter)] pub fn iter(&self) -> UiStackIterator<'_> { UiStackIterator { next: Some(self) } } diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index a7b8f18c9..bd4d14efd 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -467,7 +467,7 @@ impl IdTypeMap { /// For tests #[cfg(feature = "persistence")] - #[allow(unused)] + #[allow(unused, clippy::allow_attributes)] fn get_generation(&self, id: Id) -> Option { let element = self.map.get(&hash(TypeId::of::(), id))?; match element { diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 9aba69910..2a469435e 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -260,7 +260,6 @@ pub type ImmediateViewportRendererCallback = dyn for<'a> Fn(&Context, ImmediateV /// The default values are implementation defined, so you may want to explicitly /// configure the size of the window, and what buttons are shown. #[derive(Clone, Debug, Default, Eq, PartialEq)] -#[allow(clippy::option_option)] pub struct ViewportBuilder { /// The title of the viewport. /// `eframe` will use this as the title of the native window. diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index c0701c193..2a85a164d 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -1,6 +1,6 @@ use crate::{ - widgets, Align, Color32, CornerRadius, FontSelection, Image, NumExt, Rect, Response, Sense, - Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, + widgets, Align, Color32, CornerRadius, FontSelection, Image, NumExt as _, Rect, Response, + Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, }; /// Clickable button with text. @@ -46,13 +46,11 @@ impl<'a> Button<'a> { } /// Creates a button with an image. The size of the image as displayed is defined by the provided size. - #[allow(clippy::needless_pass_by_value)] pub fn image(image: impl Into>) -> Self { Self::opt_image_and_text(Some(image.into()), None) } /// Creates a button with an image to the left of the text. The size of the image as displayed is defined by the provided size. - #[allow(clippy::needless_pass_by_value)] pub fn image_and_text(image: impl Into>, text: impl Into) -> Self { Self::opt_image_and_text(Some(image.into()), Some(text.into())) } diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs index 7bdb6c86f..97bd97b88 100644 --- a/crates/egui/src/widgets/checkbox.rs +++ b/crates/egui/src/widgets/checkbox.rs @@ -1,6 +1,6 @@ use crate::{ - epaint, pos2, vec2, NumExt, Response, Sense, Shape, TextStyle, Ui, Vec2, Widget, WidgetInfo, - WidgetText, WidgetType, + epaint, pos2, vec2, NumExt as _, Response, Sense, Shape, TextStyle, Ui, Vec2, Widget, + WidgetInfo, WidgetText, WidgetType, }; // 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 f8e186658..07678d458 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -3,7 +3,7 @@ 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, WidgetInfo, WidgetType, + Sense, Ui, UiKind, Widget as _, WidgetInfo, WidgetType, }; use epaint::{ ecolor::{Color32, Hsva, HsvaGamma, Rgba}, diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index f0f35c1e9..864222ae1 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, Key, Modifiers, NumExt, Response, RichText, Sense, TextEdit, - TextWrapMode, Ui, Widget, WidgetInfo, MINUS_CHAR_STR, + emath, text, Button, CursorIcon, Key, Modifiers, NumExt as _, Response, RichText, Sense, + TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, MINUS_CHAR_STR, }; // ---------------------------------------------------------------------------- @@ -550,7 +550,7 @@ impl Widget for DragValue<'_> { } // some clones below are redundant if AccessKit is disabled - #[allow(clippy::redundant_clone)] + #[expect(clippy::redundant_clone)] let mut response = if is_kb_editing { let mut value_text = ui .data_mut(|data| data.remove_temp::(id)) diff --git a/crates/egui/src/widgets/hyperlink.rs b/crates/egui/src/widgets/hyperlink.rs index 7d5129b14..4896be410 100644 --- a/crates/egui/src/widgets/hyperlink.rs +++ b/crates/egui/src/widgets/hyperlink.rs @@ -96,7 +96,7 @@ pub struct Hyperlink { } impl Hyperlink { - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn new(url: impl ToString) -> Self { let url = url.to_string(); Self { @@ -106,7 +106,7 @@ impl Hyperlink { } } - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn from_label_and_url(text: impl Into, url: impl ToString) -> Self { Self { url: url.to_string(), diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index 44c9b8971..6739c0e2e 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, Pos2, Rect, Response, Rgba, Sense, Shape, Stroke, - TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, + lerp, vec2, Color32, CornerRadius, NumExt as _, Pos2, Rect, Response, Rgba, Sense, Shape, + Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, }; enum ProgressBarText { diff --git a/crates/egui/src/widgets/radio_button.rs b/crates/egui/src/widgets/radio_button.rs index fabf565b5..7c178840d 100644 --- a/crates/egui/src/widgets/radio_button.rs +++ b/crates/egui/src/widgets/radio_button.rs @@ -1,5 +1,5 @@ use crate::{ - epaint, pos2, vec2, NumExt, Response, Sense, TextStyle, Ui, Vec2, Widget, WidgetInfo, + epaint, pos2, vec2, NumExt as _, Response, Sense, TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, }; diff --git a/crates/egui/src/widgets/selected_label.rs b/crates/egui/src/widgets/selected_label.rs index dfed4d2ba..4b2ee9ae2 100644 --- a/crates/egui/src/widgets/selected_label.rs +++ b/crates/egui/src/widgets/selected_label.rs @@ -1,4 +1,6 @@ -use crate::{NumExt, Response, Sense, TextStyle, Ui, Widget, WidgetInfo, WidgetText, WidgetType}; +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. diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 0f7eb0495..017270fc7 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -4,8 +4,8 @@ use std::ops::RangeInclusive; use crate::{ emath, epaint, lerp, pos2, remap, remap_clamp, style, style::HandleShape, vec2, Color32, - DragValue, EventFilter, Key, Label, NumExt, Pos2, Rangef, Rect, Response, Sense, TextStyle, - TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, MINUS_CHAR_STR, + DragValue, EventFilter, Key, Label, NumExt as _, Pos2, Rangef, Rect, Response, Sense, + TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, MINUS_CHAR_STR, }; use super::drag_value::clamp_value_to_range; diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 6a4244496..ca201edda 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -13,8 +13,8 @@ use crate::{ 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, Response, Sense, Shape, TextBuffer, - TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetWithState, + ImeEvent, Key, KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, Shape, + TextBuffer, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetWithState, }; use super::{TextEditOutput, TextEditState}; @@ -878,7 +878,7 @@ fn mask_if_password(is_password: bool, text: &str) -> String { // ---------------------------------------------------------------------------- /// Check for (keyboard) events to edit the cursor and/or text. -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn events( ui: &crate::Ui, state: &mut TextEditState, diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index 0734811bd..0051ea8e7 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -72,7 +72,7 @@ impl TextEditState { self.undoer.lock().clone() } - #[allow(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability + #[expect(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability pub fn set_undoer(&mut self, undoer: TextEditUndoer) { *self.undoer.lock() = undoer; } diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index ad6bfc3bc..b3ff74b12 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -80,7 +80,7 @@ struct RotatingTriangle { vertex_array: glow::VertexArray, } -#[allow(unsafe_code)] // we need unsafe code to use glow +#[expect(unsafe_code)] // we need unsafe code to use glow impl RotatingTriangle { fn new(gl: &glow::Context) -> Option { use glow::HasContext as _; diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 2e3a34f7d..d3b10d480 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -1,7 +1,7 @@ use std::num::NonZeroU64; use eframe::{ - egui_wgpu::wgpu::util::DeviceExt, + egui_wgpu::wgpu::util::DeviceExt as _, egui_wgpu::{self, wgpu}, }; diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index e0a29f14c..289d9b6e2 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -110,7 +110,7 @@ impl BackendPanel { if cfg!(debug_assertions) && cfg!(target_arch = "wasm32") { ui.separator(); // For testing panic handling on web: - #[allow(clippy::manual_assert)] + #[expect(clippy::manual_assert)] if ui.button("panic!()").clicked() { panic!("intentional panic!"); } diff --git a/crates/egui_demo_app/src/lib.rs b/crates/egui_demo_app/src/lib.rs index 0eb34486d..9cfa26baa 100644 --- a/crates/egui_demo_app/src/lib.rs +++ b/crates/egui_demo_app/src/lib.rs @@ -10,7 +10,7 @@ pub use wrap_app::{Anchor, WrapApp}; /// Time of day as seconds since midnight. Used for clock in demo app. pub(crate) fn seconds_since_midnight() -> f64 { - use chrono::Timelike; + use chrono::Timelike as _; let time = chrono::Local::now().time(); time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64) } diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 6a2bb2da7..476ffdacd 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -75,7 +75,7 @@ fn start_puffin_server() { // We can store the server if we want, but in this case we just want // it to keep running. Dropping it closes the server, so let's not drop it! - #[allow(clippy::mem_forget)] + #[expect(clippy::mem_forget)] std::mem::forget(puffin_server); } Err(err) => { diff --git a/crates/egui_demo_app/src/web.rs b/crates/egui_demo_app/src/web.rs index 8c9cdd1d4..104ef1dce 100644 --- a/crates/egui_demo_app/src/web.rs +++ b/crates/egui_demo_app/src/web.rs @@ -14,7 +14,7 @@ pub struct WebHandle { #[wasm_bindgen] impl WebHandle { /// Installs a panic hook, then returns. - #[allow(clippy::new_without_default)] + #[allow(clippy::new_without_default, clippy::allow_attributes)] #[wasm_bindgen(constructor)] pub fn new() -> Self { // Redirect [`log`] message to `console.log` and friends: diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index a571cfbb2..ea5fbfaba 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -188,7 +188,7 @@ impl WrapApp { // This gives us image support: egui_extras::install_image_loaders(&cc.egui_ctx); - #[allow(unused_mut)] + #[allow(unused_mut, clippy::allow_attributes)] let mut slf = Self { state: State::default(), diff --git a/crates/egui_demo_app/tests/test_demo_app.rs b/crates/egui_demo_app/tests/test_demo_app.rs index 056bee49e..8b0b272fa 100644 --- a/crates/egui_demo_app/tests/test_demo_app.rs +++ b/crates/egui_demo_app/tests/test_demo_app.rs @@ -1,7 +1,7 @@ use egui::accesskit::Role; use egui::Vec2; use egui_demo_app::{Anchor, WrapApp}; -use egui_kittest::kittest::Queryable; +use egui_kittest::kittest::Queryable as _; use egui_kittest::SnapshotResults; #[test] diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 9094c19a7..89a8cdafa 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -105,7 +105,7 @@ impl crate::Demo for CodeExample { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - use crate::View; + use crate::View as _; egui::Window::new(self.name()) .open(open) .min_width(375.0) 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 ce18ac927..7b8972786 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; use super::About; use crate::is_mobile; use crate::Demo; -use crate::View; +use crate::View as _; use egui::containers::menu; use egui::style::StyleModifier; use egui::{Context, Modifiers, ScrollArea, Ui}; @@ -359,9 +359,9 @@ fn file_menu_button(ui: &mut Ui) { #[cfg(test)] mod tests { - use crate::{demo::demo_app_windows::DemoGroups, Demo}; + use crate::{demo::demo_app_windows::DemoGroups, Demo as _}; use egui::Vec2; - use egui_kittest::kittest::Queryable; + use egui_kittest::kittest::Queryable as _; use egui_kittest::{Harness, SnapshotOptions, SnapshotResults}; #[test] diff --git a/crates/egui_demo_lib/src/demo/font_book.rs b/crates/egui_demo_lib/src/demo/font_book.rs index 605b7ed7f..1ef9867f9 100644 --- a/crates/egui_demo_lib/src/demo/font_book.rs +++ b/crates/egui_demo_lib/src/demo/font_book.rs @@ -169,7 +169,7 @@ fn char_name(chr: char) -> String { } fn special_char_name(chr: char) -> Option<&'static str> { - #[allow(clippy::match_same_arms)] // many "flag" + #[expect(clippy::match_same_arms)] // many "flag" match chr { // Special private-use-area extensions found in `emoji-icon-font.ttf`: // Private use area extensions: diff --git a/crates/egui_demo_lib/src/demo/interactive_container.rs b/crates/egui_demo_lib/src/demo/interactive_container.rs index 11d8afa48..5faeb8458 100644 --- a/crates/egui_demo_lib/src/demo/interactive_container.rs +++ b/crates/egui_demo_lib/src/demo/interactive_container.rs @@ -1,4 +1,4 @@ -use egui::{Frame, Label, RichText, Sense, UiBuilder, Widget}; +use egui::{Frame, Label, RichText, Sense, UiBuilder, Widget as _}; /// Showcase [`egui::Ui::response`]. #[derive(PartialEq, Eq, Default)] diff --git a/crates/egui_demo_lib/src/demo/modals.rs b/crates/egui_demo_lib/src/demo/modals.rs index f83820e74..fcb33f0bb 100644 --- a/crates/egui_demo_lib/src/demo/modals.rs +++ b/crates/egui_demo_lib/src/demo/modals.rs @@ -1,4 +1,4 @@ -use egui::{ComboBox, Context, Id, Modal, ProgressBar, Ui, Widget, Window}; +use egui::{ComboBox, Context, Id, Modal, ProgressBar, Ui, Widget as _, Window}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] @@ -163,10 +163,10 @@ impl crate::View for Modals { #[cfg(test)] mod tests { use crate::demo::modals::Modals; - use crate::Demo; + use crate::Demo as _; use egui::accesskit::Role; use egui::Key; - use egui_kittest::kittest::Queryable; + use egui_kittest::kittest::Queryable as _; use egui_kittest::{Harness, SnapshotResults}; #[test] diff --git a/crates/egui_demo_lib/src/demo/paint_bezier.rs b/crates/egui_demo_lib/src/demo/paint_bezier.rs index 08e29a9ff..df85e4377 100644 --- a/crates/egui_demo_lib/src/demo/paint_bezier.rs +++ b/crates/egui_demo_lib/src/demo/paint_bezier.rs @@ -2,7 +2,7 @@ use egui::{ emath, epaint::{self, CubicBezierShape, PathShape, QuadraticBezierShape}, pos2, Color32, Context, Frame, Grid, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, Ui, Vec2, - Widget, Window, + Widget as _, Window, }; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui_demo_lib/src/demo/password.rs b/crates/egui_demo_lib/src/demo/password.rs index 528f5a515..88a2bb706 100644 --- a/crates/egui_demo_lib/src/demo/password.rs +++ b/crates/egui_demo_lib/src/demo/password.rs @@ -8,7 +8,6 @@ /// ``` ignore /// password_ui(ui, &mut my_password); /// ``` -#[allow(clippy::ptr_arg)] // false positive pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response { // This widget has its own state — show or hide password characters (`show_plaintext`). // In this case we use a simple `bool`, but you can also declare your own type. diff --git a/crates/egui_demo_lib/src/demo/screenshot.rs b/crates/egui_demo_lib/src/demo/screenshot.rs index eb62611c8..c2367914b 100644 --- a/crates/egui_demo_lib/src/demo/screenshot.rs +++ b/crates/egui_demo_lib/src/demo/screenshot.rs @@ -1,4 +1,4 @@ -use egui::{Image, UserData, ViewportCommand, Widget}; +use egui::{Image, UserData, ViewportCommand, Widget as _}; use std::sync::Arc; /// Showcase [`ViewportCommand::Screenshot`]. diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 7a2ca4d7c..dab3c4f5a 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, Rect, - ScrollArea, Sense, Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget, + pos2, scroll_area::ScrollBarVisibility, Align, Align2, Color32, DragValue, NumExt as _, Rect, + ScrollArea, Sense, Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget as _, }; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 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 031f3e2ce..20e8d21bf 100644 --- a/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs @@ -1,5 +1,5 @@ use egui::{ - emath::{GuiRounding, TSTransform}, + emath::{GuiRounding as _, TSTransform}, epaint::{self, RectShape}, vec2, Color32, Pos2, Rect, Sense, StrokeKind, Vec2, }; diff --git a/crates/egui_demo_lib/src/demo/text_edit.rs b/crates/egui_demo_lib/src/demo/text_edit.rs index 06911957f..685a9c38f 100644 --- a/crates/egui_demo_lib/src/demo/text_edit.rs +++ b/crates/egui_demo_lib/src/demo/text_edit.rs @@ -114,7 +114,7 @@ impl crate::View for TextEditDemo { #[cfg(test)] mod tests { use egui::{accesskit, CentralPanel}; - use egui_kittest::kittest::{Key, Queryable}; + use egui_kittest::kittest::{Key, Queryable as _}; use egui_kittest::Harness; #[test] diff --git a/crates/egui_demo_lib/src/demo/toggle_switch.rs b/crates/egui_demo_lib/src/demo/toggle_switch.rs index 9619bd2c0..623228cbd 100644 --- a/crates/egui_demo_lib/src/demo/toggle_switch.rs +++ b/crates/egui_demo_lib/src/demo/toggle_switch.rs @@ -76,7 +76,7 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { } /// Here is the same code again, but a bit more compact: -#[allow(dead_code)] +#[expect(dead_code)] fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0); let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 6005d6aec..c7b6df28a 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -48,7 +48,7 @@ impl Default for WidgetGallery { } impl WidgetGallery { - #[allow(unused_mut)] // if not chrono + #[allow(unused_mut, clippy::allow_attributes)] // if not chrono #[inline] pub fn with_date_button(mut self, _with_date_button: bool) -> Self { #[cfg(feature = "chrono")] @@ -308,7 +308,7 @@ fn doc_link_label_with_crate<'a>( #[cfg(test)] mod tests { use super::*; - use crate::View; + use crate::View as _; use egui::Vec2; use egui_kittest::Harness; diff --git a/crates/egui_extras/src/datepicker/mod.rs b/crates/egui_extras/src/datepicker/mod.rs index 33038d763..e7ba47e74 100644 --- a/crates/egui_extras/src/datepicker/mod.rs +++ b/crates/egui_extras/src/datepicker/mod.rs @@ -2,7 +2,7 @@ mod button; mod popup; pub use button::DatePickerButton; -use chrono::{Datelike, Duration, NaiveDate, Weekday}; +use chrono::{Datelike as _, Duration, NaiveDate, Weekday}; #[derive(Debug)] struct Week { diff --git a/crates/egui_extras/src/datepicker/popup.rs b/crates/egui_extras/src/datepicker/popup.rs index 8bf2f1a12..79f3d37f6 100644 --- a/crates/egui_extras/src/datepicker/popup.rs +++ b/crates/egui_extras/src/datepicker/popup.rs @@ -1,4 +1,4 @@ -use chrono::{Datelike, NaiveDate, Weekday}; +use chrono::{Datelike as _, NaiveDate, Weekday}; use egui::{Align, Button, Color32, ComboBox, Direction, Id, Layout, RichText, Ui, Vec2}; diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index fb62cec40..b1c79d0a6 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -1,4 +1,4 @@ -use egui::{emath::GuiRounding, Id, Pos2, Rect, Response, Sense, Ui, UiBuilder}; +use egui::{emath::GuiRounding as _, Id, Pos2, Rect, Response, Sense, Ui, UiBuilder}; #[derive(Clone, Copy)] pub(crate) enum CellSize { diff --git a/crates/egui_extras/src/lib.rs b/crates/egui_extras/src/lib.rs index 6f3390102..1a58402f5 100644 --- a/crates/egui_extras/src/lib.rs +++ b/crates/egui_extras/src/lib.rs @@ -26,7 +26,7 @@ mod table; pub use crate::datepicker::DatePickerButton; #[doc(hidden)] -#[allow(deprecated)] +#[expect(deprecated)] pub use crate::image::RetainedImage; pub(crate) use crate::layout::StripLayout; pub use crate::sizing::Size; diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 87ca4db06..bb025651d 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -77,7 +77,7 @@ impl ImageLoader for ImageCrateLoader { } #[cfg(not(target_arch = "wasm32"))] - #[allow(clippy::unnecessary_wraps)] // needed here to match other return types + #[expect(clippy::unnecessary_wraps)] // needed here to match other return types fn load_image( ctx: &egui::Context, uri: &str, diff --git a/crates/egui_extras/src/loaders/svg_loader.rs b/crates/egui_extras/src/loaders/svg_loader.rs index 4c33281fe..63cfd4a96 100644 --- a/crates/egui_extras/src/loaders/svg_loader.rs +++ b/crates/egui_extras/src/loaders/svg_loader.rs @@ -30,7 +30,7 @@ fn is_supported(uri: &str) -> bool { impl Default for SvgLoader { fn default() -> Self { // opt is mutated when `svg_text` feature flag is enabled - #[allow(unused_mut)] + #[allow(unused_mut, clippy::allow_attributes)] let mut options = resvg::usvg::Options::default(); #[cfg(feature = "svg_text")] diff --git a/crates/egui_extras/src/loaders/webp_loader.rs b/crates/egui_extras/src/loaders/webp_loader.rs index 528c449a7..ef1a5d527 100644 --- a/crates/egui_extras/src/loaders/webp_loader.rs +++ b/crates/egui_extras/src/loaders/webp_loader.rs @@ -5,7 +5,7 @@ use egui::{ mutex::Mutex, ColorImage, FrameDurations, Id, }; -use image::{codecs::webp::WebPDecoder, AnimationDecoder as _, ColorType, ImageDecoder, Rgba}; +use image::{codecs::webp::WebPDecoder, AnimationDecoder as _, ColorType, ImageDecoder as _, Rgba}; use std::{io::Cursor, mem::size_of, sync::Arc, time::Duration}; #[derive(Clone)] diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index ac51c673c..6bb51184f 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -33,7 +33,7 @@ pub fn highlight( // performing it at a separate thread (ctx, ctx.style()) can be used and when ui is available // (ui.ctx(), ui.style()) can be used - #[allow(non_local_definitions)] + #[expect(non_local_definitions)] impl egui::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> for Highlighter { fn compute( &mut self, @@ -285,7 +285,7 @@ impl CodeTheme { impl CodeTheme { // The syntect version takes it by value. This could be avoided by specializing the from_style // function, but at the cost of more code duplication. - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] fn dark_with_font_id(font_id: egui::FontId) -> Self { use egui::{Color32, TextFormat}; Self { @@ -302,7 +302,7 @@ impl CodeTheme { } // The syntect version takes it by value - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] fn light_with_font_id(font_id: egui::FontId) -> Self { use egui::{Color32, TextFormat}; Self { @@ -413,7 +413,6 @@ impl Default for Highlighter { } impl Highlighter { - #[allow(clippy::unused_self, clippy::unnecessary_wraps)] fn highlight( &self, font_id: egui::FontId, @@ -512,7 +511,7 @@ struct Highlighter {} #[cfg(not(feature = "syntect"))] impl Highlighter { - #[allow(clippy::unused_self, clippy::unnecessary_wraps)] + #[expect(clippy::unused_self, clippy::unnecessary_wraps)] fn highlight_impl( &self, theme: &CodeTheme, diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index d151be377..7f38700cd 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -9,7 +9,7 @@ use std::num::NonZeroU32; use std::sync::Arc; use egui_winit::winit; -use winit::raw_window_handle::HasWindowHandle; +use winit::raw_window_handle::HasWindowHandle as _; /// The majority of `GlutinWindowContext` is taken from `eframe` struct GlutinWindowContext { @@ -22,12 +22,12 @@ struct GlutinWindowContext { impl GlutinWindowContext { // refactor this function to use `glutin-winit` crate eventually. // preferably add android support at the same time. - #[allow(unsafe_code)] + #[expect(unsafe_code)] unsafe fn new(event_loop: &winit::event_loop::ActiveEventLoop) -> Self { - use glutin::context::NotCurrentGlContext; - use glutin::display::GetGlDisplay; - use glutin::display::GlDisplay; - use glutin::prelude::GlSurface; + use glutin::context::NotCurrentGlContext as _; + use glutin::display::GetGlDisplay as _; + use glutin::display::GlDisplay as _; + use glutin::prelude::GlSurface as _; let winit_window_builder = winit::window::WindowAttributes::default() .with_resizable(true) .with_inner_size(winit::dpi::LogicalSize { @@ -138,7 +138,7 @@ impl GlutinWindowContext { } fn resize(&self, physical_size: winit::dpi::PhysicalSize) { - use glutin::surface::GlSurface; + use glutin::surface::GlSurface as _; self.gl_surface.resize( &self.gl_context, physical_size.width.try_into().unwrap(), @@ -147,12 +147,12 @@ impl GlutinWindowContext { } fn swap_buffers(&self) -> glutin::error::Result<()> { - use glutin::surface::GlSurface; + use glutin::surface::GlSurface as _; self.gl_surface.swap_buffers(&self.gl_context) } fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void { - use glutin::display::GlDisplay; + use glutin::display::GlDisplay as _; self.gl_display.get_proc_address(addr) } } diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index 430f1287e..aaea1d0b9 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -73,7 +73,7 @@ macro_rules! check_for_gl_error_even_in_release { #[doc(hidden)] pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, context: &str) { use glow::HasContext as _; - #[allow(unsafe_code)] + #[expect(unsafe_code)] let error_code = unsafe { gl.get_error() }; if error_code != glow::NO_ERROR { let error_str = match error_code { diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 1d1322c42..939246e64 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -296,7 +296,7 @@ impl Painter { /// So if in a [`egui::Shape::Callback`] you need to use an offscreen FBO, you should /// then restore to this afterwards with /// `gl.bind_framebuffer(glow::FRAMEBUFFER, painter.intermediate_fbo());` - #[allow(clippy::unused_self)] + #[expect(clippy::unused_self)] pub fn intermediate_fbo(&self) -> Option { // We don't currently ever render to an offscreen buffer, // but we may want to start to in order to do anti-aliasing on web, for instance. @@ -663,7 +663,6 @@ impl Painter { self.textures.get(&texture_id).copied() } - #[allow(clippy::needless_pass_by_value)] // False positive pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId { self.assert_not_destroyed(); let id = egui::TextureId::User(self.next_native_tex_id); @@ -672,7 +671,6 @@ impl Painter { id } - #[allow(clippy::needless_pass_by_value)] // False positive pub fn replace_native_texture(&mut self, id: egui::TextureId, replacing: glow::Texture) { if let Some(old_tex) = self.textures.insert(id, replacing) { self.textures_to_destroy.push(old_tex); diff --git a/crates/egui_glow/src/shader_version.rs b/crates/egui_glow/src/shader_version.rs index 77c1e63ce..249cda369 100644 --- a/crates/egui_glow/src/shader_version.rs +++ b/crates/egui_glow/src/shader_version.rs @@ -1,11 +1,10 @@ #![allow(unsafe_code)] #![allow(clippy::undocumented_unsafe_blocks)] -use std::convert::TryInto; +use std::convert::TryInto as _; /// Helper for parsing and interpreting the OpenGL shader version. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[allow(dead_code)] pub enum ShaderVersion { Gl120, diff --git a/crates/egui_glow/src/vao.rs b/crates/egui_glow/src/vao.rs index 6759a829d..febe67fdd 100644 --- a/crates/egui_glow/src/vao.rs +++ b/crates/egui_glow/src/vao.rs @@ -27,7 +27,6 @@ pub(crate) struct VertexArrayObject { } impl VertexArrayObject { - #[allow(clippy::needless_pass_by_value)] // false positive pub(crate) unsafe fn new( gl: &glow::Context, vbo: glow::Buffer, diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 86ed053d2..acd260a4a 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -339,6 +339,7 @@ pub fn image_snapshot_options(current: &image::RgbaImage, name: &str, options: & } /// Image snapshot test. +/// /// The snapshot will be saved under `tests/snapshots/{name}.png`. /// The new image from the last test run will be saved under `tests/snapshots/{name}.new.png`. /// If the new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`. @@ -451,7 +452,7 @@ impl Harness<'_, State> { // Deprecated wgpu_snapshot functions // TODO(lucasmerlin): Remove in 0.32 -#[allow(clippy::missing_errors_doc)] +#[expect(clippy::missing_errors_doc)] #[cfg(feature = "wgpu")] impl Harness<'_, State> { #[deprecated( @@ -552,7 +553,7 @@ impl SnapshotResults { } /// Convert this into a `Result<(), Self>`. - #[allow(clippy::missing_errors_doc)] + #[expect(clippy::missing_errors_doc)] pub fn into_result(self) -> Result<(), Self> { if self.has_errors() { Err(self) @@ -566,7 +567,7 @@ impl SnapshotResults { } /// Panics if there are any errors, displaying each. - #[allow(clippy::unused_self)] + #[expect(clippy::unused_self)] #[track_caller] pub fn unwrap(self) { // Panic is handled in drop @@ -586,7 +587,7 @@ impl Drop for SnapshotResults { if std::thread::panicking() { return; } - #[allow(clippy::manual_assert)] + #[expect(clippy::manual_assert)] if self.has_errors() { panic!("{}", self); } diff --git a/crates/egui_kittest/tests/menu.rs b/crates/egui_kittest/tests/menu.rs index 4c6d6fd5c..a01e39dca 100644 --- a/crates/egui_kittest/tests/menu.rs +++ b/crates/egui_kittest/tests/menu.rs @@ -1,7 +1,7 @@ use egui::containers::menu::{Bar, MenuConfig, SubMenuButton}; use egui::{include_image, PopupCloseBehavior, Ui}; use egui_kittest::{Harness, SnapshotResults}; -use kittest::Queryable; +use kittest::Queryable as _; struct TestMenu { config: MenuConfig, diff --git a/crates/egui_kittest/tests/popup.rs b/crates/egui_kittest/tests/popup.rs index f55bf6388..368e8de7e 100644 --- a/crates/egui_kittest/tests/popup.rs +++ b/crates/egui_kittest/tests/popup.rs @@ -1,4 +1,4 @@ -use kittest::Queryable; +use kittest::Queryable as _; #[test] fn test_interactive_tooltip() { diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index bd3f7926e..50ed1095a 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -1,6 +1,6 @@ use egui::accesskit::Role; -use egui::{Button, ComboBox, Image, Vec2, Widget}; -use egui_kittest::{kittest::Queryable, Harness, SnapshotResults}; +use egui::{Button, ComboBox, Image, Vec2, Widget as _}; +use egui_kittest::{kittest::Queryable as _, Harness, SnapshotResults}; #[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 52f455c7b..9bf07063b 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -1,6 +1,6 @@ use egui::Modifiers; use egui_kittest::Harness; -use kittest::{Key, Queryable}; +use kittest::{Key, Queryable as _}; #[test] fn test_shrink() { diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index ad657fcc3..337fa2045 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -248,7 +248,7 @@ pub fn almost_equal(a: f32, b: f32, epsilon: f32) -> bool { } } -#[allow(clippy::approx_constant)] +#[expect(clippy::approx_constant)] #[test] fn test_format() { assert_eq!(format_with_minimum_decimals(1_234_567.0, 0), "1234567"); diff --git a/crates/emath/src/numeric.rs b/crates/emath/src/numeric.rs index 9a7814b23..1fbddbc66 100644 --- a/crates/emath/src/numeric.rs +++ b/crates/emath/src/numeric.rs @@ -23,7 +23,7 @@ macro_rules! impl_numeric_float { #[inline(always)] fn to_f64(self) -> f64 { - #[allow(trivial_numeric_casts)] + #[allow(trivial_numeric_casts, clippy::allow_attributes)] { self as f64 } @@ -31,7 +31,7 @@ macro_rules! impl_numeric_float { #[inline(always)] fn from_f64(num: f64) -> Self { - #[allow(trivial_numeric_casts)] + #[allow(trivial_numeric_casts, clippy::allow_attributes)] { num as Self } diff --git a/crates/emath/src/ordered_float.rs b/crates/emath/src/ordered_float.rs index 0b8014d90..fa80a498f 100644 --- a/crates/emath/src/ordered_float.rs +++ b/crates/emath/src/ordered_float.rs @@ -101,7 +101,7 @@ impl Float for f64 { // Keep this trait in private module, to avoid exposing its methods as extensions in user code mod private { - use super::{Hash, Hasher}; + use super::{Hash as _, Hasher}; pub trait FloatImpl { fn is_nan(&self) -> bool; diff --git a/crates/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index 9ef010d9a..e75ae42ce 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -115,7 +115,7 @@ fn simplest_digit_closed_range(min: i32, max: i32) -> i32 { } } -#[allow(clippy::approx_constant)] +#[expect(clippy::approx_constant)] #[test] fn test_aim() { assert_eq!(best_in_range_f64(-0.2, 0.0), 0.0, "Prefer zero"); diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index f84a8caff..a19727690 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -72,7 +72,7 @@ pub use self::{ #[deprecated = "Renamed to CornerRadius"] pub type Rounding = CornerRadius; -#[allow(deprecated)] +#[expect(deprecated)] pub use tessellator::tessellate_shapes; pub use ecolor::{Color32, Hsva, HsvaGamma, Rgba}; diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index a855d653a..bc16e43d5 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -296,7 +296,7 @@ impl Shape { Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind)) } - #[allow(clippy::needless_pass_by_value)] + #[expect(clippy::needless_pass_by_value)] pub fn text( fonts: &Fonts, pos: Pos2, diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 2b24869ae..32f715b10 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -5,7 +5,7 @@ #![allow(clippy::identity_op)] -use emath::{pos2, remap, vec2, GuiRounding as _, NumExt, Pos2, Rect, Rot2, Vec2}; +use emath::{pos2, remap, vec2, GuiRounding as _, NumExt as _, Pos2, Rect, Rot2, Vec2}; use crate::{ color::ColorMode, emath, stroke::PathStroke, texture_atlas::PreparedDisc, CircleShape, @@ -16,7 +16,7 @@ use crate::{ // ---------------------------------------------------------------------------- -#[allow(clippy::approx_constant)] +#[expect(clippy::approx_constant)] mod precomputed_vertices { // fn main() { // let n = 64; @@ -2222,7 +2222,7 @@ impl Tessellator { /// /// ## Returns /// A list of clip rectangles with matching [`Mesh`]. - #[allow(unused_mut)] + #[allow(unused_mut, clippy::allow_attributes)] pub fn tessellate_shapes(&mut self, mut shapes: Vec) -> Vec { profiling::function_scope!(); diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 5415bae07..78d9c1413 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::sync::Arc; -use emath::{vec2, GuiRounding, Vec2}; +use emath::{vec2, GuiRounding as _, Vec2}; use crate::{ mutex::{Mutex, RwLock}, @@ -100,7 +100,7 @@ impl FontImpl { "pixels_per_point must be greater than 0, got: {pixels_per_point:?}" ); - use ab_glyph::{Font, ScaleFont}; + use ab_glyph::{Font as _, ScaleFont as _}; let scaled = ab_glyph_font.as_scaled(scale_in_pixels); let ascent = (scaled.ascent() / pixels_per_point).round_ui(); let descent = (scaled.descent() / pixels_per_point).round_ui(); @@ -241,7 +241,7 @@ impl FontImpl { last_glyph_id: ab_glyph::GlyphId, glyph_id: ab_glyph::GlyphId, ) -> f32 { - use ab_glyph::{Font as _, ScaleFont}; + use ab_glyph::{Font as _, ScaleFont as _}; self.ab_glyph_font .as_scaled(self.scale_in_pixels as f32) .kern(last_glyph_id, glyph_id) @@ -271,7 +271,7 @@ impl FontImpl { fn allocate_glyph(&self, glyph_id: ab_glyph::GlyphId) -> GlyphInfo { assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0"); - use ab_glyph::{Font as _, ScaleFont}; + use ab_glyph::{Font as _, ScaleFont as _}; let glyph = glyph_id.with_scale_and_position( self.scale_in_pixels as f32, diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index bfa854680..bca9212dc 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -54,7 +54,6 @@ impl FontId { } } -#[allow(clippy::derived_hash_with_manual_eq)] impl std::hash::Hash for FontId { #[inline(always)] fn hash(&self, state: &mut H) { diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index b2dba96fc..63dfc3893 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use emath::{pos2, vec2, Align, GuiRounding as _, NumExt, Pos2, Rect, Vec2}; +use emath::{pos2, vec2, Align, GuiRounding as _, NumExt as _, Pos2, Rect, Vec2}; use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex}; diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 4bd15d3e3..49ec29087 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, OrderedFloat, Pos2, Rect, Vec2}; +use emath::{pos2, vec2, Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2}; /// Describes the task of laying out text. /// @@ -971,7 +971,7 @@ impl Galley { /// /// This is the same as [`CCursor::default`]. #[inline] - #[allow(clippy::unused_self)] + #[expect(clippy::unused_self)] pub fn begin(&self) -> CCursor { CCursor::default() } @@ -1062,7 +1062,7 @@ impl Galley { /// ## Cursor positions impl Galley { - #[allow(clippy::unused_self)] + #[expect(clippy::unused_self)] pub fn cursor_left_one_character(&self, cursor: &CCursor) -> CCursor { if cursor.index == 0 { Default::default() diff --git a/crates/epaint/src/texture_handle.rs b/crates/epaint/src/texture_handle.rs index 1f640a171..315437750 100644 --- a/crates/epaint/src/texture_handle.rs +++ b/crates/epaint/src/texture_handle.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::{ - emath::NumExt, mutex::RwLock, textures::TextureOptions, ImageData, ImageDelta, TextureId, + emath::NumExt as _, mutex::RwLock, textures::TextureOptions, ImageData, ImageDelta, TextureId, TextureManager, }; @@ -66,7 +66,7 @@ impl TextureHandle { } /// Assign a new image to an existing texture. - #[allow(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability + #[expect(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability pub fn set(&mut self, image: impl Into, options: TextureOptions) { self.tex_mngr .write() @@ -74,7 +74,7 @@ impl TextureHandle { } /// Assign a new image to a subregion of the whole texture. - #[allow(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability + #[expect(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability pub fn set_partial( &mut self, pos: [usize; 2], diff --git a/examples/custom_style/src/main.rs b/examples/custom_style/src/main.rs index 111139379..1e78bea0f 100644 --- a/examples/custom_style/src/main.rs +++ b/examples/custom_style/src/main.rs @@ -4,7 +4,7 @@ use eframe::egui::{ self, global_theme_preference_buttons, style::Selection, Color32, Stroke, Style, Theme, }; -use egui_demo_lib::{View, WidgetGallery}; +use egui_demo_lib::{View as _, WidgetGallery}; fn main() -> eframe::Result { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index f2d7a0c01..b75a20e00 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -165,7 +165,7 @@ fn start_puffin_server() { // We can store the server if we want, but in this case we just want // it to keep running. Dropping it closes the server, so let's not drop it! - #[allow(clippy::mem_forget)] + #[expect(clippy::mem_forget)] std::mem::forget(puffin_server); } Err(err) => { diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs index 264b0052f..96015cbd9 100644 --- a/tests/egui_tests/tests/test_widgets.rs +++ b/tests/egui_tests/tests/test_widgets.rs @@ -2,9 +2,9 @@ use egui::load::SizedTexture; use egui::{ include_image, Align, Button, Color32, ColorImage, Direction, DragValue, Event, Grid, Layout, PointerButton, Pos2, Response, Slider, Stroke, StrokeKind, TextWrapMode, TextureHandle, - TextureOptions, Ui, UiBuilder, Vec2, Widget, + TextureOptions, Ui, UiBuilder, Vec2, Widget as _, }; -use egui_kittest::kittest::{by, Node, Queryable}; +use egui_kittest::kittest::{by, Node, Queryable as _}; use egui_kittest::{Harness, SnapshotResult, SnapshotResults}; #[test] diff --git a/tests/test_inline_glow_paint/src/main.rs b/tests/test_inline_glow_paint/src/main.rs index 125696526..3245af348 100644 --- a/tests/test_inline_glow_paint/src/main.rs +++ b/tests/test_inline_glow_paint/src/main.rs @@ -29,7 +29,7 @@ impl eframe::App for MyTestApp { use glow::HasContext as _; let gl = frame.gl().unwrap(); - #[allow(unsafe_code)] + #[expect(unsafe_code)] unsafe { gl.disable(glow::SCISSOR_TEST); gl.viewport(0, 0, 100, 100); From f2ce6424f3a32f47308fb9871d540c01377b2cd9 Mon Sep 17 00:00:00 2001 From: MStarha <59487310+MStarha@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:01:22 +0200 Subject: [PATCH 030/388] `ScrollArea` improvements for user configurability (#5443) * Closes * [x] I have followed the instructions in the PR template The changes follow what is described in the issue with a couple changes: - Scroll bars are not hidden when dragging is disabled, for that `ScrollArea::scroll_bar_visibility()` has to be used, this is as not to limit the user configurability by imposing a specific function. The user might want to retain the scrollbars visibility to show the current position. - The input for mouse wheel scrolling is unchanged. When I inspected the code initially I made a mistake in recognizing the source of scrolling. Current implementation is in fact using `InputState::smooth_scroll_delta` and not `PassState::scroll_delta`, therefore it is possible to prevent scrolling by setting the `InputState::smooth_scroll_delta` to zero before painting the `ScrollArea`. A simple demo is available at https://github.com/MStarha/egui_scroll_area_test --- crates/egui/src/containers/scroll_area.rs | 274 ++++++++++++++++++---- crates/egui/src/containers/window.rs | 7 +- crates/egui_extras/src/table.rs | 7 +- 3 files changed, 236 insertions(+), 52 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index b2df2100b..0c4cc7efb 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,8 +1,10 @@ #![allow(clippy::needless_range_loop)] +use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; + use crate::{ - emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, Context, Id, NumExt as _, Pos2, - Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, + emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, Context, CursorIcon, Id, + NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, }; #[derive(Clone, Copy, Debug)] @@ -133,6 +135,113 @@ impl ScrollBarVisibility { ]; } +/// What is the source of scrolling for a [`ScrollArea`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct ScrollSource { + /// Scroll the area by dragging a scroll bar. + /// + /// By default the scroll bars remain visible to show current position. + /// To hide them use [`ScrollArea::scroll_bar_visibility()`]. + pub scroll_bar: bool, + + /// Scroll the area by dragging the contents. + pub drag: bool, + + /// Scroll the area by scrolling (or shift scrolling) the mouse wheel with + /// the mouse cursor over the [`ScrollArea`]. + pub mouse_wheel: bool, +} + +impl Default for ScrollSource { + fn default() -> Self { + Self::ALL + } +} + +impl ScrollSource { + pub const NONE: Self = Self { + scroll_bar: false, + drag: false, + mouse_wheel: false, + }; + pub const ALL: Self = Self { + scroll_bar: true, + drag: true, + mouse_wheel: true, + }; + pub const SCROLL_BAR: Self = Self { + scroll_bar: true, + drag: false, + mouse_wheel: false, + }; + pub const DRAG: Self = Self { + scroll_bar: false, + drag: true, + mouse_wheel: false, + }; + pub const MOUSE_WHEEL: Self = Self { + scroll_bar: false, + drag: false, + mouse_wheel: true, + }; + + /// Is everything disabled? + #[inline] + pub fn is_none(&self) -> bool { + self == &Self::NONE + } + + /// Is anything enabled? + #[inline] + pub fn any(&self) -> bool { + self.scroll_bar | self.drag | self.mouse_wheel + } + + /// Is everything enabled? + #[inline] + pub fn is_all(&self) -> bool { + self.scroll_bar & self.drag & self.mouse_wheel + } +} + +impl BitOr for ScrollSource { + type Output = Self; + + #[inline] + fn bitor(self, rhs: Self) -> Self::Output { + Self { + scroll_bar: self.scroll_bar | rhs.scroll_bar, + drag: self.drag | rhs.drag, + mouse_wheel: self.mouse_wheel | rhs.mouse_wheel, + } + } +} + +#[expect(clippy::suspicious_arithmetic_impl)] +impl Add for ScrollSource { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self::Output { + self | rhs + } +} + +impl BitOrAssign for ScrollSource { + #[inline] + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; + } +} + +impl AddAssign for ScrollSource { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + /// Add vertical and/or horizontal scrolling to a contained [`Ui`]. /// /// By default, scroll bars only show up when needed, i.e. when the contents @@ -168,7 +277,7 @@ impl ScrollBarVisibility { #[must_use = "You should call .show()"] pub struct ScrollArea { /// Do we have horizontal/vertical scrolling enabled? - scroll_enabled: Vec2b, + direction_enabled: Vec2b, auto_shrink: Vec2b, max_size: Vec2, @@ -178,10 +287,10 @@ pub struct ScrollArea { id_salt: Option, offset_x: Option, offset_y: Option, - - /// If false, we ignore scroll events. - scrolling_enabled: bool, - drag_to_scroll: bool, + on_hover_cursor: Option, + on_drag_cursor: Option, + scroll_source: ScrollSource, + wheel_scroll_multiplier: Vec2, /// If true for vertical or horizontal the scroll wheel will stick to the /// end position until user manually changes position. It will become true @@ -220,9 +329,9 @@ impl ScrollArea { /// Create a scroll area where you decide which axis has scrolling enabled. /// For instance, `ScrollArea::new([true, false])` enables horizontal scrolling. - pub fn new(scroll_enabled: impl Into) -> Self { + pub fn new(direction_enabled: impl Into) -> Self { Self { - scroll_enabled: scroll_enabled.into(), + direction_enabled: direction_enabled.into(), auto_shrink: Vec2b::TRUE, max_size: Vec2::INFINITY, min_scrolled_size: Vec2::splat(64.0), @@ -231,8 +340,10 @@ impl ScrollArea { id_salt: None, offset_x: None, offset_y: None, - scrolling_enabled: true, - drag_to_scroll: true, + on_hover_cursor: None, + on_drag_cursor: None, + scroll_source: ScrollSource::default(), + wheel_scroll_multiplier: Vec2::splat(1.0), stick_to_end: Vec2b::FALSE, animated: true, } @@ -355,17 +466,41 @@ impl ScrollArea { self } + /// Set the cursor used when the mouse pointer is hovering over the [`ScrollArea`]. + /// + /// Only applies if [`Self::scroll_source()`] has set [`ScrollSource::drag`] to `true`. + /// + /// Any changes to the mouse cursor made within the contents of the [`ScrollArea`] will + /// override this setting. + #[inline] + pub fn on_hover_cursor(mut self, cursor: CursorIcon) -> Self { + self.on_hover_cursor = Some(cursor); + self + } + + /// Set the cursor used when the [`ScrollArea`] is being dragged. + /// + /// Only applies if [`Self::scroll_source()`] has set [`ScrollSource::drag`] to `true`. + /// + /// Any changes to the mouse cursor made within the contents of the [`ScrollArea`] will + /// override this setting. + #[inline] + pub fn on_drag_cursor(mut self, cursor: CursorIcon) -> Self { + self.on_drag_cursor = Some(cursor); + self + } + /// Turn on/off scrolling on the horizontal axis. #[inline] pub fn hscroll(mut self, hscroll: bool) -> Self { - self.scroll_enabled[0] = hscroll; + self.direction_enabled[0] = hscroll; self } /// Turn on/off scrolling on the vertical axis. #[inline] pub fn vscroll(mut self, vscroll: bool) -> Self { - self.scroll_enabled[1] = vscroll; + self.direction_enabled[1] = vscroll; self } @@ -373,16 +508,16 @@ impl ScrollArea { /// /// You can pass in `false`, `true`, `[false, true]` etc. #[inline] - pub fn scroll(mut self, scroll_enabled: impl Into) -> Self { - self.scroll_enabled = scroll_enabled.into(); + pub fn scroll(mut self, direction_enabled: impl Into) -> Self { + self.direction_enabled = direction_enabled.into(); self } /// Turn on/off scrolling on the horizontal/vertical axes. #[deprecated = "Renamed to `scroll`"] #[inline] - pub fn scroll2(mut self, scroll_enabled: impl Into) -> Self { - self.scroll_enabled = scroll_enabled.into(); + pub fn scroll2(mut self, direction_enabled: impl Into) -> Self { + self.direction_enabled = direction_enabled.into(); self } @@ -395,9 +530,14 @@ impl ScrollArea { /// is typing text in a [`crate::TextEdit`] widget contained within the scroll area. /// /// This controls both scrolling directions. + #[deprecated = "Use `ScrollArea::scroll_source()"] #[inline] pub fn enable_scrolling(mut self, enable: bool) -> Self { - self.scrolling_enabled = enable; + self.scroll_source = if enable { + ScrollSource::ALL + } else { + ScrollSource::NONE + }; self } @@ -408,9 +548,28 @@ impl ScrollArea { /// If `true`, the [`ScrollArea`] will sense drags. /// /// Default: `true`. + #[deprecated = "Use `ScrollArea::scroll_source()"] #[inline] pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { - self.drag_to_scroll = drag_to_scroll; + self.scroll_source.drag = drag_to_scroll; + self + } + + /// What sources does the [`ScrollArea`] use for scrolling the contents. + #[inline] + pub fn scroll_source(mut self, scroll_source: ScrollSource) -> Self { + self.scroll_source = scroll_source; + self + } + + /// The scroll amount caused by a mouse wheel scroll is multiplied by this amount. + /// + /// Independent for each scroll direction. Defaults to `Vec2{x: 1.0, y: 1.0}`. + /// + /// This can invert or effectively disable mouse scrolling. + #[inline] + pub fn wheel_scroll_multiplier(mut self, multiplier: Vec2) -> Self { + self.wheel_scroll_multiplier = multiplier; self } @@ -437,7 +596,7 @@ impl ScrollArea { /// Is any scrolling enabled? pub(crate) fn is_any_scroll_enabled(&self) -> bool { - self.scroll_enabled[0] || self.scroll_enabled[1] + self.direction_enabled[0] || self.direction_enabled[1] } /// The scroll handle will stick to the rightmost position even while the content size @@ -472,7 +631,7 @@ struct Prepared { auto_shrink: Vec2b, /// Does this `ScrollArea` have horizontal/vertical scrolling enabled? - scroll_enabled: Vec2b, + direction_enabled: Vec2b, /// Smoothly interpolated boolean of whether or not to show the scroll bars. show_bars_factor: Vec2, @@ -500,7 +659,8 @@ struct Prepared { /// `viewport.min == ZERO` means we scrolled to the top. viewport: Rect, - scrolling_enabled: bool, + scroll_source: ScrollSource, + wheel_scroll_multiplier: Vec2, stick_to_end: Vec2b, /// If there was a scroll target before the [`ScrollArea`] was added this frame, it's @@ -513,7 +673,7 @@ struct Prepared { impl ScrollArea { fn begin(self, ui: &mut Ui) -> Prepared { let Self { - scroll_enabled, + direction_enabled, auto_shrink, max_size, min_scrolled_size, @@ -522,14 +682,15 @@ impl ScrollArea { id_salt, offset_x, offset_y, - scrolling_enabled, - drag_to_scroll, + on_hover_cursor, + on_drag_cursor, + scroll_source, + wheel_scroll_multiplier, stick_to_end, animated, } = self; let ctx = ui.ctx().clone(); - let scrolling_enabled = scrolling_enabled && ui.is_enabled(); let id_salt = id_salt.unwrap_or_else(|| Id::new("scroll_area")); let id = ui.make_persistent_id(id_salt); @@ -546,7 +707,7 @@ impl ScrollArea { let show_bars: Vec2b = match scroll_bar_visibility { ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE, ScrollBarVisibility::VisibleWhenNeeded => state.show_scroll, - ScrollBarVisibility::AlwaysVisible => scroll_enabled, + ScrollBarVisibility::AlwaysVisible => direction_enabled, }; let show_bars_factor = Vec2::new( @@ -568,7 +729,7 @@ impl ScrollArea { // one shouldn't collapse into nothingness. // See https://github.com/emilk/egui/issues/1097 for d in 0..2 { - if scroll_enabled[d] { + if direction_enabled[d] { inner_size[d] = inner_size[d].max(min_scrolled_size[d]); } } @@ -585,7 +746,7 @@ impl ScrollArea { } else { // Tell the inner Ui to use as much space as possible, we can scroll to see it! for d in 0..2 { - if scroll_enabled[d] { + if direction_enabled[d] { content_max_size[d] = f32::INFINITY; } } @@ -603,7 +764,7 @@ impl ScrollArea { let clip_rect_margin = ui.visuals().clip_rect_margin; let mut content_clip_rect = ui.clip_rect(); for d in 0..2 { - if scroll_enabled[d] { + if direction_enabled[d] { content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; } else { @@ -619,7 +780,8 @@ impl ScrollArea { let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size); let dt = ui.input(|i| i.stable_dt).at_most(0.1); - if (scrolling_enabled && drag_to_scroll) + if scroll_source.drag + && ui.is_enabled() && (state.content_is_too_large[0] || state.content_is_too_large[1]) { // Drag contents to scroll (for touch screens mostly). @@ -634,7 +796,7 @@ impl ScrollArea { .is_some_and(|response| response.dragged()) { for d in 0..2 { - if scroll_enabled[d] { + if direction_enabled[d] { ui.input(|input| { state.offset[d] -= input.pointer.delta()[d]; }); @@ -649,7 +811,7 @@ impl ScrollArea { .is_some_and(|response| response.drag_stopped()) { state.vel = - scroll_enabled.to_vec2() * ui.input(|input| input.pointer.velocity()); + direction_enabled.to_vec2() * ui.input(|input| input.pointer.velocity()); } for d in 0..2 { // Kinetic scrolling @@ -668,6 +830,19 @@ impl ScrollArea { } } } + + // Set the desired mouse cursors. + if let Some(response) = content_response_option { + if response.dragged() { + if let Some(cursor) = on_drag_cursor { + response.on_hover_cursor(cursor); + } + } else if response.hovered() { + if let Some(cursor) = on_hover_cursor { + response.on_hover_cursor(cursor); + } + } + } } // Scroll with an animation if we have a target offset (that hasn't been cleared by the code @@ -709,7 +884,7 @@ impl ScrollArea { id, state, auto_shrink, - scroll_enabled, + direction_enabled, show_bars_factor, current_bar_use, scroll_bar_visibility, @@ -717,7 +892,8 @@ impl ScrollArea { inner_rect, content_ui, viewport, - scrolling_enabled, + scroll_source, + wheel_scroll_multiplier, stick_to_end, saved_scroll_target, animated, @@ -824,14 +1000,15 @@ impl Prepared { mut state, inner_rect, auto_shrink, - scroll_enabled, + direction_enabled, mut show_bars_factor, current_bar_use, scroll_bar_visibility, scroll_bar_rect, content_ui, viewport: _, - scrolling_enabled, + scroll_source, + wheel_scroll_multiplier, stick_to_end, saved_scroll_target, animated, @@ -854,7 +1031,7 @@ impl Prepared { .ctx() .pass_state_mut(|state| state.scroll_target[d].take()); - if scroll_enabled[d] { + if direction_enabled[d] { if let Some(target) = scroll_target { let pass_state::ScrollTarget { range, @@ -930,7 +1107,7 @@ impl Prepared { let mut inner_size = inner_rect.size(); for d in 0..2 { - inner_size[d] = match (scroll_enabled[d], auto_shrink[d]) { + inner_size[d] = match (direction_enabled[d], auto_shrink[d]) { (true, true) => inner_size[d].min(content_size[d]), // shrink scroll area if content is small (true, false) => inner_size[d], // let scroll area be larger than content; fill with blank space (false, true) => content_size[d], // Follow the content (expand/contract to fit it). @@ -944,18 +1121,18 @@ impl Prepared { let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use); let content_is_too_large = Vec2b::new( - scroll_enabled[0] && inner_rect.width() < content_size.x, - scroll_enabled[1] && inner_rect.height() < content_size.y, + direction_enabled[0] && inner_rect.width() < content_size.x, + direction_enabled[1] && inner_rect.height() < content_size.y, ); let max_offset = content_size - inner_rect.size(); let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); - if scrolling_enabled && is_hovering_outer_rect { + if scroll_source.mouse_wheel && ui.is_enabled() && is_hovering_outer_rect { let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction - && scroll_enabled[0] != scroll_enabled[1]; + && direction_enabled[0] != direction_enabled[1]; for d in 0..2 { - if scroll_enabled[d] { - let scroll_delta = ui.ctx().input_mut(|input| { + if direction_enabled[d] { + let scroll_delta = ui.ctx().input(|input| { if always_scroll_enabled_direction { // no bidirectional scrolling; allow horizontal scrolling without pressing shift input.smooth_scroll_delta[0] + input.smooth_scroll_delta[1] @@ -963,6 +1140,7 @@ impl Prepared { input.smooth_scroll_delta[d] } }); + let scroll_delta = scroll_delta * wheel_scroll_multiplier[d]; let scrolling_up = state.offset[d] > 0.0 && scroll_delta > 0.0; let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta < 0.0; @@ -990,7 +1168,7 @@ impl Prepared { let show_scroll_this_frame = match scroll_bar_visibility { ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE, ScrollBarVisibility::VisibleWhenNeeded => content_is_too_large, - ScrollBarVisibility::AlwaysVisible => scroll_enabled, + ScrollBarVisibility::AlwaysVisible => direction_enabled, }; // Avoid frame delay; start showing scroll bar right away: @@ -1120,7 +1298,7 @@ impl Prepared { let handle_rect = calculate_handle_rect(d, &state.offset); let interact_id = id.with(d); - let sense = if self.scrolling_enabled { + let sense = if scroll_source.scroll_bar && ui.is_enabled() { Sense::click_and_drag() } else { Sense::hover() @@ -1170,7 +1348,7 @@ impl Prepared { // Avoid frame-delay by calculating a new handle rect: let handle_rect = calculate_handle_rect(d, &state.offset); - let visuals = if scrolling_enabled { + let visuals = if scroll_source.scroll_bar && ui.is_enabled() { // Pick visuals based on interaction with the handle. // Remember that the response is for the whole scroll bar! let is_hovering_handle = response.hovered() diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 52a81d37d..fa3d0d1fe 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -8,7 +8,7 @@ use epaint::{CornerRadiusF32, RectShape}; use crate::collapsing_header::CollapsingState; use crate::*; -use super::scroll_area::ScrollBarVisibility; +use super::scroll_area::{ScrollBarVisibility, ScrollSource}; use super::{area, resize, Area, Frame, Resize, ScrollArea}; /// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default). @@ -403,7 +403,10 @@ impl<'open> Window<'open> { /// See [`ScrollArea::drag_to_scroll`] for more. #[inline] pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { - self.scroll = self.scroll.drag_to_scroll(drag_to_scroll); + self.scroll = self.scroll.scroll_source(ScrollSource { + drag: drag_to_scroll, + ..Default::default() + }); self } diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 172f1bee5..2d64f2570 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -4,7 +4,7 @@ //! Takes all available height, so if you want something below the table, put it in a strip. use egui::{ - scroll_area::{ScrollAreaOutput, ScrollBarVisibility}, + scroll_area::{ScrollAreaOutput, ScrollBarVisibility, ScrollSource}, Align, Id, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b, }; @@ -745,7 +745,10 @@ impl Table<'_> { let mut scroll_area = ScrollArea::new([false, vscroll]) .id_salt(state_id.with("__scroll_area")) - .drag_to_scroll(drag_to_scroll) + .scroll_source(ScrollSource { + drag: drag_to_scroll, + ..Default::default() + }) .stick_to_bottom(stick_to_bottom) .min_scrolled_height(min_scrolled_height) .max_height(max_scroll_height) From 7d185acb41d9257997ae95e7459b24f3c5392f88 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 28 Apr 2025 11:58:05 +0200 Subject: [PATCH 031/388] Add button benchmark (#6854) This helped me benchmark the atomic layout (#5830) changes. I also realized that the label benchmark wasn't testing the painting, since the buttons at some point will be placed outside the screen_rect, meaning it won't be painted. This fixes it by benching the label in a child ui. The `label &str` benchmark went from 483 ns to 535 ns with these changes. EDIT: I fixed another benchmark problem, since the benchmark would show the same widget millions of times for a single frame, the WidgetRects hashmap would get huge, causing each iteration to slow down a bit more and causing the benchmark to have unreliable results. With this the `label &str` benchmark went from 535ns to 298ns. Also the `label format!` benchmark now takes almost the same time (302 ns). Before, it was a lot slower since it reused the same Context which already had millions of widget ids. --- crates/egui_demo_lib/README.md | 16 +++++ crates/egui_demo_lib/benches/benchmark.rs | 74 ++++++++++++++++++++--- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/crates/egui_demo_lib/README.md b/crates/egui_demo_lib/README.md index 43ca0a0f3..58a5d6305 100644 --- a/crates/egui_demo_lib/README.md +++ b/crates/egui_demo_lib/README.md @@ -14,3 +14,19 @@ The demo library is a separate crate for three reasons: * To remove the amount of code in `egui` proper. * To make it easy for 3rd party egui integrations to use it for tests. - See for instance https://github.com/not-fl3/egui-miniquad/blob/master/examples/demo.rs + +This crate also contains benchmarks for egui. +Run them with +```bash +# Run all benchmarks +cargo bench -p egui_demo_lib + +# Run a single benchmark +cargo bench -p egui_demo_lib "benchmark name" + +# Profile benchmarks with cargo-flamegraph (--root flag is necessary for MacOS) +CARGO_PROFILE_BENCH_DEBUG=true cargo flamegraph --bench benchmark --root -p egui_demo_lib -- --bench "benchmark name" + +# Profile with cargo-instruments +CARGO_PROFILE_BENCH_DEBUG=true cargo instruments --profile bench --bench benchmark -p egui_demo_lib -t time -- --bench "benchmark name" +``` diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index dab6bdd7b..331788c9b 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -1,11 +1,20 @@ use std::fmt::Write as _; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use egui::epaint::TextShape; +use egui::load::SizedTexture; +use egui::{Button, Id, TextureId, Ui, UiBuilder, Vec2}; use egui_demo_lib::LOREM_IPSUM_LONG; use rand::Rng as _; +/// Each iteration should be called in their own `Ui` with an intentional id clash, +/// to prevent the Context from building a massive map of `WidgetRects` (which would slow the test, +/// causing unreliable results). +fn create_benchmark_ui(ctx: &egui::Context) -> Ui { + Ui::new(ctx.clone(), Id::new("clashing_id"), UiBuilder::new()) +} + pub fn criterion_benchmark(c: &mut Criterion) { use egui::RawInput; @@ -55,17 +64,62 @@ pub fn criterion_benchmark(c: &mut Criterion) { { let ctx = egui::Context::default(); let _ = ctx.run(RawInput::default(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - c.bench_function("label &str", |b| { - b.iter(|| { + c.bench_function("label &str", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { ui.label("the quick brown fox jumps over the lazy dog"); - }); - }); - c.bench_function("label format!", |b| { - b.iter(|| { + }, + BatchSize::LargeInput, + ); + }); + c.bench_function("label format!", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { ui.label("the quick brown fox jumps over the lazy dog".to_owned()); - }); - }); + }, + BatchSize::LargeInput, + ); + }); + }); + } + + { + let ctx = egui::Context::default(); + let _ = ctx.run(RawInput::default(), |ctx| { + let mut group = c.benchmark_group("button"); + + // To ensure we have a valid image, let's use the font texture. The size + // shouldn't be important for this benchmark. + let image = SizedTexture::new(TextureId::default(), Vec2::splat(16.0)); + + group.bench_function("1_button_text", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { + ui.add(Button::new("Hello World")); + }, + BatchSize::LargeInput, + ); + }); + group.bench_function("2_button_text_image", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { + ui.add(Button::image_and_text(image, "Hello World")); + }, + BatchSize::LargeInput, + ); + }); + group.bench_function("3_button_text_image_right_text", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { + ui.add(Button::image_and_text(image, "Hello World").right_text("⏵")); + }, + BatchSize::LargeInput, + ); }); }); } From 3a02963c332ea1f5f0ed712a88efa83dde8ba9b1 Mon Sep 17 00:00:00 2001 From: Gaelan McMillan <73502924+gaelanmcmillan@users.noreply.github.com> Date: Tue, 29 Apr 2025 06:02:42 -0400 Subject: [PATCH 032/388] Add macOS-specific `has_shadow` and `with_has_shadow` to ViewportBuilder (#6850) * [X] I have followed the instructions in the PR template This PR fixes a ghosting issue I encountered while making a native macOS transparent overlay app using egui and eframe by exposing the [existing macOS window attribute `has_shadow`](https://docs.rs/winit/latest/winit/platform/macos/trait.WindowExtMacOS.html#tymethod.has_shadow) to the `ViewportBuilder` via a new `with_has_shadow` option. ## Example of Ghosting Issue ### Before `ViewportBuilder::with_has_shadow` By default, the underlying `winit` window's `.has_shadow()` defaults to `true`. https://github.com/user-attachments/assets/c3dcc2bd-535a-4960-918e-3ae5df503b12 ### After `ViewportBuilder::with_has_shadow` https://github.com/user-attachments/assets/484462a1-ea88-43e6-85b4-0bb9724e5f14 Source code for the above example can be found here: https://github.com/gaelanmcmillan/egui-overlay-app-with-shadow-artifacts-example/blob/main/src/main.rs ### Further background By default on macOS, `winit` windows have a drop-shadow effect. When creating a fully transparent overlay GUI, this drop-shadow can create a ghosting effect, as the window content has a drop shadow which is not cleared by the app itself. This issue has been experienced by users of `bevy`, another Rust project that has an upstream dependency on `winit`: https://github.com/bevyengine/bevy/issues/18673 --- crates/egui-winit/src/lib.rs | 4 +++- crates/egui/src/viewport.rs | 25 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index fe4d2945d..03ec3e831 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1620,6 +1620,7 @@ pub fn create_winit_window_attributes( title_shown: _title_shown, titlebar_buttons_shown: _titlebar_buttons_shown, titlebar_shown: _titlebar_shown, + has_shadow: _has_shadow, // Windows: drag_and_drop: _drag_and_drop, @@ -1764,7 +1765,8 @@ pub fn create_winit_window_attributes( .with_titlebar_buttons_hidden(!_titlebar_buttons_shown.unwrap_or(true)) .with_titlebar_transparent(!_titlebar_shown.unwrap_or(true)) .with_fullsize_content_view(_fullsize_content_view.unwrap_or(false)) - .with_movable_by_window_background(_movable_by_window_background.unwrap_or(false)); + .with_movable_by_window_background(_movable_by_window_background.unwrap_or(false)) + .with_has_shadow(_has_shadow.unwrap_or(true)); } window_attributes diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 2a469435e..4e61e45e1 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -294,6 +294,7 @@ pub struct ViewportBuilder { pub title_shown: Option, pub titlebar_buttons_shown: Option, pub titlebar_shown: Option, + pub has_shadow: Option, // windows: pub drag_and_drop: Option, @@ -380,6 +381,10 @@ impl ViewportBuilder { /// The default is `false`. /// If this is not working, it's because the graphic context doesn't support transparency, /// you will need to set the transparency in the eframe! + /// + /// ## Platform-specific + /// + /// **macOS:** When using this feature to create an overlay-like UI, you likely want to combine this with [`Self::with_has_shadow`] set to `false` in order to avoid ghosting artifacts. #[inline] pub fn with_transparent(mut self, transparent: bool) -> Self { self.transparent = Some(transparent); @@ -433,7 +438,6 @@ 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. #[inline] pub fn with_movable_by_background(mut self, value: bool) -> Self { @@ -462,6 +466,19 @@ impl ViewportBuilder { self } + /// macOS: Set to `false` to make the window render without a drop shadow. + /// + /// The default is `true`. + /// + /// Disabling this feature can solve ghosting issues experienced if using [`Self::with_transparent`]. + /// + /// Look at winit for more details + #[inline] + pub fn with_has_shadow(mut self, has_shadow: bool) -> Self { + self.has_shadow = Some(has_shadow); + self + } + /// windows: Whether show or hide the window icon in the taskbar. #[inline] pub fn with_taskbar(mut self, show: bool) -> Self { @@ -653,6 +670,7 @@ impl ViewportBuilder { title_shown: new_title_shown, titlebar_buttons_shown: new_titlebar_buttons_shown, titlebar_shown: new_titlebar_shown, + has_shadow: new_has_shadow, close_button: new_close_button, minimize_button: new_minimize_button, maximize_button: new_maximize_button, @@ -823,6 +841,11 @@ impl ViewportBuilder { recreate_window = true; } + if new_has_shadow.is_some() && self.has_shadow != new_has_shadow { + self.has_shadow = new_has_shadow; + recreate_window = true; + } + if new_taskbar.is_some() && self.taskbar != new_taskbar { self.taskbar = new_taskbar; recreate_window = true; From d666742c13604e2f15455fd97d8984ed635647c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 29 Apr 2025 12:03:24 +0200 Subject: [PATCH 033/388] Bump `ron` to `0.10.1` (#6861) This should help `cargo-deny` be at peace with https://github.com/emilk/egui/pull/6860, pending https://github.com/gfx-rs/wgpu/pull/7557. --- Cargo.lock | 21 ++++++++------------- Cargo.toml | 2 +- crates/eframe/src/native/file_storage.rs | 3 ++- deny.toml | 1 - 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4411be0c6..c430601f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -539,12 +539,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -3153,7 +3147,7 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.22.1", + "base64", "indexmap", "quick-xml 0.32.0", "serde", @@ -3542,14 +3536,15 @@ dependencies = [ [[package]] name = "ron" -version = "0.8.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" dependencies = [ - "base64 0.21.7", + "base64", "bitflags 2.8.0", "serde", "serde_derive", + "unicode-ident", ] [[package]] @@ -4344,7 +4339,7 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" dependencies = [ - "base64 0.22.1", + "base64", "flate2", "log", "once_cell", @@ -4386,7 +4381,7 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ac8e0e3e4696253dc06167990b3fe9a2668ab66270adf949a464db4088cb354" dependencies = [ - "base64 0.22.1", + "base64", "data-url", "flate2", "fontdb", diff --git a/Cargo.toml b/Cargo.toml index 80fee9f74..3b306176b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ profiling = { version = "1.0.16", default-features = false } puffin = "0.19" puffin_http = "0.16" raw-window-handle = "0.6.0" -ron = "0.8" +ron = "0.10.1" serde = { version = "1", features = ["derive"] } similar-asserts = "1.4.2" thiserror = "1.0.37" diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index fa89b9059..e1fdc9b84 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -207,7 +207,8 @@ fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { let config = Default::default(); profiling::scope!("ron::serialize"); - if let Err(err) = ron::ser::to_writer_pretty(&mut writer, &kv, config) + if let Err(err) = ron::Options::default() + .to_io_writer_pretty(&mut writer, &kv, config) .and_then(|_| writer.flush().map_err(|err| err.into())) { log::warn!("Failed to serialize app state: {}", err); diff --git a/deny.toml b/deny.toml index 2f7bdbcca..f77dd4522 100644 --- a/deny.toml +++ b/deny.toml @@ -45,7 +45,6 @@ deny = [ ] skip = [ - { name = "base64" }, # Pretty small { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … From 8d9e42413a83d9c96b0232885b87664d104b5d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 29 Apr 2025 12:03:59 +0200 Subject: [PATCH 034/388] Remove outdated skip entries from deny.toml (#6862) Looks like these got deduplicated sometime. --- deny.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/deny.toml b/deny.toml index f77dd4522..a99373253 100644 --- a/deny.toml +++ b/deny.toml @@ -48,21 +48,15 @@ skip = [ { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … - { name = "event-listener" }, # TODO(emilk): rustls pulls in two versions of this 😭 - { name = "futures-lite" }, # old version via accesskit_unix and zbus - { name = "memoffset" }, # tiny dependency { name = "ndk-sys" }, # old version via wgpu, winit uses newer version { name = "quick-xml" }, # old version via wayland-scanner { name = "redox_syscall" }, # old version via winit { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x { name = "thiserror-impl" }, # same as above - { name = "time" }, # old version pulled in by unmaintained crate 'chrono' { name = "windows-core" }, # Chrono pulls in 0.51, accesskit uses 0.58.0 { name = "windows-sys" }, # glutin pulls in 0.52.0, accesskit pulls in 0.59.0, rfd pulls 0.48, webbrowser pulls 0.45.0 (via jni) ] skip-tree = [ - { name = "criterion" }, # dev-dependency - { name = "foreign-types" }, # small crate. Old version via core-graphics (winit). { name = "rfd" }, # example dependency ] From fed2ab5df3c0c92c9b18cd5ab80785302bcaf275 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 29 Apr 2025 20:07:39 +1000 Subject: [PATCH 035/388] feat: Add `Scene::sense` option for customising how `Scene` should respond to user input (#5893) Allows for specifying how the `Scene` should respond to user input. With #5892, closes #5891. --- Edit: Failing tests unrelated and appear on master: https://github.com/emilk/egui/actions/runs/14330259861/job/40164414607. --------- Co-authored-by: Lucas Meurer --- crates/egui/src/containers/scene.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index e5a350327..aaca37291 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -45,6 +45,7 @@ fn fit_to_rect_in_scene( #[must_use = "You should call .show()"] pub struct Scene { zoom_range: Rangef, + sense: Sense, max_inner_size: Vec2, drag_pan_buttons: DragPanButtons, } @@ -76,6 +77,7 @@ impl Default for Scene { fn default() -> Self { Self { zoom_range: Rangef::new(f32::EPSILON, 1.0), + sense: Sense::click_and_drag(), max_inner_size: Vec2::splat(1000.0), drag_pan_buttons: DragPanButtons::all(), } @@ -88,6 +90,17 @@ impl Scene { Default::default() } + /// Specify what type of input the scene should respond to. + /// + /// The default is `Sense::click_and_drag()`. + /// + /// Set this to `Sense::hover()` to disable panning via clicking and dragging. + #[inline] + pub fn sense(mut self, sense: Sense) -> Self { + self.sense = sense; + self + } + /// Set the allowed zoom range. /// /// The default zoom range is `0.0..=1.0`, @@ -184,7 +197,7 @@ impl Scene { UiBuilder::new() .layer_id(scene_layer_id) .max_rect(Rect::from_min_size(Pos2::ZERO, self.max_inner_size)) - .sense(Sense::click_and_drag()), + .sense(self.sense), ); let mut pan_response = local_ui.response(); From c075053391448515c5516723845b1863a8156c6b Mon Sep 17 00:00:00 2001 From: Will Brown Date: Tue, 29 Apr 2025 06:09:23 -0400 Subject: [PATCH 036/388] Add external eventloop support (#6750) * Closes #2875 * Closes https://github.com/emilk/egui/pull/3340 * [x] I have followed the instructions in the PR template Adds `create_native`. Similiar to `run_native` but it returns an `EframeWinitApplication` which is a `winit::ApplicationHandler`. This can be run on your own event loop. A helper fn `pump_eframe_app` is provided to pump the event loop and get the control flow state back. I have been using this approach for a few months. --------- Co-authored-by: Will Brown --- Cargo.lock | 59 +++++++- crates/eframe/src/lib.rs | 136 +++++++++++++++--- crates/eframe/src/native/run.rs | 130 +++++++++++++++++ examples/external_eventloop/Cargo.toml | 25 ++++ examples/external_eventloop/README.md | 7 + examples/external_eventloop/src/main.rs | 89 ++++++++++++ examples/external_eventloop_async/Cargo.toml | 35 +++++ examples/external_eventloop_async/README.md | 10 ++ examples/external_eventloop_async/src/app.rs | 130 +++++++++++++++++ examples/external_eventloop_async/src/main.rs | 15 ++ 10 files changed, 614 insertions(+), 22 deletions(-) create mode 100644 examples/external_eventloop/Cargo.toml create mode 100644 examples/external_eventloop/README.md create mode 100644 examples/external_eventloop/src/main.rs create mode 100644 examples/external_eventloop_async/Cargo.toml create mode 100644 examples/external_eventloop_async/README.md create mode 100644 examples/external_eventloop_async/src/app.rs create mode 100644 examples/external_eventloop_async/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index c430601f2..35ff4c9af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,6 +1614,26 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "external_eventloop" +version = "0.1.0" +dependencies = [ + "eframe", + "env_logger", + "winit", +] + +[[package]] +name = "external_eventloop_async" +version = "0.1.0" +dependencies = [ + "eframe", + "env_logger", + "log", + "tokio", + "winit", +] + [[package]] name = "fancy-regex" version = "0.11.0" @@ -2446,9 +2466,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" @@ -2603,6 +2623,17 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + [[package]] name = "multiple_viewports" version = "0.1.0" @@ -3860,6 +3891,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -4178,6 +4219,20 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + [[package]] name = "toml_datetime" version = "0.6.8" diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index ec27b05a0..aefe4333b 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -182,6 +182,14 @@ pub use web::{WebLogger, WebRunner}; #[cfg(any(feature = "glow", feature = "wgpu"))] mod native; +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub use native::run::EframeWinitApplication; + +#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub use native::run::EframePumpStatus; + #[cfg(not(target_arch = "wasm32"))] #[cfg(any(feature = "glow", feature = "wgpu"))] #[cfg(feature = "persistence")] @@ -242,26 +250,7 @@ pub fn run_native( mut native_options: NativeOptions, app_creator: AppCreator<'_>, ) -> Result { - #[cfg(not(feature = "__screenshot"))] - assert!( - std::env::var("EFRAME_SCREENSHOT_TO").is_err(), - "EFRAME_SCREENSHOT_TO found without compiling with the '__screenshot' feature" - ); - - if native_options.viewport.title.is_none() { - native_options.viewport.title = Some(app_name.to_owned()); - } - - let renderer = native_options.renderer; - - #[cfg(all(feature = "glow", feature = "wgpu"))] - { - match renderer { - Renderer::Glow => "glow", - Renderer::Wgpu => "wgpu", - }; - log::info!("Both the glow and wgpu renderers are available. Using {renderer}."); - } + let renderer = init_native(app_name, &mut native_options); match renderer { #[cfg(feature = "glow")] @@ -278,6 +267,113 @@ pub fn run_native( } } +/// Provides a proxy for your native eframe application to run on your own event loop. +/// +/// See `run_native` for details about `app_name`. +/// +/// Call from `fn main` like this: +/// ``` no_run +/// use eframe::{egui, UserEvent}; +/// use winit::event_loop::{ControlFlow, EventLoop}; +/// +/// fn main() -> eframe::Result { +/// let native_options = eframe::NativeOptions::default(); +/// let eventloop = EventLoop::::with_user_event().build()?; +/// eventloop.set_control_flow(ControlFlow::Poll); +/// +/// let mut winit_app = eframe::create_native( +/// "MyExtApp", +/// native_options, +/// Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc)))), +/// &eventloop, +/// ); +/// +/// eventloop.run_app(&mut winit_app)?; +/// +/// Ok(()) +/// } +/// +/// #[derive(Default)] +/// struct MyEguiApp {} +/// +/// impl MyEguiApp { +/// fn new(cc: &eframe::CreationContext<'_>) -> Self { +/// Self::default() +/// } +/// } +/// +/// impl eframe::App for MyEguiApp { +/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { +/// egui::CentralPanel::default().show(ctx, |ui| { +/// ui.heading("Hello World!"); +/// }); +/// } +/// } +/// ``` +/// +/// See the `external_eventloop` example for a more complete example. +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +pub fn create_native<'a>( + app_name: &str, + mut native_options: NativeOptions, + app_creator: AppCreator<'a>, + event_loop: &winit::event_loop::EventLoop, +) -> EframeWinitApplication<'a> { + let renderer = init_native(app_name, &mut native_options); + + match renderer { + #[cfg(feature = "glow")] + Renderer::Glow => { + log::debug!("Using the glow renderer"); + EframeWinitApplication::new(native::run::create_glow( + app_name, + native_options, + app_creator, + event_loop, + )) + } + + #[cfg(feature = "wgpu")] + Renderer::Wgpu => { + log::debug!("Using the wgpu renderer"); + EframeWinitApplication::new(native::run::create_wgpu( + app_name, + native_options, + app_creator, + event_loop, + )) + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu"))] +fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { + #[cfg(not(feature = "__screenshot"))] + assert!( + std::env::var("EFRAME_SCREENSHOT_TO").is_err(), + "EFRAME_SCREENSHOT_TO found without compiling with the '__screenshot' feature" + ); + + if native_options.viewport.title.is_none() { + native_options.viewport.title = Some(app_name.to_owned()); + } + + let renderer = native_options.renderer; + + #[cfg(all(feature = "glow", feature = "wgpu"))] + { + match native_options.renderer { + Renderer::Glow => "glow", + Renderer::Wgpu => "wgpu", + }; + log::info!("Both the glow and wgpu renderers are available. Using {renderer}."); + } + + renderer +} + // ---------------------------------------------------------------------------- /// The simplest way to get started when writing a native app. diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 9bcb49686..8edfdbe2e 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -362,6 +362,19 @@ pub fn run_glow( run_and_exit(event_loop, glow_eframe) } +#[cfg(feature = "glow")] +pub fn create_glow<'a>( + app_name: &str, + native_options: epi::NativeOptions, + app_creator: epi::AppCreator<'a>, + event_loop: &EventLoop, +) -> impl ApplicationHandler + 'a { + use super::glow_integration::GlowWinitApp; + + let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator); + WinitAppWrapper::new(glow_eframe, true) +} + // ---------------------------------------------------------------------------- #[cfg(feature = "wgpu")] @@ -386,3 +399,120 @@ pub fn run_wgpu( let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator); run_and_exit(event_loop, wgpu_eframe) } + +#[cfg(feature = "wgpu")] +pub fn create_wgpu<'a>( + app_name: &str, + native_options: epi::NativeOptions, + app_creator: epi::AppCreator<'a>, + event_loop: &EventLoop, +) -> impl ApplicationHandler + 'a { + use super::wgpu_integration::WgpuWinitApp; + + let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator); + WinitAppWrapper::new(wgpu_eframe, true) +} + +// ---------------------------------------------------------------------------- + +/// A proxy to the eframe application that implements [`ApplicationHandler`]. +/// +/// This can be run directly on your own [`EventLoop`] by itself or with other +/// windows you manage outside of eframe. +pub struct EframeWinitApplication<'a> { + wrapper: Box + 'a>, + control_flow: ControlFlow, +} + +impl ApplicationHandler for EframeWinitApplication<'_> { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + self.wrapper.resumed(event_loop); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + self.wrapper.window_event(event_loop, window_id, event); + } + + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) { + self.wrapper.new_events(event_loop, cause); + } + + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { + self.wrapper.user_event(event_loop, event); + } + + fn device_event( + &mut self, + event_loop: &ActiveEventLoop, + device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + self.wrapper.device_event(event_loop, device_id, event); + } + + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + self.wrapper.about_to_wait(event_loop); + self.control_flow = event_loop.control_flow(); + } + + fn suspended(&mut self, event_loop: &ActiveEventLoop) { + self.wrapper.suspended(event_loop); + } + + fn exiting(&mut self, event_loop: &ActiveEventLoop) { + self.wrapper.exiting(event_loop); + } + + fn memory_warning(&mut self, event_loop: &ActiveEventLoop) { + self.wrapper.memory_warning(event_loop); + } +} + +impl<'a> EframeWinitApplication<'a> { + pub(crate) fn new + 'a>(app: T) -> Self { + Self { + wrapper: Box::new(app), + control_flow: ControlFlow::default(), + } + } + + /// Pump the `EventLoop` to check for and dispatch pending events to this application. + /// + /// Returns either the exit code for the application or the final state of the [`ControlFlow`] + /// after all events have been dispatched in this iteration. + /// + /// This is useful when your [`EventLoop`] is not the main event loop for your application. + /// See the `external_eventloop_async` example. + #[cfg(not(target_os = "ios"))] + pub fn pump_eframe_app( + &mut self, + event_loop: &mut EventLoop, + timeout: Option, + ) -> EframePumpStatus { + use winit::platform::pump_events::{EventLoopExtPumpEvents as _, PumpStatus}; + + match event_loop.pump_app_events(timeout, self) { + PumpStatus::Continue => EframePumpStatus::Continue(self.control_flow), + PumpStatus::Exit(code) => EframePumpStatus::Exit(code), + } + } +} + +/// Either an exit code or a [`ControlFlow`] from the [`ActiveEventLoop`]. +/// +/// The result of [`EframeWinitApplication::pump_eframe_app`]. +#[cfg(not(target_os = "ios"))] +pub enum EframePumpStatus { + /// The final state of the [`ControlFlow`] after all events have been dispatched + /// + /// Callers should perform the action that is appropriate for the [`ControlFlow`] value. + Continue(ControlFlow), + + /// The exit code for the application + Exit(i32), +} diff --git a/examples/external_eventloop/Cargo.toml b/examples/external_eventloop/Cargo.toml new file mode 100644 index 000000000..301f30251 --- /dev/null +++ b/examples/external_eventloop/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "external_eventloop" +version = "0.1.0" +authors = ["Will Brown "] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.84" +publish = false + +[lints] +workspace = true + + +[dependencies] +eframe = { workspace = true, features = [ + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } + +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } + +winit = { workspace = true } diff --git a/examples/external_eventloop/README.md b/examples/external_eventloop/README.md new file mode 100644 index 000000000..11b06389b --- /dev/null +++ b/examples/external_eventloop/README.md @@ -0,0 +1,7 @@ +Example running an eframe application on an external eventloop. + +This allows you to run your eframe application alongside other windows and/or toolkits on the same event loop. + +```sh +cargo run -p external_eventloop +``` diff --git a/examples/external_eventloop/src/main.rs b/examples/external_eventloop/src/main.rs new file mode 100644 index 000000000..178c2865f --- /dev/null +++ b/examples/external_eventloop/src/main.rs @@ -0,0 +1,89 @@ +#![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 std::{cell::Cell, rc::Rc}; +use winit::event_loop::{ControlFlow, EventLoop}; + +fn main() -> eframe::Result { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), + ..Default::default() + }; + + let eventloop = EventLoop::::with_user_event().build().unwrap(); + eventloop.set_control_flow(ControlFlow::Poll); + + let mut winit_app = eframe::create_native( + "External Eventloop Application", + options, + Box::new(|_| Ok(Box::::default())), + &eventloop, + ); + + eventloop.run_app(&mut winit_app)?; + + Ok(()) +} + +struct MyApp { + value: Rc>, + spin: bool, + blinky: bool, +} + +impl Default for MyApp { + fn default() -> Self { + Self { + value: Rc::new(Cell::new(42)), + spin: false, + blinky: false, + } + } +} + +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("My External Eventloop Application"); + + ui.horizontal(|ui| { + if ui.button("Increment Now").clicked() { + self.value.set(self.value.get() + 1); + } + }); + ui.label(format!("Value: {}", self.value.get())); + + if ui.button("Toggle Spinner").clicked() { + self.spin = !self.spin; + } + + if ui.button("Toggle Blinky").clicked() { + self.blinky = !self.blinky; + } + + if self.spin { + ui.spinner(); + } + + if self.blinky { + let now = ui.ctx().input(|i| i.time); + let blink = now % 1.0 < 0.5; + egui::Frame::new() + .inner_margin(3) + .corner_radius(5) + .fill(if blink { + egui::Color32::RED + } else { + egui::Color32::TRANSPARENT + }) + .show(ui, |ui| { + ui.label("Blinky!"); + }); + + ctx.request_repaint_after_secs((0.5 - (now % 0.5)) as f32); + } + }); + } +} diff --git a/examples/external_eventloop_async/Cargo.toml b/examples/external_eventloop_async/Cargo.toml new file mode 100644 index 000000000..399ff7c39 --- /dev/null +++ b/examples/external_eventloop_async/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "external_eventloop_async" +version = "0.1.0" +authors = ["Will Brown "] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.84" +publish = false + +[lints] +workspace = true + +[features] +linux-example = [] + +[[bin]] +name = "external_eventloop_async" +required-features = ["linux-example"] + +[dependencies] +eframe = { workspace = true, features = [ + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } + +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } + +log = { workspace = true } + +winit = { workspace = true } + +tokio = { version = "1", features = ["rt", "time", "net"] } diff --git a/examples/external_eventloop_async/README.md b/examples/external_eventloop_async/README.md new file mode 100644 index 000000000..37755e08d --- /dev/null +++ b/examples/external_eventloop_async/README.md @@ -0,0 +1,10 @@ +Example running an eframe application on an external eventloop on top of a tokio executor on Linux. + +By running the event loop, eframe, and tokio in the same thread, one can leverage local async tasks. +These tasks can share data with the UI without the need for locks or message passing. + +In tokio CPU-bound async tasks can be run with `spawn_blocking` to avoid impacting the UI frame rate. + +```sh +cargo run -p external_eventloop_async --features linux-example +``` diff --git a/examples/external_eventloop_async/src/app.rs b/examples/external_eventloop_async/src/app.rs new file mode 100644 index 000000000..de3326b19 --- /dev/null +++ b/examples/external_eventloop_async/src/app.rs @@ -0,0 +1,130 @@ +use eframe::{egui, EframePumpStatus, UserEvent}; +use std::{cell::Cell, io, os::fd::AsRawFd as _, rc::Rc, time::Duration}; +use tokio::task::LocalSet; +use winit::event_loop::{ControlFlow, EventLoop}; + +pub fn run() -> io::Result<()> { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), + ..Default::default() + }; + + let mut eventloop = EventLoop::::with_user_event().build().unwrap(); + eventloop.set_control_flow(ControlFlow::Poll); + + let mut winit_app = eframe::create_native( + "External Eventloop Application", + options, + Box::new(|_| Ok(Box::::default())), + &eventloop, + ); + + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + let local = LocalSet::new(); + local.block_on(&rt, async { + let eventloop_fd = tokio::io::unix::AsyncFd::new(eventloop.as_raw_fd())?; + let mut control_flow = ControlFlow::Poll; + + loop { + let mut guard = match control_flow { + ControlFlow::Poll => None, + ControlFlow::Wait => Some(eventloop_fd.readable().await?), + ControlFlow::WaitUntil(deadline) => { + tokio::time::timeout_at(deadline.into(), eventloop_fd.readable()) + .await + .ok() + .transpose()? + } + }; + + match winit_app.pump_eframe_app(&mut eventloop, None) { + EframePumpStatus::Continue(next) => control_flow = next, + EframePumpStatus::Exit(code) => { + log::info!("exit code: {code}"); + break; + } + } + + if let Some(mut guard) = guard.take() { + guard.clear_ready(); + } + } + + Ok::<_, io::Error>(()) + }) +} + +struct MyApp { + value: Rc>, + spin: bool, + blinky: bool, +} + +impl Default for MyApp { + fn default() -> Self { + Self { + value: Rc::new(Cell::new(42)), + spin: false, + blinky: false, + } + } +} + +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("My External Eventloop Application"); + + ui.horizontal(|ui| { + if ui.button("Increment Now").clicked() { + self.value.set(self.value.get() + 1); + } + if ui.button("Increment Later").clicked() { + let value = self.value.clone(); + let ctx = ctx.clone(); + tokio::task::spawn_local(async move { + tokio::time::sleep(Duration::from_secs(1)).await; + value.set(value.get() + 1); + ctx.request_repaint(); + }); + } + }); + ui.label(format!("Value: {}", self.value.get())); + + if ui.button("Toggle Spinner").clicked() { + self.spin = !self.spin; + } + + if ui.button("Toggle Blinky").clicked() { + self.blinky = !self.blinky; + } + + if self.spin { + ui.spinner(); + } + + if self.blinky { + let now = ui.ctx().input(|i| i.time); + let blink = now % 1.0 < 0.5; + egui::Frame::new() + .inner_margin(3) + .corner_radius(5) + .fill(if blink { + egui::Color32::RED + } else { + egui::Color32::TRANSPARENT + }) + .show(ui, |ui| { + ui.label("Blinky!"); + }); + + ctx.request_repaint_after_secs((0.5 - (now % 0.5)) as f32); + } + }); + } +} diff --git a/examples/external_eventloop_async/src/main.rs b/examples/external_eventloop_async/src/main.rs new file mode 100644 index 000000000..bbb52084f --- /dev/null +++ b/examples/external_eventloop_async/src/main.rs @@ -0,0 +1,15 @@ +#![allow(rustdoc::missing_crate_level_docs)] // it's an example + +#[cfg(target_os = "linux")] +mod app; + +#[cfg(target_os = "linux")] +fn main() -> std::io::Result<()> { + app::run() +} + +// Do not check `app` on unsupported platforms when check "--all-features" is used in CI. +#[cfg(not(target_os = "linux"))] +fn main() { + println!("This example only supports Linux."); +} From 1ab325900878dd41794d033bd30b49a6f14c937e Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 30 Apr 2025 10:38:41 +0200 Subject: [PATCH 037/388] Add italic button benchmark to test `RichText` performance impact (#6897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Time on my m4 pro macbook: 302.79 ns (vs 303.83 ns for the regular button 🤷) --- crates/egui_demo_lib/benches/benchmark.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 331788c9b..2b9a2cc05 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -4,7 +4,7 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use egui::epaint::TextShape; use egui::load::SizedTexture; -use egui::{Button, Id, TextureId, Ui, UiBuilder, Vec2}; +use egui::{Button, Id, RichText, TextureId, Ui, UiBuilder, Vec2}; use egui_demo_lib::LOREM_IPSUM_LONG; use rand::Rng as _; @@ -121,6 +121,15 @@ pub fn criterion_benchmark(c: &mut Criterion) { BatchSize::LargeInput, ); }); + group.bench_function("4_button_italic", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { + ui.add(Button::new(RichText::new("Hello World").italics())); + }, + BatchSize::LargeInput, + ); + }); }); } From fdaac16e4a5515472f01de276a5d5fc39b5af737 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 30 Apr 2025 10:40:50 +0200 Subject: [PATCH 038/388] Fix image button panicking with tiny `available_space` (#6900) * Fixes * [x] I have followed the instructions in the PR template --- crates/egui/src/widgets/button.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 2a85a164d..a75997eff 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -249,7 +249,7 @@ impl Widget for Button<'_> { ) } else { ( - ui.available_size() - 2.0 * button_padding, + (ui.available_size() - 2.0 * button_padding).at_least(Vec2::ZERO), default_font_height(), ) }; From 2947821c60f4ef474194695bcd3e71c9def380cc Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 30 Apr 2025 12:55:57 +0200 Subject: [PATCH 039/388] Load images on the ui thread for tests (#6901) https://github.com/emilk/egui/pull/5394 made it so images would load on a background thread, which is great. But this makes snapshot tests that have images via include_image!() flakey since they might or might not load by the time the snapshot is rendered. This is no perfect solution, since the underlying problem of "waiting for something async to happen" still exists and we should add some more general solution for that. --- crates/egui_extras/src/loaders/image_loader.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index bb025651d..2528c2aec 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -8,9 +8,6 @@ use egui::{ use image::ImageFormat; use std::{mem::size_of, path::Path, sync::Arc, task::Poll}; -#[cfg(not(target_arch = "wasm32"))] -use std::thread; - type Entry = Poll, String>>; #[derive(Default)] @@ -76,7 +73,7 @@ impl ImageLoader for ImageCrateLoader { return Err(LoadError::NotSupported); } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(any(target_arch = "wasm32", test)))] #[expect(clippy::unnecessary_wraps)] // needed here to match other return types fn load_image( ctx: &egui::Context, @@ -88,7 +85,7 @@ impl ImageLoader for ImageCrateLoader { cache.lock().insert(uri.clone(), Poll::Pending); // Do the image parsing on a bg thread - thread::Builder::new() + std::thread::Builder::new() .name(format!("egui_extras::ImageLoader::load({uri:?})")) .spawn({ let ctx = ctx.clone(); @@ -116,7 +113,8 @@ impl ImageLoader for ImageCrateLoader { Ok(ImagePoll::Pending { size: None }) } - #[cfg(target_arch = "wasm32")] + // Load images on the current thread for tests, so they are less flaky + #[cfg(any(target_arch = "wasm32", test))] fn load_image( _ctx: &egui::Context, uri: &str, From f3611e3e5a448ba8a96cb880ea3a29245bb3a2d2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 30 Apr 2025 14:10:59 +0200 Subject: [PATCH 040/388] Enforce that PRs are not opened from the 'master' branch of a fork (#6899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Fail all PRs that are opened from the master/main branch of the fork. ## Why PR:s opened from the `master` branch cannot be collaborated on. That is, we maintainers cannot push our own commits to it (e.g. to fix smaller problems with it before merging). ## How Untested code straight from Claude 3.7 😅 --- .github/workflows/enforce_branch_name.yml | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/enforce_branch_name.yml diff --git a/.github/workflows/enforce_branch_name.yml b/.github/workflows/enforce_branch_name.yml new file mode 100644 index 000000000..7868426cb --- /dev/null +++ b/.github/workflows/enforce_branch_name.yml @@ -0,0 +1,34 @@ +name: PR Branch Name Check + +on: + pull_request_target: + types: [opened, reopened, synchronize] + +jobs: + check-source-branch: + runs-on: ubuntu-latest + steps: + - name: Leave comment if PR is from master/main branch of fork + if: ${{ github.event.pull_request.head.repo.fork == 'true' && (github.event.pull_request.head.ref == 'master' || github.event.pull_request.head.ref == 'main') }} + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '⚠️ **ERROR:** Pull requests from the `master`/`main` branch of forks are not allowed, because it prevents maintainers from contributing to your PR. Please create a feature branch in your fork and submit the PR from that branch instead.' + }) + + - name: Check PR source branch + run: | + # Check if PR is from a fork + if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then + # Check if PR is from the master/main branch of a fork + if [[ "${{ github.event.pull_request.head.ref }}" == "master" || "${{ github.event.pull_request.head.ref }}" == "main" ]]; then + echo "ERROR: Pull requests from the master/main branch of forks are not allowed, because it prevents maintainers from contributing to your PR" + echo "Please create a feature branch in your fork and submit the PR from that branch instead." + exit 1 + fi + fi From 6c922f72a819e6083ffc4b6a452c2493c9170e63 Mon Sep 17 00:00:00 2001 From: Alexander Nadeau Date: Wed, 30 Apr 2025 08:12:08 -0400 Subject: [PATCH 041/388] Fix text distortion on mobile devices/browsers with `glow` backend (#6893) Did not test on platforms other than my phone, but I can't imagine it causing problems. AFAIK if highp isn't supported then `precision highp float;` needs to still not cause the program to fail to link/compile or anything; it should just silently use some other precision. * Fixes https://github.com/emilk/egui/issues/4268 for me but I only tested it on a native Android app and I don't know whether backends other than glow are affected. * [x] I have followed the instructions in the PR template (but the change is trivial so I'm just doing it from the master branch) Before: ![image](https://github.com/user-attachments/assets/9f449749-5a48-4e9c-aef0-7a8ac3912eb6) After: ![image](https://github.com/user-attachments/assets/544e5977-13e0-411a-bccf-b15a15289e28) --- crates/egui_glow/src/shader/fragment.glsl | 8 +++++++- crates/egui_glow/src/shader/vertex.glsl | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/egui_glow/src/shader/fragment.glsl b/crates/egui_glow/src/shader/fragment.glsl index 30da2809e..f2792ed04 100644 --- a/crates/egui_glow/src/shader/fragment.glsl +++ b/crates/egui_glow/src/shader/fragment.glsl @@ -1,5 +1,11 @@ #ifdef GL_ES - precision mediump float; + // To avoid weird distortion issues when rendering text etc, we want highp if possible. + // But apparently some devices don't support it, so we have to check first. + #if defined(GL_FRAGMENT_PRECISION_HIGH) && GL_FRAGMENT_PRECISION_HIGH == 1 + precision highp float; + #else + precision mediump float; + #endif #endif uniform sampler2D u_sampler; diff --git a/crates/egui_glow/src/shader/vertex.glsl b/crates/egui_glow/src/shader/vertex.glsl index fff31463c..0c6e9d231 100644 --- a/crates/egui_glow/src/shader/vertex.glsl +++ b/crates/egui_glow/src/shader/vertex.glsl @@ -9,7 +9,13 @@ #endif #ifdef GL_ES - precision mediump float; + // To avoid weird distortion issues when rendering text etc, we want highp if possible. + // But apparently some devices don't support it, so we have to check first. + #if defined(GL_FRAGMENT_PRECISION_HIGH) && GL_FRAGMENT_PRECISION_HIGH == 1 + precision highp float; + #else + precision mediump float; + #endif #endif uniform vec2 u_screen_size; From ba70106399645cdf66a36736137dc135a843f830 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 6 May 2025 10:25:02 +0200 Subject: [PATCH 042/388] Fix enforce_branch_name.yml --- .github/workflows/enforce_branch_name.yml | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/enforce_branch_name.yml b/.github/workflows/enforce_branch_name.yml index 7868426cb..85035330e 100644 --- a/.github/workflows/enforce_branch_name.yml +++ b/.github/workflows/enforce_branch_name.yml @@ -8,19 +8,6 @@ jobs: check-source-branch: runs-on: ubuntu-latest steps: - - name: Leave comment if PR is from master/main branch of fork - if: ${{ github.event.pull_request.head.repo.fork == 'true' && (github.event.pull_request.head.ref == 'master' || github.event.pull_request.head.ref == 'main') }} - uses: actions/github-script@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '⚠️ **ERROR:** Pull requests from the `master`/`main` branch of forks are not allowed, because it prevents maintainers from contributing to your PR. Please create a feature branch in your fork and submit the PR from that branch instead.' - }) - - name: Check PR source branch run: | # Check if PR is from a fork @@ -32,3 +19,16 @@ jobs: exit 1 fi fi + + - name: Leave comment if PR is from master/main branch of fork d + if: ${{ failure() }} + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '⚠️ **ERROR:** Pull requests from the `master`/`main` branch of forks are not allowed, because it prevents maintainers from contributing to your PR. Please create a feature branch in your fork and submit the PR from that branch instead.' + }) From 71e0b0859cd3dd9cf362f39345ed6a66c3889032 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 6 May 2025 17:35:56 +0200 Subject: [PATCH 043/388] Make `WidgetText` smaller and faster (#6903) * In preparation of #5830, this should reduce the performance impact of that PR --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widget_text.rs | 202 ++++++++++++-------- crates/egui/src/widgets/label.rs | 14 +- crates/egui_demo_app/src/wrap_app.rs | 2 +- crates/epaint/src/text/text_layout_types.rs | 15 ++ 4 files changed, 150 insertions(+), 83 deletions(-) diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index e66cb1bc8..d9f98859b 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,6 +1,7 @@ use std::{borrow::Cow, sync::Arc}; use emath::GuiRounding as _; +use epaint::text::TextFormat; use crate::{ text::{LayoutJob, TextWrapping}, @@ -488,7 +489,16 @@ impl RichText { /// which will be replaced with a color chosen by the widget that paints the text. #[derive(Clone)] pub enum WidgetText { - RichText(RichText), + /// Plain unstyled text. + /// + /// We have this as a special case, as it is the common-case, + /// and it uses less memory than [`Self::RichText`]. + Text(String), + + /// Text and optional style choices for it. + /// + /// Prefer [`Self::Text`] if there is no styling, as it will be faster. + RichText(Arc), /// Use this [`LayoutJob`] when laying out the text. /// @@ -502,7 +512,7 @@ pub enum WidgetText { /// /// You can color the text however you want, or use [`Color32::PLACEHOLDER`] /// which will be replaced with a color chosen by the widget that paints the text. - LayoutJob(LayoutJob), + LayoutJob(Arc), /// Use exactly this galley when painting the text. /// @@ -513,7 +523,7 @@ pub enum WidgetText { impl Default for WidgetText { fn default() -> Self { - Self::RichText(RichText::default()) + Self::Text(String::new()) } } @@ -521,6 +531,7 @@ impl WidgetText { #[inline] pub fn is_empty(&self) -> bool { match self { + Self::Text(text) => text.is_empty(), Self::RichText(text) => text.is_empty(), Self::LayoutJob(job) => job.is_empty(), Self::Galley(galley) => galley.is_empty(), @@ -530,21 +541,36 @@ impl WidgetText { #[inline] pub fn text(&self) -> &str { match self { + Self::Text(text) => text, Self::RichText(text) => text.text(), Self::LayoutJob(job) => &job.text, Self::Galley(galley) => galley.text(), } } + /// Map the contents based on the provided closure. + /// + /// - [`Self::Text`] => convert to [`RichText`] and call f + /// - [`Self::RichText`] => call f + /// - else do nothing + #[must_use] + fn map_rich_text(self, f: F) -> Self + where + F: FnOnce(RichText) -> RichText, + { + match self { + Self::Text(text) => Self::RichText(Arc::new(f(RichText::new(text)))), + Self::RichText(text) => Self::RichText(Arc::new(f(Arc::unwrap_or_clone(text)))), + other => other, + } + } + /// Override the [`TextStyle`] if, and only if, this is a [`RichText`]. /// /// Prefer using [`RichText`] directly! #[inline] pub fn text_style(self, text_style: TextStyle) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.text_style(text_style)), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.text_style(text_style)) } /// Set the [`TextStyle`] unless it has already been set @@ -552,10 +578,7 @@ impl WidgetText { /// Prefer using [`RichText`] directly! #[inline] pub fn fallback_text_style(self, text_style: TextStyle) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.fallback_text_style(text_style)), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.fallback_text_style(text_style)) } /// Override text color if, and only if, this is a [`RichText`]. @@ -563,111 +586,85 @@ impl WidgetText { /// Prefer using [`RichText`] directly! #[inline] pub fn color(self, color: impl Into) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.color(color)), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.color(color)) } /// Prefer using [`RichText`] directly! + #[inline] pub fn heading(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.heading()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.heading()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn monospace(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.monospace()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.monospace()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn code(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.code()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.code()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn strong(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.strong()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.strong()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn weak(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.weak()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.weak()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn underline(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.underline()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.underline()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn strikethrough(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.strikethrough()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.strikethrough()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn italics(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.italics()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.italics()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn small(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.small()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.small()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn small_raised(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.small_raised()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.small_raised()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn raised(self) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.raised()), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.raised()) } /// Prefer using [`RichText`] directly! + #[inline] pub fn background_color(self, background_color: impl Into) -> Self { - match self { - Self::RichText(text) => Self::RichText(text.background_color(background_color)), - Self::LayoutJob(_) | Self::Galley(_) => self, - } + self.map_rich_text(|text| text.background_color(background_color)) } /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub(crate) fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 { match self { + Self::Text(_) => fonts.row_height(&FontSelection::Default.resolve(style)), Self::RichText(text) => text.font_height(fonts, style), Self::LayoutJob(job) => job.font_height(fonts), Self::Galley(galley) => { @@ -685,11 +682,24 @@ impl WidgetText { style: &Style, fallback_font: FontSelection, default_valign: Align, - ) -> LayoutJob { + ) -> Arc { match self { - Self::RichText(text) => text.into_layout_job(style, fallback_font, default_valign), + Self::Text(text) => Arc::new(LayoutJob::simple_format( + text, + TextFormat { + font_id: FontSelection::Default.resolve(style), + color: crate::Color32::PLACEHOLDER, + valign: default_valign, + ..Default::default() + }, + )), + Self::RichText(text) => Arc::new(Arc::unwrap_or_clone(text).into_layout_job( + style, + fallback_font, + default_valign, + )), Self::LayoutJob(job) => job, - Self::Galley(galley) => (*galley.job).clone(), + Self::Galley(galley) => galley.job.clone(), } } @@ -721,12 +731,30 @@ impl WidgetText { default_valign: Align, ) -> Arc { match self { - Self::RichText(text) => { - let mut layout_job = text.into_layout_job(style, fallback_font, default_valign); + Self::Text(text) => { + let mut layout_job = LayoutJob::simple_format( + text, + TextFormat { + font_id: FontSelection::Default.resolve(style), + color: crate::Color32::PLACEHOLDER, + valign: default_valign, + ..Default::default() + }, + ); layout_job.wrap = text_wrapping; ctx.fonts(|f| f.layout_job(layout_job)) } - Self::LayoutJob(mut job) => { + Self::RichText(text) => { + let mut layout_job = Arc::unwrap_or_clone(text).into_layout_job( + style, + fallback_font, + default_valign, + ); + layout_job.wrap = text_wrapping; + ctx.fonts(|f| f.layout_job(layout_job)) + } + Self::LayoutJob(job) => { + let mut job = Arc::unwrap_or_clone(job); job.wrap = text_wrapping; ctx.fonts(|f| f.layout_job(job)) } @@ -738,48 +766,55 @@ impl WidgetText { impl From<&str> for WidgetText { #[inline] fn from(text: &str) -> Self { - Self::RichText(RichText::new(text)) + Self::Text(text.to_owned()) } } impl From<&String> for WidgetText { #[inline] fn from(text: &String) -> Self { - Self::RichText(RichText::new(text)) + Self::Text(text.clone()) } } impl From for WidgetText { #[inline] fn from(text: String) -> Self { - Self::RichText(RichText::new(text)) + Self::Text(text) } } impl From<&Box> for WidgetText { #[inline] fn from(text: &Box) -> Self { - Self::RichText(RichText::new(text.clone())) + Self::Text(text.to_string()) } } impl From> for WidgetText { #[inline] fn from(text: Box) -> Self { - Self::RichText(RichText::new(text)) + Self::Text(text.into()) } } impl From> for WidgetText { #[inline] fn from(text: Cow<'_, str>) -> Self { - Self::RichText(RichText::new(text)) + Self::Text(text.into_owned()) } } impl From for WidgetText { #[inline] fn from(rich_text: RichText) -> Self { + Self::RichText(Arc::new(rich_text)) + } +} + +impl From> for WidgetText { + #[inline] + fn from(rich_text: Arc) -> Self { Self::RichText(rich_text) } } @@ -787,6 +822,13 @@ impl From for WidgetText { impl From for WidgetText { #[inline] fn from(layout_job: LayoutJob) -> Self { + Self::LayoutJob(Arc::new(layout_job)) + } +} + +impl From> for WidgetText { + #[inline] + fn from(layout_job: Arc) -> Self { Self::LayoutJob(layout_job) } } @@ -797,3 +839,13 @@ impl From> for WidgetText { Self::Galley(galley) } } + +#[cfg(test)] +mod tests { + use crate::WidgetText; + + #[test] + fn ensure_small_widget_text() { + assert_eq!(size_of::(), size_of::()); + } +} diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 3656af92b..d90bdf963 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -1,12 +1,10 @@ use std::sync::Arc; use crate::{ - epaint, pos2, text_selection, Align, Direction, FontSelection, Galley, Pos2, Response, Sense, - Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, + epaint, pos2, text_selection::LabelSelectionState, Align, Direction, FontSelection, Galley, + Pos2, Response, Sense, Stroke, TextWrapMode, Ui, Widget, WidgetInfo, WidgetText, WidgetType, }; -use self::text_selection::LabelSelectionState; - /// Static text. /// /// Usually it is more convenient to use [`Ui::label`]. @@ -182,9 +180,11 @@ impl Label { } let valign = ui.text_valign(); - let mut layout_job = self - .text - .into_layout_job(ui.style(), FontSelection::Default, valign); + let mut layout_job = Arc::unwrap_or_clone(self.text.into_layout_job( + ui.style(), + FontSelection::Default, + valign, + )); let available_width = ui.available_width(); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index ea5fbfaba..3775d93d2 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -134,7 +134,7 @@ impl std::fmt::Display for Anchor { impl From for egui::WidgetText { fn from(value: Anchor) -> Self { - Self::RichText(egui::RichText::new(value.to_string())) + Self::from(value.to_string()) } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 49ec29087..795b9c9f4 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -118,6 +118,21 @@ impl LayoutJob { } } + /// Break on `\n` + #[inline] + pub fn simple_format(text: String, format: TextFormat) -> Self { + Self { + sections: vec![LayoutSection { + leading_space: 0.0, + byte_range: 0..text.len(), + format, + }], + text, + break_on_newline: true, + ..Default::default() + } + } + /// Does not break on `\n`, but shows the replacement character instead. #[inline] pub fn simple_singleline(text: String, font_id: FontId, color: Color32) -> Self { From 5bb20f511e1e3bbaf7d0d16761fe8b6fa9c419a2 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 6 May 2025 17:40:18 +0200 Subject: [PATCH 044/388] Fix links and text selection in horizontal_wrapped layout (#6905) * Closes * [x] I have followed the instructions in the PR template This was broken in https://github.com/emilk/egui/pull/5411. Not sure if this is the best fix or if `PlacedRow::rect` should be updated, but I think it makes sense that PlacedRow::rect ignores leading space. --- crates/egui/src/text_selection/accesskit_text.rs | 2 +- crates/egui/src/widgets/label.rs | 4 +++- crates/epaint/src/text/text_layout_types.rs | 10 +++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index de193e3b0..e04a54d18 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -45,7 +45,7 @@ pub fn update_accesskit_for_text_widget( let row_id = parent_id.with(row_index); ctx.accesskit_node_builder(row_id, |builder| { builder.set_role(accesskit::Role::TextRun); - let rect = global_from_galley * row.rect(); + let rect = global_from_galley * row.rect_without_leading_space(); builder.set_bounds(accesskit::Rect { x0: rect.min.x.into(), y0: rect.min.y.into(), diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index d90bdf963..9f3606d12 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -216,7 +216,9 @@ impl Label { let pos = pos2(ui.max_rect().left(), ui.cursor().top()); assert!(!galley.rows.is_empty(), "Galleys are never empty"); // collect a response from many rows: - let rect = galley.rows[0].rect().translate(pos.to_vec2()); + let rect = galley.rows[0] + .rect_without_leading_space() + .translate(pos.to_vec2()); let mut response = ui.allocate_rect(rect, sense); for placed_row in galley.rows.iter().skip(1) { let rect = placed_row.rect().translate(pos.to_vec2()); diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 795b9c9f4..6b7863426 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -576,10 +576,18 @@ pub struct PlacedRow { impl PlacedRow { /// Logical bounding rectangle on font heights etc. - /// Use this when drawing a selection or similar! + /// + /// This ignores / includes the `LayoutSection::leading_space`. pub fn rect(&self) -> Rect { Rect::from_min_size(self.pos, self.row.size) } + + /// Same as [`Self::rect`] but excluding the `LayoutSection::leading_space`. + pub fn rect_without_leading_space(&self) -> Rect { + let x = self.glyphs.first().map_or(self.pos.x, |g| g.pos.x); + let size_x = self.size.x - x; + Rect::from_min_size(Pos2::new(x, self.pos.y), Vec2::new(size_x, self.size.y)) + } } impl std::ops::Deref for PlacedRow { From 7216d0e38633e3c30f6ed90b2577a86c56512765 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 6 May 2025 17:54:06 +0200 Subject: [PATCH 045/388] Use mimalloc for benchmarks (#7029) `mimalloc` is a _much_ faster allocator, especially important when doing a lot of small allocations (which egui does). We use `mimalloc` in Rerun, and I recommend everyone to use it. ## The difference it makes ![image](https://github.com/user-attachments/assets/b22e0025-bc5e-4b3c-94e0-74ce46e86f85) --- Cargo.lock | 22 ++++++++++++++++++++++ Cargo.toml | 1 + crates/egui/src/lib.rs | 3 +++ crates/egui_demo_app/Cargo.toml | 1 + crates/egui_demo_app/src/main.rs | 3 +++ crates/egui_demo_lib/Cargo.toml | 3 ++- crates/egui_demo_lib/benches/benchmark.rs | 3 +++ crates/epaint/Cargo.toml | 1 + crates/epaint/benches/benchmark.rs | 3 +++ 9 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 35ff4c9af..8354ad599 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1336,6 +1336,7 @@ dependencies = [ "env_logger", "image", "log", + "mimalloc", "poll-promise", "profiling", "puffin", @@ -1358,6 +1359,7 @@ dependencies = [ "egui", "egui_extras", "egui_kittest", + "mimalloc", "rand", "serde", "unicode_names2", @@ -1559,6 +1561,7 @@ dependencies = [ "emath", "epaint_default_fonts", "log", + "mimalloc", "nohash-hasher", "parking_lot", "profiling", @@ -2486,6 +2489,16 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libmimalloc-sys" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libredox" version = "0.1.3" @@ -2591,6 +2604,15 @@ dependencies = [ "paste", ] +[[package]] +name = "mimalloc" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "mime" version = "0.3.17" diff --git a/Cargo.toml b/Cargo.toml index 3b306176b..d3adbd90a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ 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" } log = { version = "0.4", features = ["std"] } +mimalloc = "0.1.46" nohash-hasher = "0.2" parking_lot = "0.12" pollster = "0.4" diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index f8842a1cd..1025e7763 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -400,6 +400,9 @@ //! profile-with-puffin = ["profiling/profile-with-puffin"] //! ``` //! +//! ## Custom allocator +//! egui apps can run significantly (~20%) faster by using a custom allocator, like [mimalloc](https://crates.io/crates/mimalloc) or [talc](https://crates.io/crates/talc). +//! #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index b3eeb565d..5868ed481 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -86,6 +86,7 @@ env_logger = { version = "0.10", default-features = false, features = [ "auto-color", "humantime", ] } +mimalloc.workspace = true rfd = { version = "0.15.3", optional = true } # web: diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 476ffdacd..cf391ee99 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -4,6 +4,9 @@ #![allow(rustdoc::missing_crate_level_docs)] // it's an example #![allow(clippy::never_loop)] // False positive +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; // Much faster allocator, can give 20% speedups: https://github.com/emilk/egui/pull/7029 + // When compiling natively: fn main() -> eframe::Result { for arg in std::env::args().skip(1) { diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index 77b8fdcb3..61b35f2e6 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -56,8 +56,9 @@ serde = { workspace = true, optional = true } [dev-dependencies] criterion.workspace = true -egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } egui = { workspace = true, features = ["default_fonts"] } +egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } +mimalloc.workspace = true # for benchmarks rand = "0.9" [[bench]] diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 2b9a2cc05..b511f0de8 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -8,6 +8,9 @@ use egui::{Button, Id, RichText, TextureId, Ui, UiBuilder, Vec2}; use egui_demo_lib::LOREM_IPSUM_LONG; use rand::Rng as _; +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; // Much faster allocator + /// Each iteration should be called in their own `Ui` with an intentional id clash, /// to prevent the Context from building a massive map of `WidgetRects` (which would slow the test, /// causing unreliable results). diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index b8b006d44..0dccd2256 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -101,6 +101,7 @@ backtrace = { workspace = true, optional = true } [dev-dependencies] criterion.workspace = true +mimalloc.workspace = true similar-asserts.workspace = true diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 444f81a11..14f4d2fa7 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -5,6 +5,9 @@ use epaint::{ TessellationOptions, Tessellator, TextureAtlas, Vec2, }; +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; // Much faster allocator + fn single_dashed_lines(c: &mut Criterion) { c.bench_function("single_dashed_lines", move |b| { b.iter(|| { From d0876a1a60fa79883a7fd047fed7ddb9ad2791c7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 8 May 2025 09:15:42 +0200 Subject: [PATCH 046/388] Rename `master` branch to `main` (#7034) For consistency with other repositories, i.e. so I can write `git checkout main` without worrying which repo I'm browsing. --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +-- .github/pull_request_template.md | 2 +- .github/workflows/deploy_web_demo.yml | 4 +- .github/workflows/png_only_on_lfs.yml | 2 +- CHANGELOG.md | 4 +- README.md | 42 +++++++++---------- RELEASES.md | 6 +-- crates/eframe/Cargo.toml | 8 ++-- crates/eframe/README.md | 8 ++-- crates/eframe/src/epi.rs | 2 +- crates/eframe/src/lib.rs | 2 +- crates/egui-wgpu/Cargo.toml | 4 +- crates/egui-wgpu/src/renderer.rs | 2 +- crates/egui-winit/Cargo.toml | 4 +- crates/egui/examples/README.md | 4 +- crates/egui/src/context.rs | 2 +- crates/egui/src/drag_and_drop.rs | 2 +- crates/egui/src/lib.rs | 8 ++-- crates/egui/src/viewport.rs | 2 +- crates/egui_demo_app/README.md | 4 +- crates/egui_demo_app/src/apps/http_app.rs | 4 +- crates/egui_demo_app/src/backend_panel.rs | 2 +- crates/egui_demo_lib/Cargo.toml | 4 +- crates/egui_demo_lib/src/demo/password.rs | 2 +- .../egui_demo_lib/src/demo/toggle_switch.rs | 2 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- crates/egui_demo_lib/src/lib.rs | 4 +- crates/egui_glow/Cargo.toml | 4 +- crates/egui_glow/README.md | 4 +- crates/egui_glow/src/painter.rs | 2 +- crates/egui_kittest/src/snapshot.rs | 2 +- crates/egui_web/README.md | 2 +- crates/emath/Cargo.toml | 4 +- crates/epaint/Cargo.toml | 4 +- crates/epaint_default_fonts/Cargo.toml | 4 +- examples/README.md | 4 +- web_demo/README.md | 2 +- 37 files changed, 85 insertions(+), 85 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 66c17862f..e7c1d6bc3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,13 +10,13 @@ assignees: '' **Describe the bug** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 58fcd32ad..fb9926e3a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,5 @@ --- crates/eframe/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 78208eb53..f0f392256 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -209,7 +209,7 @@ pub mod icon_data; /// This is how you start a native (desktop) app. /// -/// The first argument is name of your app, which is a an identifier +/// The first argument is name of your app, which is an identifier /// used for the save location of persistence (see [`App::save`]). /// It is also used as the application id on wayland. /// If you set no title on the viewport, the app id will be used From 417fdb1a43c7a1118126527f33825dc8162f0ed6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 3 Jun 2025 07:59:02 -0700 Subject: [PATCH 069/388] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeab0c72a..09faecc52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -326,7 +326,7 @@ There also has been several small improvements to the look of egui: * The `extra_asserts` and `extra_debug_asserts` feature flags have been removed ([#4478](https://github.com/emilk/egui/pull/4478)) * Remove `Event::Scroll` and handle it in egui. Use `Event::MouseWheel` instead ([#4524](https://github.com/emilk/egui/pull/4524)) * `Event::Zoom` is no longer emitted on ctrl+scroll. Use `InputState::smooth_scroll_delta` instead ([#4524](https://github.com/emilk/egui/pull/4524)) -* `ui.set_enabled` and `set_visbile` have been deprecated ([#4614](https://github.com/emilk/egui/pull/4614)) +* `ui.set_enabled` and `set_visible` have been deprecated ([#4614](https://github.com/emilk/egui/pull/4614)) * `DragValue::clamp_range` renamed to `range` (([#4728](https://github.com/emilk/egui/pull/4728)) ### ⭐ Added From 6d04140736ddfbd1e406195de6804f57f1406321 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 4 Jun 2025 10:10:47 +0200 Subject: [PATCH 070/388] Fix update from ci script on linux (#7113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently MacOS is case insensitive 😬 --- scripts/update_snapshots_from_ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/update_snapshots_from_ci.sh b/scripts/update_snapshots_from_ci.sh index 755399d1f..c42ffb0fd 100755 --- a/scripts/update_snapshots_from_ci.sh +++ b/scripts/update_snapshots_from_ci.sh @@ -2,6 +2,7 @@ # This script searches for the last CI run with your branch name, downloads the test_results artefact # 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 set -eu @@ -9,7 +10,7 @@ BRANCH=$(git rev-parse --abbrev-ref HEAD) RUN_ID=$(gh run list --branch "$BRANCH" --workflow "Rust" --json databaseId -q '.[0].databaseId') -ECHO "Downloading test results from run $RUN_ID from branch $BRANCH" +echo "Downloading test results from run $RUN_ID from branch $BRANCH" # remove any existing .new.png that might have been left behind find . -type d -path "*/tests/snapshots*" | while read dir; do From 96816449364b052ae8aac82c7d74739efa32c5f7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 18:25:19 +0200 Subject: [PATCH 071/388] Move all input-related options into `InputOptions` (#7121) --- crates/egui/src/context.rs | 2 +- crates/egui/src/input_state/mod.rs | 106 +++++++++++++++++++---------- crates/egui/src/memory/mod.rs | 37 ---------- 3 files changed, 72 insertions(+), 73 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index b567493ac..4ec5e140a 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -491,7 +491,7 @@ impl ContextImpl { new_raw_input, viewport.repaint.requested_immediate_repaint_prev_pass(), pixels_per_point, - &self.memory.options, + self.memory.options.input_options, ); let screen_rect = viewport.input.screen_rect; diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index a5d6951ce..6b3760159 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -18,9 +18,15 @@ pub use touch_state::MultiTouchInfo; use touch_state::TouchState; /// Options for input state handling. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct InputOptions { + /// Multiplier for the scroll speed when reported in [`crate::MouseWheelUnit::Line`]s. + pub line_scroll_speed: f32, + + /// Controls the speed at which we zoom in when doing ctrl/cmd + scroll. + pub scroll_zoom_speed: f32, + /// After a pointer-down event, if the pointer moves more than this, it won't become a click. pub max_click_dist: f32, @@ -39,7 +45,17 @@ pub struct InputOptions { impl Default for InputOptions { fn default() -> Self { + // TODO(emilk): figure out why these constants need to be different on web and on native (winit). + let is_web = cfg!(target_arch = "wasm32"); + let line_scroll_speed = if is_web { + 8.0 + } else { + 40.0 // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 + }; + Self { + line_scroll_speed, + scroll_zoom_speed: 1.0 / 200.0, max_click_dist: 6.0, max_click_duration: 0.8, max_double_click_delay: 0.3, @@ -51,39 +67,59 @@ impl InputOptions { /// Show the options in the ui. pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { + line_scroll_speed, + scroll_zoom_speed, max_click_dist, max_click_duration, max_double_click_delay, } = self; - crate::containers::CollapsingHeader::new("InputOptions") - .default_open(false) + crate::Grid::new("InputOptions") + .num_columns(2) + .striped(true) .show(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Max click distance"); - ui.add( - crate::DragValue::new(max_click_dist) - .range(0.0..=f32::INFINITY) + ui.label("Line scroll speed"); + ui.add(crate::DragValue::new(line_scroll_speed).range(0.0..=f32::INFINITY)) + .on_hover_text( + "How many lines to scroll with each tick of the mouse wheel", + ); + ui.end_row(); + + ui.label("Scroll zoom speed"); + ui.add( + crate::DragValue::new(scroll_zoom_speed) + .range(0.0..=f32::INFINITY) + .speed(0.001), + ) + .on_hover_text("How fast to zoom with ctrl/cmd + scroll"); + ui.end_row(); + + ui.label("Max click distance"); + ui.add(crate::DragValue::new(max_click_dist).range(0.0..=f32::INFINITY)) + .on_hover_text( + "If the pointer moves more than this, it won't become a click", + ); + ui.end_row(); + + + ui.label("Max click duration"); + ui.add( + crate::DragValue::new(max_click_duration) + .range(0.1..=f64::INFINITY) + .speed(0.1), ) - .on_hover_text("If the pointer moves more than this, it won't become a click"); - }); - ui.horizontal(|ui| { - ui.label("Max click duration"); - ui.add( - crate::DragValue::new(max_click_duration) - .range(0.1..=f64::INFINITY) - .speed(0.1), - ) - .on_hover_text("If the pointer is down for longer than this it will no longer register as a click"); - }); - ui.horizontal(|ui| { - ui.label("Max double click delay"); - ui.add( - crate::DragValue::new(max_double_click_delay) - .range(0.01..=f64::INFINITY) - .speed(0.1), - ) - .on_hover_text("Max time interval for double click to count"); - }); + .on_hover_text( + "If the pointer is down for longer than this it will no longer register as a click", + ); + ui.end_row(); + + ui.label("Max double click delay"); + ui.add( + crate::DragValue::new(max_double_click_delay) + .range(0.01..=f64::INFINITY) + .speed(0.1), + ) + .on_hover_text("Max time interval for double click to count"); + ui.end_row(); }); } } @@ -267,7 +303,7 @@ impl InputState { mut new: RawInput, requested_immediate_repaint_prev_frame: bool, pixels_per_point: f32, - options: &crate::Options, + input_options: InputOptions, ) -> Self { profiling::function_scope!(); @@ -287,7 +323,7 @@ impl InputState { for touch_state in self.touch_states.values_mut() { touch_state.begin_pass(time, &new, self.pointer.interact_pos); } - let pointer = self.pointer.begin_pass(time, &new, options); + let pointer = self.pointer.begin_pass(time, &new, input_options); let mut keys_down = self.keys_down; let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor @@ -320,7 +356,7 @@ impl InputState { } => { let mut delta = match unit { MouseWheelUnit::Point => *delta, - MouseWheelUnit::Line => options.line_scroll_speed * *delta, + MouseWheelUnit::Line => input_options.line_scroll_speed * *delta, MouseWheelUnit::Page => screen_rect.height() * *delta, }; @@ -403,7 +439,7 @@ impl InputState { } zoom_factor_delta *= - (options.scroll_zoom_speed * smooth_scroll_delta_for_zoom).exp(); + (input_options.scroll_zoom_speed * smooth_scroll_delta_for_zoom).exp(); } } @@ -437,7 +473,7 @@ impl InputState { keys_down, events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events raw: new, - input_options: options.input_options.clone(), + input_options, } } @@ -912,12 +948,12 @@ impl PointerState { mut self, time: f64, new: &RawInput, - options: &crate::Options, + input_options: InputOptions, ) -> Self { let was_decidedly_dragging = self.is_decidedly_dragging(); self.time = time; - self.input_options = options.input_options.clone(); + self.input_options = input_options; self.pointer_events.clear(); diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 5bf1a1d8f..e9f7d9f86 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -284,14 +284,6 @@ pub struct Options { /// By default this is `true` in debug builds. pub warn_on_id_clash: bool, - // ------------------------------ - // Input: - /// Multiplier for the scroll speed when reported in [`crate::MouseWheelUnit::Line`]s. - pub line_scroll_speed: f32, - - /// Controls the speed at which we zoom in when doing ctrl/cmd + scroll. - pub scroll_zoom_speed: f32, - /// Options related to input state handling. pub input_options: crate::input_state::InputOptions, @@ -311,14 +303,6 @@ pub struct Options { impl Default for Options { fn default() -> Self { - // TODO(emilk): figure out why these constants need to be different on web and on native (winit). - let is_web = cfg!(target_arch = "wasm32"); - let line_scroll_speed = if is_web { - 8.0 - } else { - 40.0 // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 - }; - Self { dark_style: std::sync::Arc::new(Theme::Dark.default_style()), light_style: std::sync::Arc::new(Theme::Light.default_style()), @@ -335,8 +319,6 @@ impl Default for Options { warn_on_id_clash: cfg!(debug_assertions), // Input: - line_scroll_speed, - scroll_zoom_speed: 1.0 / 200.0, input_options: Default::default(), reduce_texture_memory: false, } @@ -391,9 +373,6 @@ impl Options { screen_reader: _, // needs to come from the integration preload_font_glyphs: _, warn_on_id_clash, - - line_scroll_speed, - scroll_zoom_speed, input_options, reduce_texture_memory, } = self; @@ -448,22 +427,6 @@ impl Options { CollapsingHeader::new("🖱 Input") .default_open(false) .show(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Line scroll speed"); - ui.add(crate::DragValue::new(line_scroll_speed).range(0.0..=f32::INFINITY)) - .on_hover_text( - "How many lines to scroll with each tick of the mouse wheel", - ); - }); - ui.horizontal(|ui| { - ui.label("Scroll zoom speed"); - ui.add( - crate::DragValue::new(scroll_zoom_speed) - .range(0.0..=f32::INFINITY) - .speed(0.001), - ) - .on_hover_text("How fast to zoom with ctrl/cmd + scroll"); - }); input_options.ui(ui); }); From cbd9c603997c46230cf3918699af389b7709b3cb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 18:36:16 +0200 Subject: [PATCH 072/388] Add `Modifiers::matches_any` (#7123) * Part of https://github.com/emilk/egui/issues/7120 --- crates/egui/src/data/input.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index f84bc78c2..00950addb 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -842,6 +842,37 @@ impl Modifiers { self.cmd_ctrl_matches(pattern) } + /// Check if any of the modifiers match exactly. + /// + /// Returns true if the same modifier is pressed in `self` as in `pattern`, + /// for at least one modifier. + /// + /// ## Behavior: + /// ``` + /// # use egui::Modifiers; + /// assert!(Modifiers::CTRL.matches_any(Modifiers::CTRL)); + /// assert!(Modifiers::CTRL.matches_any(Modifiers::CTRL | Modifiers::SHIFT)); + /// assert!((Modifiers::CTRL | Modifiers::SHIFT).matches_any(Modifiers::CTRL)); + /// ``` + pub fn matches_any(&self, pattern: Self) -> bool { + if self.alt && pattern.alt { + return true; + } + if self.shift && pattern.shift { + return true; + } + if self.ctrl && pattern.ctrl { + return true; + } + if self.mac_cmd && pattern.mac_cmd { + return true; + } + if (self.mac_cmd || self.command || self.ctrl) && pattern.command { + return true; + } + false + } + /// Checks only cmd/ctrl, not alt/shift. /// /// `self` here are the currently pressed modifiers, From 1d5b011793f5d5662006de7fd9a21bfe8c0a200a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 18:36:23 +0200 Subject: [PATCH 073/388] Add `OperatingSystem::is_mac` (#7122) * Part of https://github.com/emilk/egui/issues/7120 --- crates/egui/src/os.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/egui/src/os.rs b/crates/egui/src/os.rs index e7497160b..283e33863 100644 --- a/crates/egui/src/os.rs +++ b/crates/egui/src/os.rs @@ -76,4 +76,9 @@ impl OperatingSystem { Self::Unknown } } + + /// Are we either macOS or iOS? + pub fn is_mac(&self) -> bool { + matches!(self, Self::Mac | Self::IOS) + } } From 53098fad7b71778dc51c6ce95a27ad222cc33e2b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 19:18:13 +0200 Subject: [PATCH 074/388] Support vertical-only scrolling by holding down Alt (#7124) * Closes https://github.com/emilk/egui/issues/7120 You can now zoom only the X axis by holding down shift, and zoom only the Y axis by holding down ALT. In summary * `Shift`: horizontal * `Alt`: vertical * `Ctrl`: zoom (`Cmd` on Mac) Thus follows: * `scroll`: pan both axis (at least for trackpads and mice with two-axis scroll) * `Shift + scroll`: pan only horizontal axis * `Alt + scroll`: pan only vertical axis * `Ctrl + scroll`: zoom all axes * `Ctrl + Shift + scroll`: zoom only horizontal axis * `Ctrl + Alt + scroll`: zoom only vertical axis This is provided the application uses `zoom_delta_2d` for its zooming needs. The modifiers are exposed in `InputOptions`, but it is strongly recommended that you do not change them. ## Testing Unfortunately we have no nice way of testing this in egui. But I've tested it in `egui_plot`. --- crates/egui/src/data/input.rs | 6 + crates/egui/src/input_state/mod.rs | 135 +++++++++++++++------ crates/egui/src/input_state/touch_state.rs | 4 +- crates/egui/src/lib.rs | 2 +- 4 files changed, 106 insertions(+), 41 deletions(-) diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 00950addb..2e35e34e7 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -986,6 +986,12 @@ impl std::ops::BitOrAssign for Modifiers { } } +impl Modifiers { + pub fn ui(&self, ui: &mut crate::Ui) { + ui.label(ModifierNames::NAMES.format(self, ui.ctx().os().is_mac())); + } +} + // ---------------------------------------------------------------------------- /// Names of different modifier keys. diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 6b3760159..c871a8452 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -41,6 +41,23 @@ pub struct InputOptions { /// The new pointer press must come within this many seconds from previous pointer release /// for double click (or when this value is doubled, triple click) to count. pub max_double_click_delay: f64, + + /// When this modifier is down, all scroll events are treated as zoom events. + /// + /// The default is CTRL/CMD, and it is STRONGLY recommended to NOT change this. + pub zoom_modifier: Modifiers, + + /// When this modifier is down, all scroll events are treated as horizontal scrolls, + /// and when combined with [`Self::zoom_modifier`] it will result in zooming + /// on only the horizontal axis. + /// + /// The default is SHIFT, and it is STRONGLY recommended to NOT change this. + pub horizontal_scroll_modifier: Modifiers, + + /// When this modifier is down, all scroll events are treated as vertical scrolls, + /// and when combined with [`Self::zoom_modifier`] it will result in zooming + /// on only the vertical axis. + pub vertical_scroll_modifier: Modifiers, } impl Default for InputOptions { @@ -59,6 +76,9 @@ impl Default for InputOptions { max_click_dist: 6.0, max_click_duration: 0.8, max_double_click_delay: 0.3, + zoom_modifier: Modifiers::COMMAND, + horizontal_scroll_modifier: Modifiers::SHIFT, + vertical_scroll_modifier: Modifiers::ALT, } } } @@ -72,6 +92,9 @@ impl InputOptions { max_click_dist, max_click_duration, max_double_click_delay, + zoom_modifier, + horizontal_scroll_modifier, + vertical_scroll_modifier, } = self; crate::Grid::new("InputOptions") .num_columns(2) @@ -100,7 +123,6 @@ impl InputOptions { ); ui.end_row(); - ui.label("Max click duration"); ui.add( crate::DragValue::new(max_click_duration) @@ -120,6 +142,19 @@ impl InputOptions { ) .on_hover_text("Max time interval for double click to count"); ui.end_row(); + + ui.label("zoom_modifier"); + zoom_modifier.ui(ui); + ui.end_row(); + + ui.label("horizontal_scroll_modifier"); + horizontal_scroll_modifier.ui(ui); + ui.end_row(); + + ui.label("vertical_scroll_modifier"); + vertical_scroll_modifier.ui(ui); + ui.end_row(); + }); } } @@ -263,7 +298,7 @@ pub struct InputState { /// Input state management configuration. /// /// This gets copied from `egui::Options` at the start of each frame for convenience. - input_options: InputOptions, + options: InputOptions, } impl Default for InputState { @@ -291,7 +326,7 @@ impl Default for InputState { modifiers: Default::default(), keys_down: Default::default(), events: Default::default(), - input_options: Default::default(), + options: Default::default(), } } } @@ -303,7 +338,7 @@ impl InputState { mut new: RawInput, requested_immediate_repaint_prev_frame: bool, pixels_per_point: f32, - input_options: InputOptions, + options: InputOptions, ) -> Self { profiling::function_scope!(); @@ -323,7 +358,7 @@ impl InputState { for touch_state in self.touch_states.values_mut() { touch_state.begin_pass(time, &new, self.pointer.interact_pos); } - let pointer = self.pointer.begin_pass(time, &new, input_options); + let pointer = self.pointer.begin_pass(time, &new, options); let mut keys_down = self.keys_down; let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor @@ -356,15 +391,22 @@ impl InputState { } => { let mut delta = match unit { MouseWheelUnit::Point => *delta, - MouseWheelUnit::Line => input_options.line_scroll_speed * *delta, + MouseWheelUnit::Line => options.line_scroll_speed * *delta, MouseWheelUnit::Page => screen_rect.height() * *delta, }; - if modifiers.shift { - // Treat as horizontal scrolling. + let is_horizontal = modifiers.matches_any(options.horizontal_scroll_modifier); + let is_vertical = modifiers.matches_any(options.vertical_scroll_modifier); + + if is_horizontal && !is_vertical { + // Treat all scrolling as horizontal scrolling. // Note: one Mac we already get horizontal scroll events when shift is down. delta = vec2(delta.x + delta.y, 0.0); } + if !is_horizontal && is_vertical { + // Treat all scrolling as vertical scrolling. + delta = vec2(0.0, delta.x + delta.y); + } raw_scroll_delta += delta; @@ -378,14 +420,14 @@ impl InputState { MouseWheelUnit::Line | MouseWheelUnit::Page => false, }; - let is_zoom = modifiers.ctrl || modifiers.mac_cmd || modifiers.command; + let is_zoom = modifiers.matches_any(options.zoom_modifier); #[expect(clippy::collapsible_else_if)] if is_zoom { if is_smooth { - smooth_scroll_delta_for_zoom += delta.y; + smooth_scroll_delta_for_zoom += delta.x + delta.y; } else { - unprocessed_scroll_delta_for_zoom += delta.y; + unprocessed_scroll_delta_for_zoom += delta.x + delta.y; } } else { if is_smooth { @@ -439,7 +481,7 @@ impl InputState { } zoom_factor_delta *= - (input_options.scroll_zoom_speed * smooth_scroll_delta_for_zoom).exp(); + (options.scroll_zoom_speed * smooth_scroll_delta_for_zoom).exp(); } } @@ -473,7 +515,7 @@ impl InputState { keys_down, events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events raw: new, - input_options, + options, } } @@ -488,10 +530,13 @@ impl InputState { self.screen_rect } - /// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture). + /// Uniform zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture). /// * `zoom = 1`: no change /// * `zoom < 1`: pinch together /// * `zoom > 1`: pinch spread + /// + /// If your application supports non-proportional zooming, + /// then you probably want to use [`Self::zoom_delta_2d`] instead. #[inline(always)] pub fn zoom_delta(&self) -> f32 { // If a multi touch gesture is detected, it measures the exact and linear proportions of @@ -521,10 +566,29 @@ impl InputState { // the distances of the finger tips. It is therefore potentially more accurate than // `zoom_factor_delta` which is based on the `ctrl-scroll` event which, in turn, may be // synthesized from an original touch gesture. - self.multi_touch().map_or_else( - || Vec2::splat(self.zoom_factor_delta), - |touch| touch.zoom_delta_2d, - ) + if let Some(multi_touch) = self.multi_touch() { + multi_touch.zoom_delta_2d + } else { + let mut zoom = Vec2::splat(self.zoom_factor_delta); + + let is_horizontal = self + .modifiers + .matches_any(self.options.horizontal_scroll_modifier); + let is_vertical = self + .modifiers + .matches_any(self.options.vertical_scroll_modifier); + + if is_horizontal && !is_vertical { + // Horizontal-only zooming. + zoom.y = 1.0; + } + if !is_horizontal && is_vertical { + // Vertical-only zooming. + zoom.x = 1.0; + } + + zoom + } } /// How long has it been (in seconds) since the use last scrolled? @@ -550,10 +614,10 @@ impl InputState { // We need to wake up and check for press-and-hold for the context menu. if let Some(press_start_time) = self.pointer.press_start_time { let press_duration = self.time - press_start_time; - if self.input_options.max_click_duration.is_finite() - && press_duration < self.input_options.max_click_duration + if self.options.max_click_duration.is_finite() + && press_duration < self.options.max_click_duration { - let secs_until_menu = self.input_options.max_click_duration - press_duration; + let secs_until_menu = self.options.max_click_duration - press_duration; return Some(Duration::from_secs_f64(secs_until_menu)); } } @@ -914,7 +978,7 @@ pub struct PointerState { /// Input state management configuration. /// /// This gets copied from `egui::Options` at the start of each frame for convenience. - input_options: InputOptions, + options: InputOptions, } impl Default for PointerState { @@ -937,23 +1001,18 @@ impl Default for PointerState { last_last_click_time: f64::NEG_INFINITY, last_move_time: f64::NEG_INFINITY, pointer_events: vec![], - input_options: Default::default(), + options: Default::default(), } } } impl PointerState { #[must_use] - pub(crate) fn begin_pass( - mut self, - time: f64, - new: &RawInput, - input_options: InputOptions, - ) -> Self { + pub(crate) fn begin_pass(mut self, time: f64, new: &RawInput, options: InputOptions) -> Self { let was_decidedly_dragging = self.is_decidedly_dragging(); self.time = time; - self.input_options = input_options; + self.options = options; self.pointer_events.clear(); @@ -974,7 +1033,7 @@ impl PointerState { if let Some(press_origin) = self.press_origin { self.has_moved_too_much_for_a_click |= - press_origin.distance(pos) > self.input_options.max_click_dist; + press_origin.distance(pos) > self.options.max_click_dist; } self.last_move_time = time; @@ -1013,10 +1072,10 @@ impl PointerState { let clicked = self.could_any_button_be_click(); let click = if clicked { - let double_click = (time - self.last_click_time) - < self.input_options.max_double_click_delay; + let double_click = + (time - self.last_click_time) < self.options.max_double_click_delay; let triple_click = (time - self.last_last_click_time) - < (self.input_options.max_double_click_delay * 2.0); + < (self.options.max_double_click_delay * 2.0); let count = if triple_click { 3 } else if double_click { @@ -1320,7 +1379,7 @@ impl PointerState { } if let Some(press_start_time) = self.press_start_time { - if self.time - press_start_time > self.input_options.max_click_duration { + if self.time - press_start_time > self.options.max_click_duration { return false; } } @@ -1356,7 +1415,7 @@ impl PointerState { && !self.has_moved_too_much_for_a_click && self.button_down(PointerButton::Primary) && self.press_start_time.is_some_and(|press_start_time| { - self.time - press_start_time > self.input_options.max_click_duration + self.time - press_start_time > self.options.max_click_duration }) } @@ -1416,7 +1475,7 @@ impl InputState { modifiers, keys_down, events, - input_options: _, + options: _, } = self; ui.style_mut() @@ -1502,7 +1561,7 @@ impl PointerState { last_last_click_time, pointer_events, last_move_time, - input_options: _, + options: _, } = self; ui.label(format!("latest_pos: {latest_pos:?}")); diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index 1ff2dc388..b4c789a8e 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -194,7 +194,7 @@ impl TouchState { let zoom_delta = state.current.avg_distance / state_previous.avg_distance; - let zoom_delta2 = match state.pinch_type { + let zoom_delta_2d = match state.pinch_type { PinchType::Horizontal => Vec2::new( state.current.avg_abs_distance2.x / state_previous.avg_abs_distance2.x, 1.0, @@ -213,7 +213,7 @@ impl TouchState { start_pos: state.start_pointer_pos, num_touches: self.active_touches.len(), zoom_delta, - zoom_delta_2d: zoom_delta2, + zoom_delta_2d, rotation_delta: normalized_angle(state.current.heading - state_previous.heading), translation_delta: state.current.avg_pos - state_previous.avg_pos, force: state.current.avg_force, diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 424654abd..6d42a230b 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -496,7 +496,7 @@ pub use self::{ epaint::text::TextWrapMode, grid::Grid, id::{Id, IdMap}, - input_state::{InputState, MultiTouchInfo, PointerState}, + input_state::{InputOptions, InputState, MultiTouchInfo, PointerState}, layers::{LayerId, Order}, layout::*, load::SizeHint, From 6e34152fa006c7fb7d6e5aa273ac46373dd33d97 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 19:22:16 +0200 Subject: [PATCH 075/388] Add `Context::format_modifiers` (#7125) Convenience --- crates/egui/src/context.rs | 68 ++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 4ec5e140a..751604d43 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -21,8 +21,7 @@ use crate::{ input_state::{InputState, MultiTouchInfo, PointerEvent}, interaction, layers::GraphicLayers, - load, - load::{Bytes, Loaders, SizedTexture}, + load::{self, Bytes, Loaders, SizedTexture}, memory::{Options, Theme}, os::OperatingSystem, output::FullOutput, @@ -32,10 +31,10 @@ use crate::{ viewport::ViewportClass, Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, - ModifierNames, 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, + 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")] @@ -1484,35 +1483,48 @@ impl Context { self.send_cmd(crate::OutputCommand::CopyImage(image)); } + fn can_show_modifier_symbols(&self) -> bool { + let ModifierNames { + alt, + ctrl, + shift, + mac_cmd, + .. + } = ModifierNames::SYMBOLS; + + let font_id = TextStyle::Body.resolve(&self.style()); + self.fonts(|f| { + let mut lock = f.lock(); + let font = lock.fonts.font(&font_id); + font.has_glyphs(alt) + && font.has_glyphs(ctrl) + && font.has_glyphs(shift) + && font.has_glyphs(mac_cmd) + }) + } + + /// Format the given modifiers in a human-readable way (e.g. `Ctrl+Shift+X`). + pub fn format_modifiers(&self, modifiers: Modifiers) -> String { + let os = self.os(); + + let is_mac = os.is_mac(); + + if is_mac && self.can_show_modifier_symbols() { + ModifierNames::SYMBOLS.format(&modifiers, is_mac) + } else { + ModifierNames::NAMES.format(&modifiers, is_mac) + } + } + /// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`). /// /// Can be used to get the text for [`crate::Button::shortcut_text`]. pub fn format_shortcut(&self, shortcut: &KeyboardShortcut) -> String { let os = self.os(); - let is_mac = matches!(os, OperatingSystem::Mac | OperatingSystem::IOS); + let is_mac = os.is_mac(); - let can_show_symbols = || { - let ModifierNames { - alt, - ctrl, - shift, - mac_cmd, - .. - } = ModifierNames::SYMBOLS; - - let font_id = TextStyle::Body.resolve(&self.style()); - self.fonts(|f| { - let mut lock = f.lock(); - let font = lock.fonts.font(&font_id); - font.has_glyphs(alt) - && font.has_glyphs(ctrl) - && font.has_glyphs(shift) - && font.has_glyphs(mac_cmd) - }) - }; - - if is_mac && can_show_symbols() { + if is_mac && self.can_show_modifier_symbols() { shortcut.format(&ModifierNames::SYMBOLS, is_mac) } else { shortcut.format(&ModifierNames::NAMES, is_mac) From 209e818bd8867f31354c6df0de1f1c98d9be6037 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 10:24:28 -0700 Subject: [PATCH 076/388] Improve deprecation message for old `egui::menu` --- crates/egui/src/menu.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 8de1fe951..9863f3941 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -1,5 +1,5 @@ #![allow(deprecated)] -//! Menu bar functionality (very basic so far). +//! Deprecated menu API - Use [`crate::containers::menu`] instead. //! //! Usage: //! ``` @@ -88,6 +88,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"] pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse { ui.horizontal(|ui| { set_menu_style(ui.style_mut()); From b8dfb138b692a3aa0371578eb368edaffae15618 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 10:24:41 -0700 Subject: [PATCH 077/388] Remove outdated link in README --- examples/serial_windows/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/serial_windows/README.md b/examples/serial_windows/README.md index 7543f95ca..9cf60b01f 100644 --- a/examples/serial_windows/README.md +++ b/examples/serial_windows/README.md @@ -7,8 +7,7 @@ Expected order of execution: - Similarly, when the second window is closed after a delay a third will be shown. - Once the third is closed the program will stop. -NOTE: this doesn't work on Mac due to . -See also . +NOTE: this doesn't work on Mac. See also . ```sh cargo run -p serial_windows From bdbe6558527899331440dd11024c07bcebb3c876 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 7 Jun 2025 17:19:12 -0700 Subject: [PATCH 078/388] Mark HarnessBuilder build functions with #[must_use] --- crates/egui_kittest/src/builder.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index 63f19c3c6..3b10ba37a 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -205,6 +205,7 @@ impl HarnessBuilder { /// }); /// }); /// ``` + #[must_use] pub fn build<'a>(self, app: impl FnMut(&egui::Context) + 'a) -> Harness<'a> { Harness::from_builder(self, AppKind::Context(Box::new(app)), (), None) } @@ -224,6 +225,7 @@ impl HarnessBuilder { /// ui.label("Hello, world!"); /// }); /// ``` + #[must_use] pub fn build_ui<'a>(self, app: impl FnMut(&mut egui::Ui) + 'a) -> Harness<'a> { Harness::from_builder(self, AppKind::Ui(Box::new(app)), (), None) } From cfb10a04f5141fa10bbecce5dc9046207c3c13f7 Mon Sep 17 00:00:00 2001 From: Rinde van Lon Date: Wed, 11 Jun 2025 11:01:34 +0100 Subject: [PATCH 079/388] Improve `ComboBox` doc example (#7116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improves the `ComboBox` example with some code that shows how to handle changes in the `ComboBox`’s selection. The approach is based on the advice given in https://github.com/emilk/egui/discussions/923 . I hope this saves future me (and hopefully others) a web search for how to do this. * [x] I have followed the instructions in the PR template --- crates/egui/src/containers/combo_box.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index ab89c4351..3ae8344ba 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -16,9 +16,10 @@ pub type IconPainter = Box; /// /// ``` /// # egui::__run_test_ui(|ui| { -/// # #[derive(Debug, PartialEq)] +/// # #[derive(Debug, PartialEq, Copy, Clone)] /// # enum Enum { First, Second, Third } /// # let mut selected = Enum::First; +/// let before = selected; /// egui::ComboBox::from_label("Select one!") /// .selected_text(format!("{:?}", selected)) /// .show_ui(ui, |ui| { @@ -27,6 +28,10 @@ pub type IconPainter = Box; /// ui.selectable_value(&mut selected, Enum::Third, "Third"); /// } /// ); +/// +/// if selected != before { +/// // Handle selection change +/// } /// # }); /// ``` #[must_use = "You should call .show*"] From 9f9153805d9d9a9863feef0b4e731c1495790c78 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 11 Jun 2025 17:38:06 +0200 Subject: [PATCH 080/388] lint: fix lints appearing in rust stable currently (#7118) * [x] I have followed the instructions in the PR template --- crates/eframe/src/native/file_storage.rs | 2 +- crates/egui-winit/src/lib.rs | 12 ++++++------ crates/egui/src/context.rs | 12 ++++++------ crates/egui/src/data/input.rs | 4 +--- crates/egui/src/data/output.rs | 2 +- crates/egui/src/memory/mod.rs | 8 ++++---- crates/egui/src/widgets/text_edit/builder.rs | 8 +++++--- crates/egui_demo_lib/src/demo/screenshot.rs | 2 +- crates/egui_demo_lib/src/demo/tests/grid_test.rs | 2 +- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index e1fdc9b84..5a3b1af32 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -45,7 +45,7 @@ pub fn storage_dir(app_id: &str) -> Option { #[expect(unsafe_code)] fn roaming_appdata() -> Option { use std::ffi::OsString; - use std::os::windows::ffi::OsStringExt; + use std::os::windows::ffi::OsStringExt as _; use std::ptr; use std::slice; diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 03ec3e831..c196acaa9 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1848,8 +1848,8 @@ pub fn short_device_event_description(event: &winit::event::DeviceEvent) -> &'st use winit::event::DeviceEvent; match event { - DeviceEvent::Added { .. } => "DeviceEvent::Added", - DeviceEvent::Removed { .. } => "DeviceEvent::Removed", + DeviceEvent::Added => "DeviceEvent::Added", + DeviceEvent::Removed => "DeviceEvent::Removed", DeviceEvent::MouseMotion { .. } => "DeviceEvent::MouseMotion", DeviceEvent::MouseWheel { .. } => "DeviceEvent::MouseWheel", DeviceEvent::Motion { .. } => "DeviceEvent::Motion", @@ -1867,11 +1867,11 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'st WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone", WindowEvent::Resized { .. } => "WindowEvent::Resized", WindowEvent::Moved { .. } => "WindowEvent::Moved", - WindowEvent::CloseRequested { .. } => "WindowEvent::CloseRequested", - WindowEvent::Destroyed { .. } => "WindowEvent::Destroyed", + WindowEvent::CloseRequested => "WindowEvent::CloseRequested", + WindowEvent::Destroyed => "WindowEvent::Destroyed", WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile", WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile", - WindowEvent::HoveredFileCancelled { .. } => "WindowEvent::HoveredFileCancelled", + WindowEvent::HoveredFileCancelled => "WindowEvent::HoveredFileCancelled", WindowEvent::Focused { .. } => "WindowEvent::Focused", WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput", WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged", @@ -1882,7 +1882,7 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'st WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel", WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput", WindowEvent::PinchGesture { .. } => "WindowEvent::PinchGesture", - WindowEvent::RedrawRequested { .. } => "WindowEvent::RedrawRequested", + WindowEvent::RedrawRequested => "WindowEvent::RedrawRequested", WindowEvent::DoubleTapGesture { .. } => "WindowEvent::DoubleTapGesture", WindowEvent::RotationGesture { .. } => "WindowEvent::RotationGesture", WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure", diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 751604d43..3204927b8 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -3511,9 +3511,10 @@ impl Context { // Try most recently added loaders first (hence `.rev()`) for loader in bytes_loaders.iter().rev() { - match loader.load(self, uri) { - Err(load::LoadError::NotSupported) => continue, - result => return result, + let result = loader.load(self, uri); + match result { + Err(load::LoadError::NotSupported) => {} + _ => return result, } } @@ -3554,10 +3555,9 @@ impl Context { // Try most recently added loaders first (hence `.rev()`) for loader in image_loaders.iter().rev() { match loader.load(self, uri, size_hint) { - Err(load::LoadError::NotSupported) => continue, + Err(load::LoadError::NotSupported) => {} Err(load::LoadError::FormatNotSupported { detected_format }) => { format = format.or(detected_format); - continue; } result => return result, } @@ -3600,7 +3600,7 @@ impl Context { // Try most recently added loaders first (hence `.rev()`) for loader in texture_loaders.iter().rev() { match loader.load(self, uri, texture_options, size_hint) { - Err(load::LoadError::NotSupported) => continue, + Err(load::LoadError::NotSupported) => {} result => return result, } } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 2e35e34e7..1e2580678 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -256,9 +256,7 @@ impl ViewportInfo { /// If this is not the root viewport, /// it is up to the user to hide this viewport the next frame. pub fn close_requested(&self) -> bool { - self.events - .iter() - .any(|&event| event == ViewportEvent::Close) + self.events.contains(&ViewportEvent::Close) } /// Helper: move [`Self::events`], clone the other fields. diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index ab9044cb6..233f75a42 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -739,7 +739,7 @@ impl WidgetInfo { if text_value.is_empty() { "blank".into() } else { - text_value.to_string() + text_value.clone() } } else { "blank".into() diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index e9f7d9f86..9bf482b83 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -1235,7 +1235,7 @@ impl Areas { self.visible_areas_current_frame.insert(layer_id); self.wants_to_be_on_top.insert(layer_id); - if !self.order.iter().any(|x| *x == layer_id) { + if !self.order.contains(&layer_id) { self.order.push(layer_id); } } @@ -1256,10 +1256,10 @@ impl Areas { self.sublayers.entry(parent).or_default().insert(child); // Make sure the layers are in the order list: - if !self.order.iter().any(|x| *x == parent) { + if !self.order.contains(&parent) { self.order.push(parent); } - if !self.order.iter().any(|x| *x == child) { + if !self.order.contains(&child) { self.order.push(child); } } @@ -1268,7 +1268,7 @@ impl Areas { self.order .iter() .filter(|layer| layer.order == order && !self.is_sublayer(layer)) - .last() + .next_back() .copied() } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index ca201edda..29f4b2cbb 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -863,9 +863,11 @@ impl TextEdit<'_> { fn mask_if_password(is_password: bool, text: &str) -> String { fn mask_password(text: &str) -> String { - std::iter::repeat(epaint::text::PASSWORD_REPLACEMENT_CHAR) - .take(text.chars().count()) - .collect::() + std::iter::repeat_n( + epaint::text::PASSWORD_REPLACEMENT_CHAR, + text.chars().count(), + ) + .collect::() } if is_password { diff --git a/crates/egui_demo_lib/src/demo/screenshot.rs b/crates/egui_demo_lib/src/demo/screenshot.rs index c2367914b..64370ec07 100644 --- a/crates/egui_demo_lib/src/demo/screenshot.rs +++ b/crates/egui_demo_lib/src/demo/screenshot.rs @@ -58,7 +58,7 @@ impl crate::View for Screenshot { None } }) - .last() + .next_back() }); if let Some(image) = image { diff --git a/crates/egui_demo_lib/src/demo/tests/grid_test.rs b/crates/egui_demo_lib/src/demo/tests/grid_test.rs index 511aa428f..0b7bfa485 100644 --- a/crates/egui_demo_lib/src/demo/tests/grid_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/grid_test.rs @@ -110,7 +110,7 @@ impl crate::View for GridTest { ui.end_row(); let mut dyn_text = String::from("O"); - dyn_text.extend(std::iter::repeat('h').take(self.text_length)); + dyn_text.extend(std::iter::repeat_n('h', self.text_length)); ui.label(dyn_text); ui.label("Fifth row, second column"); ui.end_row(); From f0abce9bb8591466ce01f741eced5b271d3a4dc1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 11 Jun 2025 23:00:59 +0200 Subject: [PATCH 081/388] `Button` inherits the `alt_text` of the `Image` in it, if any (#7136) If a `Button` has an `Image` in it (and no text), then the `Image::alt_text` will be used as the accessibility label for the button. --- crates/egui/src/widgets/button.rs | 10 +++++++--- crates/egui/src/widgets/image.rs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index a75997eff..9ae573aef 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -314,11 +314,15 @@ impl Widget for Button<'_> { let (rect, mut response) = ui.allocate_at_least(desired_size, sense); response.widget_info(|| { + let mut widget_info = WidgetInfo::new(WidgetType::Button); + widget_info.enabled = ui.is_enabled(); + if let Some(galley) = &galley { - WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), galley.text()) - } else { - WidgetInfo::new(WidgetType::Button) + widget_info.label = Some(galley.text().to_owned()); + } else if let Some(image) = &image { + widget_info.label = image.alt_text.clone(); } + widget_info }); if ui.is_rect_visible(rect) { diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index e0ff7d36a..07b08e53c 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -54,7 +54,7 @@ pub struct Image<'a> { sense: Sense, size: ImageSize, pub(crate) show_loading_spinner: Option, - alt_text: Option, + pub(crate) alt_text: Option, } impl<'a> Image<'a> { From 6eb7bb6e0814c609701240e38e668f1a0f6516a7 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Fri, 13 Jun 2025 09:39:52 +0200 Subject: [PATCH 082/388] Add `AtomLayout`, abstracing layouting within widgets (#5830) Today each widget does its own custom layout, which has some drawbacks: - not very flexible - you can add an `Image` to `Button` but it will always be shown on the left side - you can't add a `Image` to a e.g. a `SelectableLabel` - a lot of duplicated code This PR introduces `Atoms` and `AtomLayout` which abstracts over "widget content" and layout within widgets, so it'd be possible to add images / text / custom rendering (for e.g. the checkbox) to any widget. A simple custom button implementation is now as easy as this: ```rs pub struct ALButton<'a> { al: AtomicLayout<'a>, } impl<'a> ALButton<'a> { pub fn new(content: impl IntoAtomics) -> Self { Self { al: content.into_atomics() } } } impl<'a> Widget for ALButton<'a> { fn ui(mut self, ui: &mut Ui) -> Response { let response = ui.ctx().read_response(ui.next_auto_id()); let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| { ui.style().interact(&response) }); self.al.frame = self .al .frame .inner_margin(ui.style().spacing.button_padding) .fill(visuals.bg_fill) .stroke(visuals.bg_stroke) .corner_radius(visuals.corner_radius); self.al.show(ui) } } ``` The initial implementation only does very basic layout, just enough to be able to implement most current egui widgets, so: - only horizontal layout - everything is centered - a single item may grow/shrink based on the available space - everything can be contained in a Frame There is a trait `IntoAtoms` that conveniently allows you to construct `Atoms` from a tuple ``` ui.button((Image::new("image.png"), "Click me!")) ``` to get a button with image and text. This PR reimplements three egui widgets based on the new AtomLayout: - Button - matches the old button pixel-by-pixel - Button with image is now [properly aligned](https://github.com/emilk/egui/pull/5830/files#diff-962ce2c68ab50724b01c6b64c683c4067edd9b79fcdcb39a6071021e33ebe772) in justified layouts - selected button style now matches SelecatbleLabel look - For some reason the DragValue text seems shifted by a pixel almost everywhere, but I think it's more centered now, yay? - Checkbox - basically pixel-perfect but apparently the check mesh is very slightly different so I had to update the snapshot - somehow needs a bit more space in some snapshot tests? - RadioButton - pixel-perfect - somehow needs a bit more space in some snapshot tests? I plan on updating TextEdit based on AtomLayout in a separate PR (so you could use it to add a icon within the textedit frame). --- Cargo.lock | 1 + Cargo.toml | 1 + crates/egui/Cargo.toml | 1 + crates/egui/src/atomics/atom.rs | 109 ++++ crates/egui/src/atomics/atom_ext.rs | 107 ++++ crates/egui/src/atomics/atom_kind.rs | 120 +++++ crates/egui/src/atomics/atom_layout.rs | 493 ++++++++++++++++++ crates/egui/src/atomics/atoms.rs | 220 ++++++++ crates/egui/src/atomics/mod.rs | 15 + crates/egui/src/atomics/sized_atom.rs | 26 + crates/egui/src/atomics/sized_atom_kind.rs | 25 + crates/egui/src/containers/menu.rs | 12 +- crates/egui/src/lib.rs | 2 + crates/egui/src/ui.rs | 34 +- crates/egui/src/widget_text.rs | 16 +- crates/egui/src/widgets/button.rs | 356 ++++--------- crates/egui/src/widgets/checkbox.rs | 120 +++-- crates/egui/src/widgets/radio_button.rs | 97 ++-- .../tests/snapshots/imageviewer.png | 4 +- .../egui_demo_lib/src/demo/widget_gallery.rs | 5 +- .../tests/snapshots/demos/Grid Test.png | 2 +- .../tests/snapshots/demos/Layout Test.png | 4 +- .../tests/snapshots/demos/Scene.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 2 +- .../tests/snapshots/demos/Sliders.png | 4 +- .../tests/snapshots/demos/Table.png | 4 +- .../tests/snapshots/widget_gallery.png | 4 +- crates/egui_extras/src/syntax_highlighting.rs | 2 +- crates/egui_kittest/tests/menu.rs | 12 +- tests/egui_tests/tests/snapshots/grow_all.png | 3 + .../tests/snapshots/layout/atoms_image.png | 3 + .../tests/snapshots/layout/atoms_minimal.png | 3 + .../snapshots/layout/atoms_multi_grow.png | 3 + .../tests/snapshots/layout/button_image.png | 4 +- .../snapshots/layout/checkbox_checked.png | 4 +- .../egui_tests/tests/snapshots/max_width.png | 3 + .../tests/snapshots/max_width_and_grow.png | 3 + .../tests/snapshots/shrink_first_text.png | 3 + .../tests/snapshots/shrink_last_text.png | 3 + .../tests/snapshots/size_max_size.png | 3 + .../button_image_shortcut_selected.png | 4 +- tests/egui_tests/tests/test_atoms.rs | 71 +++ tests/egui_tests/tests/test_widgets.rs | 25 +- 43 files changed, 1528 insertions(+), 409 deletions(-) create mode 100644 crates/egui/src/atomics/atom.rs create mode 100644 crates/egui/src/atomics/atom_ext.rs create mode 100644 crates/egui/src/atomics/atom_kind.rs create mode 100644 crates/egui/src/atomics/atom_layout.rs create mode 100644 crates/egui/src/atomics/atoms.rs create mode 100644 crates/egui/src/atomics/mod.rs create mode 100644 crates/egui/src/atomics/sized_atom.rs create mode 100644 crates/egui/src/atomics/sized_atom_kind.rs create mode 100644 tests/egui_tests/tests/snapshots/grow_all.png create mode 100644 tests/egui_tests/tests/snapshots/layout/atoms_image.png create mode 100644 tests/egui_tests/tests/snapshots/layout/atoms_minimal.png create mode 100644 tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png create mode 100644 tests/egui_tests/tests/snapshots/max_width.png create mode 100644 tests/egui_tests/tests/snapshots/max_width_and_grow.png create mode 100644 tests/egui_tests/tests/snapshots/shrink_first_text.png create mode 100644 tests/egui_tests/tests/snapshots/shrink_last_text.png create mode 100644 tests/egui_tests/tests/snapshots/size_max_size.png create mode 100644 tests/egui_tests/tests/test_atoms.rs diff --git a/Cargo.lock b/Cargo.lock index 9d7c11a19..9a441cb30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1263,6 +1263,7 @@ dependencies = [ "profiling", "ron", "serde", + "smallvec", "unicode-segmentation", ] diff --git a/Cargo.toml b/Cargo.toml index d1efd4aee..bf5b27d30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,7 @@ raw-window-handle = "0.6.0" ron = "0.10.1" serde = { version = "1", features = ["derive"] } similar-asserts = "1.4.2" +smallvec = "1" thiserror = "1.0.37" type-map = "0.5.0" unicode-segmentation = "1.12.0" diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index e7641ef31..238a8d8ea 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -87,6 +87,7 @@ ahash.workspace = true bitflags.workspace = true nohash-hasher.workspace = true profiling.workspace = true +smallvec.workspace = true unicode-segmentation.workspace = true #! ### Optional dependencies diff --git a/crates/egui/src/atomics/atom.rs b/crates/egui/src/atomics/atom.rs new file mode 100644 index 000000000..4f4b5b750 --- /dev/null +++ b/crates/egui/src/atomics/atom.rs @@ -0,0 +1,109 @@ +use crate::{AtomKind, Id, SizedAtom, Ui}; +use emath::{NumExt as _, Vec2}; +use epaint::text::TextWrapMode; + +/// A low-level ui building block. +/// +/// Implements [`From`] for [`String`], [`str`], [`crate::Image`] and much more for convenience. +/// You can directly call the `atom_*` methods on anything that implements `Into`. +/// ``` +/// # use egui::{Image, emath::Vec2}; +/// use egui::AtomExt as _; +/// let string_atom = "Hello".atom_grow(true); +/// let image_atom = Image::new("some_image_url").atom_size(Vec2::splat(20.0)); +/// ``` +#[derive(Clone, Debug)] +pub struct Atom<'a> { + /// See [`crate::AtomExt::atom_size`] + pub size: Option, + + /// See [`crate::AtomExt::atom_max_size`] + pub max_size: Vec2, + + /// See [`crate::AtomExt::atom_grow`] + pub grow: bool, + + /// See [`crate::AtomExt::atom_shrink`] + pub shrink: bool, + + /// The atom type + pub kind: AtomKind<'a>, +} + +impl Default for Atom<'_> { + fn default() -> Self { + Atom { + size: None, + max_size: Vec2::INFINITY, + grow: false, + shrink: false, + kind: AtomKind::Empty, + } + } +} + +impl<'a> Atom<'a> { + /// Create an empty [`Atom`] marked as `grow`. + /// + /// This will expand in size, allowing all preceding atoms to be left-aligned, + /// and all following atoms to be right-aligned + pub fn grow() -> Self { + Atom { + grow: true, + ..Default::default() + } + } + + /// Create a [`AtomKind::Custom`] with a specific size. + pub fn custom(id: Id, size: impl Into) -> Self { + Atom { + size: Some(size.into()), + kind: AtomKind::Custom(id), + ..Default::default() + } + } + + /// Turn this into a [`SizedAtom`]. + pub fn into_sized( + self, + ui: &Ui, + mut available_size: Vec2, + mut wrap_mode: Option, + ) -> SizedAtom<'a> { + if !self.shrink && self.max_size.x.is_infinite() { + wrap_mode = Some(TextWrapMode::Extend); + } + available_size = available_size.at_most(self.max_size); + if let Some(size) = self.size { + available_size = available_size.at_most(size); + } + if self.max_size.x.is_finite() { + wrap_mode = Some(TextWrapMode::Truncate); + } + + let (preferred, kind) = self.kind.into_sized(ui, available_size, wrap_mode); + + let size = self + .size + .map_or_else(|| kind.size(), |s| s.at_most(self.max_size)); + + SizedAtom { + size, + preferred_size: preferred, + grow: self.grow, + kind, + } + } +} + +impl<'a, T> From for Atom<'a> +where + T: Into>, +{ + fn from(value: T) -> Self { + Atom { + kind: value.into(), + ..Default::default() + } + } +} diff --git a/crates/egui/src/atomics/atom_ext.rs b/crates/egui/src/atomics/atom_ext.rs new file mode 100644 index 000000000..0c34544d8 --- /dev/null +++ b/crates/egui/src/atomics/atom_ext.rs @@ -0,0 +1,107 @@ +use crate::{Atom, FontSelection, Ui}; +use emath::Vec2; + +/// A trait for conveniently building [`Atom`]s. +/// +/// The functions are prefixed with `atom_` to avoid conflicts with e.g. [`crate::RichText::size`]. +pub trait AtomExt<'a> { + /// Set the atom to a fixed size. + /// + /// If [`Atom::grow`] is `true`, this will be the minimum width. + /// If [`Atom::shrink`] is `true`, this will be the maximum width. + /// If both are true, the width will have no effect. + /// + /// [`Self::atom_max_size`] will limit size. + /// + /// See [`crate::AtomKind`] docs to see how the size affects the different types. + fn atom_size(self, size: Vec2) -> Atom<'a>; + + /// Grow this atom to the available space. + /// + /// This will affect the size of the [`Atom`] in the main direction. Since + /// [`crate::AtomLayout`] today only supports horizontal layout, it will affect the width. + /// + /// You can also combine this with [`Self::atom_shrink`] to make it always take exactly the + /// remaining space. + fn atom_grow(self, grow: bool) -> Atom<'a>; + + /// Shrink this atom if there isn't enough space. + /// + /// This will affect the size of the [`Atom`] in the main direction. Since + /// [`crate::AtomLayout`] today only supports horizontal layout, it will affect the width. + /// + /// NOTE: Only a single [`Atom`] may shrink for each widget. + /// + /// If no atom was set to shrink and `wrap_mode != TextWrapMode::Extend`, the first + /// `AtomKind::Text` is set to shrink. + fn atom_shrink(self, shrink: bool) -> Atom<'a>; + + /// Set the maximum size of this atom. + /// + /// Will not affect the space taken by `grow` (All atoms marked as grow will always grow + /// equally to fill the available space). + fn atom_max_size(self, max_size: Vec2) -> Atom<'a>; + + /// Set the maximum width of this atom. + /// + /// Will not affect the space taken by `grow` (All atoms marked as grow will always grow + /// equally to fill the available space). + fn atom_max_width(self, max_width: f32) -> Atom<'a>; + + /// Set the maximum height of this atom. + fn atom_max_height(self, max_height: f32) -> Atom<'a>; + + /// Set the max height of this atom to match the font size. + /// + /// This is useful for e.g. limiting the height of icons in buttons. + fn atom_max_height_font_size(self, ui: &Ui) -> Atom<'a> + where + Self: Sized, + { + let font_selection = FontSelection::default(); + let font_id = font_selection.resolve(ui.style()); + let height = ui.fonts(|f| f.row_height(&font_id)); + self.atom_max_height(height) + } +} + +impl<'a, T> AtomExt<'a> for T +where + T: Into> + Sized, +{ + fn atom_size(self, size: Vec2) -> Atom<'a> { + let mut atom = self.into(); + atom.size = Some(size); + atom + } + + fn atom_grow(self, grow: bool) -> Atom<'a> { + let mut atom = self.into(); + atom.grow = grow; + atom + } + + fn atom_shrink(self, shrink: bool) -> Atom<'a> { + let mut atom = self.into(); + atom.shrink = shrink; + atom + } + + fn atom_max_size(self, max_size: Vec2) -> Atom<'a> { + let mut atom = self.into(); + atom.max_size = max_size; + atom + } + + fn atom_max_width(self, max_width: f32) -> Atom<'a> { + let mut atom = self.into(); + atom.max_size.x = max_width; + atom + } + + fn atom_max_height(self, max_height: f32) -> Atom<'a> { + let mut atom = self.into(); + atom.max_size.y = max_height; + atom + } +} diff --git a/crates/egui/src/atomics/atom_kind.rs b/crates/egui/src/atomics/atom_kind.rs new file mode 100644 index 000000000..2672e646b --- /dev/null +++ b/crates/egui/src/atomics/atom_kind.rs @@ -0,0 +1,120 @@ +use crate::{Id, Image, ImageSource, SizedAtomKind, TextStyle, Ui, WidgetText}; +use emath::Vec2; +use epaint::text::TextWrapMode; + +/// The different kinds of [`crate::Atom`]s. +#[derive(Clone, Default, Debug)] +pub enum AtomKind<'a> { + /// Empty, that can be used with [`crate::AtomExt::atom_grow`] to reserve space. + #[default] + Empty, + + /// Text atom. + /// + /// Truncation within [`crate::AtomLayout`] works like this: + /// - + /// - if `wrap_mode` is not Extend + /// - if no atom is `shrink` + /// - the first text atom is selected and will be marked as `shrink` + /// - the atom marked as `shrink` will shrink / wrap based on the selected wrap mode + /// - any other text atoms will have `wrap_mode` extend + /// - if `wrap_mode` is extend, Text will extend as expected. + /// + /// Unless [`crate::AtomExt::atom_max_width`] is set, `wrap_mode` should only be set via [`crate::Style`] or + /// [`crate::AtomLayout::wrap_mode`], as setting a wrap mode on a [`WidgetText`] atom + /// that is not `shrink` will have unexpected results. + /// + /// The size is determined by converting the [`WidgetText`] into a galley and using the galleys + /// size. You can use [`crate::AtomExt::atom_size`] to override this, and [`crate::AtomExt::atom_max_width`] + /// to limit the width (Causing the text to wrap or truncate, depending on the `wrap_mode`. + /// [`crate::AtomExt::atom_max_height`] has no effect on text. + Text(WidgetText), + + /// Image atom. + /// + /// By default the size is determined via [`Image::calc_size`]. + /// You can use [`crate::AtomExt::atom_max_size`] or [`crate::AtomExt::atom_size`] to customize the size. + /// There is also a helper [`crate::AtomExt::atom_max_height_font_size`] to set the max height to the + /// default font height, which is convenient for icons. + Image(Image<'a>), + + /// For custom rendering. + /// + /// You can get the [`crate::Rect`] with the [`Id`] from [`crate::AtomLayoutResponse`] and use a + /// [`crate::Painter`] or [`Ui::put`] to add/draw some custom content. + /// + /// Example: + /// ``` + /// # use egui::{AtomExt, AtomKind, Atom, Button, Id, __run_test_ui}; + /// # use emath::Vec2; + /// # __run_test_ui(|ui| { + /// let id = Id::new("my_button"); + /// let response = Button::new(("Hi!", Atom::custom(id, Vec2::splat(18.0)))).atom_ui(ui); + /// + /// let rect = response.rect(id); + /// if let Some(rect) = rect { + /// ui.put(rect, Button::new("⏵")); + /// } + /// # }); + /// ``` + Custom(Id), +} + +impl<'a> AtomKind<'a> { + pub fn text(text: impl Into) -> Self { + AtomKind::Text(text.into()) + } + + pub fn image(image: impl Into>) -> Self { + AtomKind::Image(image.into()) + } + + /// Turn this [`AtomKind`] into a [`SizedAtomKind`]. + /// + /// This converts [`WidgetText`] into [`crate::Galley`] and tries to load and size [`Image`]. + /// The first returned argument is the preferred size. + pub fn into_sized( + self, + ui: &Ui, + available_size: Vec2, + wrap_mode: Option, + ) -> (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), + ) + } + AtomKind::Image(image) => { + let size = image.load_and_calc_size(ui, available_size); + let size = size.unwrap_or(Vec2::ZERO); + (size, SizedAtomKind::Image(image, size)) + } + AtomKind::Custom(id) => (Vec2::ZERO, SizedAtomKind::Custom(id)), + AtomKind::Empty => (Vec2::ZERO, SizedAtomKind::Empty), + } + } +} + +impl<'a> From> for AtomKind<'a> { + fn from(value: ImageSource<'a>) -> Self { + AtomKind::Image(value.into()) + } +} + +impl<'a> From> for AtomKind<'a> { + fn from(value: Image<'a>) -> Self { + AtomKind::Image(value) + } +} + +impl From for AtomKind<'_> +where + T: Into, +{ + fn from(value: T) -> Self { + AtomKind::Text(value.into()) + } +} diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs new file mode 100644 index 000000000..a25a4b7c6 --- /dev/null +++ b/crates/egui/src/atomics/atom_layout.rs @@ -0,0 +1,493 @@ +use crate::atomics::ATOMS_SMALL_VEC_SIZE; +use crate::{ + AtomKind, Atoms, Frame, Id, Image, IntoAtoms, Response, Sense, SizedAtom, SizedAtomKind, Ui, + Widget, +}; +use emath::{Align2, GuiRounding as _, NumExt as _, Rect, Vec2}; +use epaint::text::TextWrapMode; +use epaint::{Color32, Galley}; +use smallvec::SmallVec; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +/// Intra-widget layout utility. +/// +/// Used to lay out and paint [`crate::Atom`]s. +/// This is used internally by widgets like [`crate::Button`] and [`crate::Checkbox`]. +/// You can use it to make your own widgets. +/// +/// Painting the atoms can be split in two phases: +/// - [`AtomLayout::allocate`] +/// - calculates sizes +/// - converts texts to [`Galley`]s +/// - allocates a [`Response`] +/// - returns a [`AllocatedAtomLayout`] +/// - [`AllocatedAtomLayout::paint`] +/// - paints the [`Frame`] +/// - calculates individual [`crate::Atom`] positions +/// - paints each single atom +/// +/// You can use this to first allocate a response and then modify, e.g., the [`Frame`] on the +/// [`AllocatedAtomLayout`] for interaction styling. +pub struct AtomLayout<'a> { + id: Option, + pub atoms: Atoms<'a>, + gap: Option, + pub(crate) frame: Frame, + pub(crate) sense: Sense, + fallback_text_color: Option, + min_size: Vec2, + wrap_mode: Option, + align2: Option, +} + +impl Default for AtomLayout<'_> { + fn default() -> Self { + Self::new(()) + } +} + +impl<'a> AtomLayout<'a> { + pub fn new(atoms: impl IntoAtoms<'a>) -> Self { + Self { + id: None, + atoms: atoms.into_atoms(), + gap: None, + frame: Frame::default(), + sense: Sense::hover(), + fallback_text_color: None, + min_size: Vec2::ZERO, + wrap_mode: None, + align2: None, + } + } + + /// Set the gap between atoms. + /// + /// Default: `Spacing::icon_spacing` + #[inline] + pub fn gap(mut self, gap: f32) -> Self { + self.gap = Some(gap); + self + } + + /// Set the [`Frame`]. + #[inline] + pub fn frame(mut self, frame: Frame) -> Self { + self.frame = frame; + self + } + + /// Set the [`Sense`] used when allocating the [`Response`]. + #[inline] + pub fn sense(mut self, sense: Sense) -> Self { + self.sense = sense; + self + } + + /// Set the fallback (default) text color. + /// + /// Default: [`crate::Visuals::text_color`] + #[inline] + pub fn fallback_text_color(mut self, color: Color32) -> Self { + self.fallback_text_color = Some(color); + self + } + + /// Set the minimum size of the Widget. + /// + /// This will find and expand atoms with `grow: true`. + /// If there are no growable atoms then everything will be left-aligned. + #[inline] + pub fn min_size(mut self, size: Vec2) -> Self { + self.min_size = size; + self + } + + /// Set the [`Id`] used to allocate a [`Response`]. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + + /// Set the [`TextWrapMode`] for the [`crate::Atom`] marked as `shrink`. + /// + /// Only a single [`crate::Atom`] may shrink. If this (or `ui.wrap_mode()`) is not + /// [`TextWrapMode::Extend`] and no item is set to shrink, the first (left-most) + /// [`AtomKind::Text`] will be set to shrink. + #[inline] + pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self { + self.wrap_mode = Some(wrap_mode); + self + } + + /// Set the [`Align2`]. + /// + /// This will align the [`crate::Atom`]s within the [`Rect`] returned by [`Ui::allocate_space`]. + /// + /// The default is chosen based on the [`Ui`]s [`crate::Layout`]. See + /// [this snapshot](https://github.com/emilk/egui/blob/master/tests/egui_tests/tests/snapshots/layout/button.png) + /// for info on how the [`crate::Layout`] affects the alignment. + #[inline] + pub fn align2(mut self, align2: Align2) -> Self { + self.align2 = Some(align2); + self + } + + /// [`AtomLayout::allocate`] and [`AllocatedAtomLayout::paint`] in one go. + pub fn show(self, ui: &mut Ui) -> AtomLayoutResponse { + self.allocate(ui).paint(ui) + } + + /// Calculate sizes, create [`Galley`]s and allocate a [`Response`]. + /// + /// Use the returned [`AllocatedAtomLayout`] for painting. + pub fn allocate(self, ui: &mut Ui) -> AllocatedAtomLayout<'a> { + let Self { + id, + mut atoms, + gap, + frame, + sense, + fallback_text_color, + min_size, + wrap_mode, + align2, + } = self; + + let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); + + // If the TextWrapMode is not Extend, ensure there is some item marked as `shrink`. + // If none is found, mark the first text item as `shrink`. + if wrap_mode != TextWrapMode::Extend { + let any_shrink = atoms.iter().any(|a| a.shrink); + if !any_shrink { + let first_text = atoms + .iter_mut() + .find(|a| matches!(a.kind, AtomKind::Text(..))); + if let Some(atom) = first_text { + atom.shrink = true; // Will make the text truncate or shrink depending on wrap_mode + } + } + } + + let id = id.unwrap_or_else(|| ui.next_auto_id()); + + let fallback_text_color = + fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color()); + let gap = gap.unwrap_or(ui.spacing().icon_spacing); + + // The size available for the content + let available_inner_size = ui.available_size() - frame.total_margin().sum(); + + let mut desired_width = 0.0; + + // Preferred 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 height: f32 = 0.0; + + let mut sized_items = SmallVec::new(); + + let mut grow_count = 0; + + let mut shrink_item = None; + + let align2 = align2.unwrap_or_else(|| { + Align2([ui.layout().horizontal_align(), ui.layout().vertical_align()]) + }); + + if atoms.len() > 1 { + let gap_space = gap * (atoms.len() as f32 - 1.0); + desired_width += gap_space; + preferred_width += gap_space; + } + + for (idx, item) in atoms.into_iter().enumerate() { + if item.grow { + grow_count += 1; + } + if item.shrink { + debug_assert!( + shrink_item.is_none(), + "Only one atomic may be marked as shrink. {item:?}" + ); + if shrink_item.is_none() { + shrink_item = Some((idx, item)); + continue; + } + } + let sized = item.into_sized(ui, available_inner_size, Some(wrap_mode)); + let size = sized.size; + + desired_width += size.x; + preferred_width += sized.preferred_size.x; + + height = height.at_least(size.y); + preferred_height = preferred_height.at_least(sized.preferred_size.y); + + sized_items.push(sized); + } + + if let Some((index, item)) = shrink_item { + // The `shrink` item gets the remaining space + let available_size_for_shrink_item = Vec2::new( + available_inner_size.x - desired_width, + available_inner_size.y, + ); + + let sized = item.into_sized(ui, available_size_for_shrink_item, Some(wrap_mode)); + let size = sized.size; + + desired_width += size.x; + preferred_width += sized.preferred_size.x; + + height = height.at_least(size.y); + preferred_height = preferred_height.at_least(sized.preferred_size.y); + + sized_items.insert(index, sized); + } + + let margin = frame.total_margin(); + let desired_size = Vec2::new(desired_width, height); + let frame_size = (desired_size + margin.sum()).at_least(min_size); + + let (_, rect) = ui.allocate_space(frame_size); + let mut response = ui.interact(rect, id, sense); + + response.intrinsic_size = + Some((Vec2::new(preferred_width, preferred_height) + margin.sum()).at_least(min_size)); + + AllocatedAtomLayout { + sized_atoms: sized_items, + frame, + fallback_text_color, + response, + grow_count, + desired_size, + align2, + gap, + } + } +} + +/// Instructions for painting an [`AtomLayout`]. +#[derive(Clone, Debug)] +pub struct AllocatedAtomLayout<'a> { + pub sized_atoms: SmallVec<[SizedAtom<'a>; ATOMS_SMALL_VEC_SIZE]>, + pub frame: Frame, + pub fallback_text_color: Color32, + pub response: Response, + grow_count: usize, + // The size of the inner content, before any growing. + desired_size: Vec2, + align2: Align2, + gap: f32, +} + +impl<'atom> AllocatedAtomLayout<'atom> { + pub fn iter_kinds(&self) -> impl Iterator> { + self.sized_atoms.iter().map(|atom| &atom.kind) + } + + pub fn iter_kinds_mut(&mut self) -> impl Iterator> { + self.sized_atoms.iter_mut().map(|atom| &mut atom.kind) + } + + pub fn iter_images(&self) -> impl Iterator> { + self.iter_kinds().filter_map(|kind| { + if let SizedAtomKind::Image(image, _) = kind { + Some(image) + } else { + None + } + }) + } + + pub fn iter_images_mut(&mut self) -> impl Iterator> { + self.iter_kinds_mut().filter_map(|kind| { + if let SizedAtomKind::Image(image, _) = kind { + Some(image) + } else { + None + } + }) + } + + pub fn iter_texts(&self) -> impl Iterator> + use<'atom, '_> { + self.iter_kinds().filter_map(|kind| { + if let SizedAtomKind::Text(text) = kind { + Some(text) + } else { + None + } + }) + } + + pub fn iter_texts_mut(&mut self) -> impl Iterator> + use<'atom, '_> { + self.iter_kinds_mut().filter_map(|kind| { + if let SizedAtomKind::Text(text) = kind { + Some(text) + } else { + None + } + }) + } + + pub fn map_kind(&mut self, mut f: F) + where + F: FnMut(SizedAtomKind<'atom>) -> SizedAtomKind<'atom>, + { + for kind in self.iter_kinds_mut() { + *kind = f(std::mem::take(kind)); + } + } + + pub fn map_images(&mut self, mut f: F) + where + F: FnMut(Image<'atom>) -> Image<'atom>, + { + self.map_kind(|kind| { + if let SizedAtomKind::Image(image, size) = kind { + SizedAtomKind::Image(f(image), size) + } else { + kind + } + }); + } + + /// Paint the [`Frame`] and individual [`crate::Atom`]s. + pub fn paint(self, ui: &Ui) -> AtomLayoutResponse { + let Self { + sized_atoms, + frame, + fallback_text_color, + response, + grow_count, + desired_size, + align2, + gap, + } = self; + + let inner_rect = response.rect - self.frame.total_margin(); + + ui.painter().add(frame.paint(inner_rect)); + + let width_to_fill = inner_rect.width(); + let extra_space = f32::max(width_to_fill - desired_size.x, 0.0); + let grow_width = f32::max(extra_space / grow_count as f32, 0.0).floor_ui(); + + let aligned_rect = if grow_count > 0 { + align2.align_size_within_rect(Vec2::new(width_to_fill, desired_size.y), inner_rect) + } else { + align2.align_size_within_rect(desired_size, inner_rect) + }; + + let mut cursor = aligned_rect.left(); + + let mut response = AtomLayoutResponse::empty(response); + + for sized in sized_atoms { + let size = sized.size; + // TODO(lucasmerlin): This is not ideal, since this might lead to accumulated rounding errors + // https://github.com/emilk/egui/pull/5830#discussion_r2079627864 + let growth = if sized.is_grow() { grow_width } else { 0.0 }; + + let frame = aligned_rect + .with_min_x(cursor) + .with_max_x(cursor + size.x + growth); + cursor = frame.right() + gap; + + let align = Align2::CENTER_CENTER; + let rect = align.align_size_within_rect(size, frame); + + match sized.kind { + SizedAtomKind::Text(galley) => { + ui.painter().galley(rect.min, galley, fallback_text_color); + } + SizedAtomKind::Image(image, _) => { + image.paint_at(ui, rect); + } + SizedAtomKind::Custom(id) => { + debug_assert!( + !response.custom_rects.iter().any(|(i, _)| *i == id), + "Duplicate custom id" + ); + response.custom_rects.push((id, rect)); + } + SizedAtomKind::Empty => {} + } + } + + response + } +} + +/// Response from a [`AtomLayout::show`] or [`AllocatedAtomLayout::paint`]. +/// +/// Use [`AtomLayoutResponse::rect`] to get the response rects from [`AtomKind::Custom`]. +#[derive(Clone, Debug)] +pub struct AtomLayoutResponse { + pub response: Response, + // There should rarely be more than one custom rect. + custom_rects: SmallVec<[(Id, Rect); 1]>, +} + +impl AtomLayoutResponse { + pub fn empty(response: Response) -> Self { + Self { + response, + custom_rects: Default::default(), + } + } + + pub fn custom_rects(&self) -> impl Iterator + '_ { + self.custom_rects.iter().copied() + } + + /// Use this together with [`AtomKind::Custom`] to add custom painting / child widgets. + /// + /// NOTE: Don't `unwrap` rects, they might be empty when the widget is not visible. + pub fn rect(&self, id: Id) -> Option { + self.custom_rects + .iter() + .find_map(|(i, r)| if *i == id { Some(*r) } else { None }) + } +} + +impl Widget for AtomLayout<'_> { + fn ui(self, ui: &mut Ui) -> Response { + self.show(ui).response + } +} + +impl<'a> Deref for AtomLayout<'a> { + type Target = Atoms<'a>; + + fn deref(&self) -> &Self::Target { + &self.atoms + } +} + +impl DerefMut for AtomLayout<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.atoms + } +} + +impl<'a> Deref for AllocatedAtomLayout<'a> { + type Target = [SizedAtom<'a>]; + + fn deref(&self) -> &Self::Target { + &self.sized_atoms + } +} + +impl DerefMut for AllocatedAtomLayout<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.sized_atoms + } +} diff --git a/crates/egui/src/atomics/atoms.rs b/crates/egui/src/atomics/atoms.rs new file mode 100644 index 000000000..3752ace70 --- /dev/null +++ b/crates/egui/src/atomics/atoms.rs @@ -0,0 +1,220 @@ +use crate::{Atom, AtomKind, Image, WidgetText}; +use smallvec::SmallVec; +use std::borrow::Cow; +use std::ops::{Deref, DerefMut}; + +// Rarely there should be more than 2 atoms in one Widget. +// I guess it could happen in a menu button with Image and right text... +pub(crate) const ATOMS_SMALL_VEC_SIZE: usize = 2; + +/// A list of [`Atom`]s. +#[derive(Clone, Debug, Default)] +pub struct Atoms<'a>(SmallVec<[Atom<'a>; ATOMS_SMALL_VEC_SIZE]>); + +impl<'a> Atoms<'a> { + pub fn new(atoms: impl IntoAtoms<'a>) -> Self { + atoms.into_atoms() + } + + /// Insert a new [`Atom`] at the end of the list (right side). + pub fn push_right(&mut self, atom: impl Into>) { + self.0.push(atom.into()); + } + + /// Insert a new [`Atom`] at the beginning of the list (left side). + pub fn push_left(&mut self, atom: impl Into>) { + self.0.insert(0, atom.into()); + } + + /// Concatenate and return the text contents. + // TODO(lucasmerlin): It might not always make sense to return the concatenated text, e.g. + // in a submenu button there is a right text '⏵' which is now passed to the screen reader. + pub fn text(&self) -> Option> { + let mut string: Option> = None; + for atom in &self.0 { + if let AtomKind::Text(text) = &atom.kind { + if let Some(string) = &mut string { + let string = string.to_mut(); + string.push(' '); + string.push_str(text.text()); + } else { + string = Some(Cow::Borrowed(text.text())); + } + } + } + string + } + + pub fn iter_kinds(&'a self) -> impl Iterator> { + self.0.iter().map(|atom| &atom.kind) + } + + pub fn iter_kinds_mut(&'a mut self) -> impl Iterator> { + self.0.iter_mut().map(|atom| &mut atom.kind) + } + + pub fn iter_images(&'a self) -> impl Iterator> { + self.iter_kinds().filter_map(|kind| { + if let AtomKind::Image(image) = kind { + Some(image) + } else { + None + } + }) + } + + pub fn iter_images_mut(&'a mut self) -> impl Iterator> { + self.iter_kinds_mut().filter_map(|kind| { + if let AtomKind::Image(image) = kind { + Some(image) + } else { + None + } + }) + } + + pub fn iter_texts(&'a self) -> impl Iterator { + self.iter_kinds().filter_map(|kind| { + if let AtomKind::Text(text) = kind { + Some(text) + } else { + None + } + }) + } + + pub fn iter_texts_mut(&'a mut self) -> impl Iterator { + self.iter_kinds_mut().filter_map(|kind| { + if let AtomKind::Text(text) = kind { + Some(text) + } else { + None + } + }) + } + + pub fn map_atoms(&mut self, mut f: impl FnMut(Atom<'a>) -> Atom<'a>) { + self.iter_mut() + .for_each(|atom| *atom = f(std::mem::take(atom))); + } + + pub fn map_kind(&'a mut self, mut f: F) + where + F: FnMut(AtomKind<'a>) -> AtomKind<'a>, + { + for kind in self.iter_kinds_mut() { + *kind = f(std::mem::take(kind)); + } + } + + pub fn map_images(&'a mut self, mut f: F) + where + F: FnMut(Image<'a>) -> Image<'a>, + { + self.map_kind(|kind| { + if let AtomKind::Image(image) = kind { + AtomKind::Image(f(image)) + } else { + kind + } + }); + } + + pub fn map_texts(&'a mut self, mut f: F) + where + F: FnMut(WidgetText) -> WidgetText, + { + self.map_kind(|kind| { + if let AtomKind::Text(text) = kind { + AtomKind::Text(f(text)) + } else { + kind + } + }); + } +} + +impl<'a> IntoIterator for Atoms<'a> { + type Item = Atom<'a>; + type IntoIter = smallvec::IntoIter<[Atom<'a>; ATOMS_SMALL_VEC_SIZE]>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +/// Helper trait to convert a tuple of atoms into [`Atoms`]. +/// +/// ``` +/// use egui::{Atoms, Image, IntoAtoms, RichText}; +/// let atoms: Atoms = ( +/// "Some text", +/// RichText::new("Some RichText"), +/// Image::new("some_image_url"), +/// ).into_atoms(); +/// ``` +impl<'a, T> IntoAtoms<'a> for T +where + T: Into>, +{ + fn collect(self, atoms: &mut Atoms<'a>) { + atoms.push_right(self); + } +} + +/// Trait for turning a tuple of [`Atom`]s into [`Atoms`]. +pub trait IntoAtoms<'a> { + fn collect(self, atoms: &mut Atoms<'a>); + + fn into_atoms(self) -> Atoms<'a> + where + Self: Sized, + { + let mut atoms = Atoms::default(); + self.collect(&mut atoms); + atoms + } +} + +impl<'a> IntoAtoms<'a> for Atoms<'a> { + fn collect(self, atoms: &mut Self) { + atoms.0.extend(self.0); + } +} + +macro_rules! all_the_atoms { + ($($T:ident),*) => { + impl<'a, $($T),*> IntoAtoms<'a> for ($($T),*) + where + $($T: IntoAtoms<'a>),* + { + fn collect(self, _atoms: &mut Atoms<'a>) { + #[allow(clippy::allow_attributes)] + #[allow(non_snake_case)] + let ($($T),*) = self; + $($T.collect(_atoms);)* + } + } + }; +} + +all_the_atoms!(); +all_the_atoms!(T0, T1); +all_the_atoms!(T0, T1, T2); +all_the_atoms!(T0, T1, T2, T3); +all_the_atoms!(T0, T1, T2, T3, T4); +all_the_atoms!(T0, T1, T2, T3, T4, T5); + +impl<'a> Deref for Atoms<'a> { + type Target = [Atom<'a>]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Atoms<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/egui/src/atomics/mod.rs b/crates/egui/src/atomics/mod.rs new file mode 100644 index 000000000..7c8922c97 --- /dev/null +++ b/crates/egui/src/atomics/mod.rs @@ -0,0 +1,15 @@ +mod atom; +mod atom_ext; +mod atom_kind; +mod atom_layout; +mod atoms; +mod sized_atom; +mod sized_atom_kind; + +pub use atom::*; +pub use atom_ext::*; +pub use atom_kind::*; +pub use atom_layout::*; +pub use atoms::*; +pub use sized_atom::*; +pub use sized_atom_kind::*; diff --git a/crates/egui/src/atomics/sized_atom.rs b/crates/egui/src/atomics/sized_atom.rs new file mode 100644 index 000000000..50fa443a9 --- /dev/null +++ b/crates/egui/src/atomics/sized_atom.rs @@ -0,0 +1,26 @@ +use crate::SizedAtomKind; +use emath::Vec2; + +/// A [`crate::Atom`] which has been sized. +#[derive(Clone, Debug)] +pub struct SizedAtom<'a> { + pub(crate) grow: bool, + + /// The size of the atom. + /// + /// Used for placing this atom in [`crate::AtomLayout`], the cursor will advance by + /// size.x + gap. + pub size: Vec2, + + /// Preferred size of the atom. This is used to calculate `Response::intrinsic_size`. + pub preferred_size: Vec2, + + pub kind: SizedAtomKind<'a>, +} + +impl SizedAtom<'_> { + /// Was this [`crate::Atom`] marked as `grow`? + pub fn is_grow(&self) -> bool { + self.grow + } +} diff --git a/crates/egui/src/atomics/sized_atom_kind.rs b/crates/egui/src/atomics/sized_atom_kind.rs new file mode 100644 index 000000000..ff8da1631 --- /dev/null +++ b/crates/egui/src/atomics/sized_atom_kind.rs @@ -0,0 +1,25 @@ +use crate::{Id, Image}; +use emath::Vec2; +use epaint::Galley; +use std::sync::Arc; + +/// A sized [`crate::AtomKind`]. +#[derive(Clone, Default, Debug)] +pub enum SizedAtomKind<'a> { + #[default] + Empty, + Text(Arc), + Image(Image<'a>, Vec2), + Custom(Id), +} + +impl SizedAtomKind<'_> { + /// Get the calculated size. + pub fn size(&self) -> Vec2 { + match self { + SizedAtomKind::Text(galley) => galley.size(), + SizedAtomKind::Image(_, size) => *size, + SizedAtomKind::Empty | SizedAtomKind::Custom(_) => Vec2::ZERO, + } + } +} diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index 228bbd86d..4fe06477d 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -1,7 +1,7 @@ use crate::style::StyleModifier; use crate::{ - Button, Color32, Context, Frame, Id, InnerResponse, Layout, Popup, PopupCloseBehavior, - Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget as _, WidgetText, + 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 epaint::Stroke; @@ -243,8 +243,8 @@ pub struct MenuButton<'a> { } impl<'a> MenuButton<'a> { - pub fn new(text: impl Into) -> Self { - Self::from_button(Button::new(text)) + pub fn new(atoms: impl IntoAtoms<'a>) -> Self { + Self::from_button(Button::new(atoms.into_atoms())) } /// Set the config for the menu. @@ -293,8 +293,8 @@ impl<'a> SubMenuButton<'a> { /// The default right arrow symbol: `"⏵"` pub const RIGHT_ARROW: &'static str = "⏵"; - pub fn new(text: impl Into) -> Self { - Self::from_button(Button::new(text).right_text("⏵")) + pub fn new(atoms: impl IntoAtoms<'a>) -> Self { + Self::from_button(Button::new(atoms.into_atoms()).right_text("⏵")) } /// Create a new submenu button from a [`Button`]. diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 6d42a230b..4ff7db230 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -444,6 +444,7 @@ mod widget_rect; pub mod widget_text; pub mod widgets; +mod atomics; #[cfg(feature = "callstack")] #[cfg(debug_assertions)] mod callstack; @@ -482,6 +483,7 @@ pub mod text { } pub use self::{ + atomics::*, containers::*, context::{Context, RepaintCause, RequestRepaintInfo}, data::{ diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 4174ca961..bc2ed860d 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -25,10 +25,10 @@ use crate::{ color_picker, Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, RadioButton, SelectableLabel, Separator, Spinner, TextEdit, Widget, }, - Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, LayerId, - Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText, Sense, - Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2, WidgetRect, - WidgetText, + 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, }; // ---------------------------------------------------------------------------- @@ -2055,8 +2055,8 @@ impl Ui { /// ``` #[must_use = "You should check if the user clicked this with `if ui.button(…).clicked() { … } "] #[inline] - pub fn button(&mut self, text: impl Into) -> Response { - Button::new(text).ui(self) + pub fn button<'a>(&mut self, atoms: impl IntoAtoms<'a>) -> Response { + Button::new(atoms).ui(self) } /// A button as small as normal body text. @@ -2073,8 +2073,8 @@ impl Ui { /// /// See also [`Self::toggle_value`]. #[inline] - pub fn checkbox(&mut self, checked: &mut bool, text: impl Into) -> Response { - Checkbox::new(checked, text).ui(self) + pub fn checkbox<'a>(&mut self, checked: &'a mut bool, atoms: impl IntoAtoms<'a>) -> Response { + Checkbox::new(checked, atoms).ui(self) } /// Acts like a checkbox, but looks like a [`SelectableLabel`]. @@ -2095,8 +2095,8 @@ impl Ui { /// Often you want to use [`Self::radio_value`] instead. #[must_use = "You should check if the user clicked this with `if ui.radio(…).clicked() { … } "] #[inline] - pub fn radio(&mut self, selected: bool, text: impl Into) -> Response { - RadioButton::new(selected, text).ui(self) + pub fn radio<'a>(&mut self, selected: bool, atoms: impl IntoAtoms<'a>) -> Response { + RadioButton::new(selected, atoms).ui(self) } /// Show a [`RadioButton`]. It is selected if `*current_value == selected_value`. @@ -2118,13 +2118,13 @@ impl Ui { /// } /// # }); /// ``` - pub fn radio_value( + pub fn radio_value<'a, Value: PartialEq>( &mut self, current_value: &mut Value, alternative: Value, - text: impl Into, + atoms: impl IntoAtoms<'a>, ) -> Response { - let mut response = self.radio(*current_value == alternative, text); + let mut response = self.radio(*current_value == alternative, atoms); if response.clicked() && *current_value != alternative { *current_value = alternative; response.mark_changed(); @@ -3041,15 +3041,15 @@ impl Ui { /// ``` /// /// See also: [`Self::close`] and [`Response::context_menu`]. - pub fn menu_button( + pub fn menu_button<'a, R>( &mut self, - title: impl Into, + atoms: impl IntoAtoms<'a>, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse> { let (response, inner) = if menu::is_in_menu(self) { - menu::SubMenuButton::new(title).ui(self, add_contents) + menu::SubMenuButton::new(atoms).ui(self, add_contents) } else { - menu::MenuButton::new(title).ui(self, add_contents) + menu::MenuButton::new(atoms).ui(self, add_contents) }; InnerResponse::new(inner.map(|i| i.inner), response) } diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index d9f98859b..d0edb6a26 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,7 +1,7 @@ -use std::{borrow::Cow, sync::Arc}; - use emath::GuiRounding as _; use epaint::text::TextFormat; +use std::fmt::Formatter; +use std::{borrow::Cow, sync::Arc}; use crate::{ text::{LayoutJob, TextWrapping}, @@ -521,6 +521,18 @@ pub enum WidgetText { Galley(Arc), } +impl std::fmt::Debug for WidgetText { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let text = self.text(); + match self { + Self::Text(_) => write!(f, "Text({text:?})"), + Self::RichText(_) => write!(f, "RichText({text:?})"), + Self::LayoutJob(_) => write!(f, "LayoutJob({text:?})"), + Self::Galley(_) => write!(f, "Galley({text:?})"), + } + } +} + impl Default for WidgetText { fn default() -> Self { Self::Text(String::new()) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 9ae573aef..aa75eabd8 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -1,6 +1,7 @@ use crate::{ - widgets, Align, Color32, CornerRadius, FontSelection, Image, NumExt as _, Rect, Response, - Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, + Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame, + Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextWrapMode, Ui, Vec2, Widget, + WidgetInfo, WidgetText, WidgetType, }; /// Clickable button with text. @@ -23,56 +24,66 @@ use crate::{ /// ``` #[must_use = "You should put this widget in a ui with `ui.add(widget);`"] pub struct Button<'a> { - image: Option>, - text: Option, - right_text: WidgetText, - wrap_mode: Option, - - /// None means default for interact + layout: AtomLayout<'a>, fill: Option, stroke: Option, - sense: Sense, small: bool, frame: Option, min_size: Vec2, corner_radius: Option, selected: bool, image_tint_follows_text_color: bool, + limit_image_size: bool, } impl<'a> Button<'a> { - pub fn new(text: impl Into) -> Self { - Self::opt_image_and_text(None, Some(text.into())) - } - - /// Creates a button with an image. The size of the image as displayed is defined by the provided size. - pub fn image(image: impl Into>) -> Self { - Self::opt_image_and_text(Some(image.into()), None) - } - - /// Creates a button with an image to the left of the text. The size of the image as displayed is defined by the provided size. - pub fn image_and_text(image: impl Into>, text: impl Into) -> Self { - Self::opt_image_and_text(Some(image.into()), Some(text.into())) - } - - pub fn opt_image_and_text(image: Option>, text: Option) -> Self { + pub fn new(atoms: impl IntoAtoms<'a>) -> Self { Self { - text, - image, - right_text: Default::default(), - wrap_mode: None, + layout: AtomLayout::new(atoms.into_atoms()).sense(Sense::click()), fill: None, stroke: None, - sense: Sense::click(), small: false, frame: None, min_size: Vec2::ZERO, corner_radius: None, selected: false, image_tint_follows_text_color: false, + limit_image_size: false, } } + /// 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 + /// (using [`crate::AtomExt::atom_max_height_font_size`]). + pub fn image(image: impl Into>) -> Self { + Self::opt_image_and_text(Some(image.into()), None) + } + + /// Creates a button with an image to the left of the text. + /// + /// Note: In contrast to [`Button::new`], this limits the image size to the default font height + /// (using [`crate::AtomExt::atom_max_height_font_size`]). + pub fn image_and_text(image: impl Into>, text: impl Into) -> Self { + Self::opt_image_and_text(Some(image.into()), Some(text.into())) + } + + /// Create a button with an optional image and optional text. + /// + /// Note: In contrast to [`Button::new`], this limits the image size to the default font height + /// (using [`crate::AtomExt::atom_max_height_font_size`]). + pub fn opt_image_and_text(image: Option>, text: Option) -> Self { + let mut button = Self::new(()); + if let Some(image) = image { + button.layout.push_right(image); + } + if let Some(text) = text { + button.layout.push_right(text); + } + button.limit_image_size = true; + button + } + /// Set the wrap mode for the text. /// /// By default, [`crate::Ui::wrap_mode`] will be used, which can be overridden with [`crate::Style::wrap_mode`]. @@ -80,23 +91,20 @@ impl<'a> Button<'a> { /// Note that any `\n` in the text will always produce a new line. #[inline] pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self { - self.wrap_mode = Some(wrap_mode); + self.layout = self.layout.wrap_mode(wrap_mode); self } /// Set [`Self::wrap_mode`] to [`TextWrapMode::Wrap`]. #[inline] - pub fn wrap(mut self) -> Self { - self.wrap_mode = Some(TextWrapMode::Wrap); - - self + pub fn wrap(self) -> Self { + self.wrap_mode(TextWrapMode::Wrap) } /// Set [`Self::wrap_mode`] to [`TextWrapMode::Truncate`]. #[inline] - pub fn truncate(mut self) -> Self { - self.wrap_mode = Some(TextWrapMode::Truncate); - self + pub fn truncate(self) -> Self { + self.wrap_mode(TextWrapMode::Truncate) } /// Override background fill color. Note that this will override any on-hover effects. @@ -104,7 +112,6 @@ impl<'a> Button<'a> { #[inline] pub fn fill(mut self, fill: impl Into) -> Self { self.fill = Some(fill.into()); - self.frame = Some(true); self } @@ -120,9 +127,6 @@ impl<'a> Button<'a> { /// Make this a small button, suitable for embedding into text. #[inline] pub fn small(mut self) -> Self { - if let Some(text) = self.text { - self.text = Some(text.text_style(TextStyle::Body)); - } self.small = true; self } @@ -138,7 +142,7 @@ impl<'a> Button<'a> { /// Change this to a drag-button with `Sense::drag()`. #[inline] pub fn sense(mut self, sense: Sense) -> Self { - self.sense = sense; + self.layout = self.layout.sense(sense); self } @@ -182,15 +186,22 @@ impl<'a> Button<'a> { /// /// See also [`Self::right_text`]. #[inline] - pub fn shortcut_text(mut self, shortcut_text: impl Into) -> Self { - self.right_text = shortcut_text.into().weak(); + pub fn shortcut_text(mut self, shortcut_text: impl Into>) -> Self { + let mut atom = shortcut_text.into(); + atom.kind = match atom.kind { + AtomKind::Text(text) => AtomKind::Text(text.weak()), + other => other, + }; + self.layout.push_right(Atom::grow()); + self.layout.push_right(atom); self } /// Show some text on the right side of the button. #[inline] - pub fn right_text(mut self, right_text: impl Into) -> Self { - self.right_text = right_text.into(); + pub fn right_text(mut self, right_text: impl Into>) -> Self { + self.layout.push_right(Atom::grow()); + self.layout.push_right(right_text.into()); self } @@ -200,39 +211,41 @@ impl<'a> Button<'a> { self.selected = selected; self } -} -impl Widget for Button<'_> { - fn ui(self, ui: &mut Ui) -> Response { + /// Show the button and return a [`AtomLayoutResponse`] for painting custom contents. + pub fn atom_ui(self, ui: &mut Ui) -> AtomLayoutResponse { let Button { - text, - image, - right_text, - wrap_mode, + mut layout, fill, stroke, - sense, small, frame, - min_size, + mut min_size, corner_radius, selected, image_tint_follows_text_color, + limit_image_size, } = self; - let frame = frame.unwrap_or_else(|| ui.visuals().button_frame); + if !small { + min_size.y = min_size.y.at_least(ui.spacing().interact_size.y); + } - let default_font_height = || { - let font_selection = FontSelection::default(); - let font_id = font_selection.resolve(ui.style()); - ui.fonts(|f| f.row_height(&font_id)) - }; + if limit_image_size { + layout.map_atoms(|atom| { + if matches!(&atom.kind, AtomKind::Image(_)) { + atom.atom_max_height_font_size(ui) + } else { + atom + } + }); + } - let text_font_height = ui - .fonts(|fonts| text.as_ref().map(|wt| wt.font_height(fonts, ui.style()))) - .unwrap_or_else(default_font_height); + let text = layout.text().map(String::from); - let mut button_padding = if frame { + let has_frame = frame.unwrap_or_else(|| ui.visuals().button_frame); + + let mut button_padding = if has_frame { ui.spacing().button_padding } else { Vec2::ZERO @@ -241,192 +254,53 @@ impl Widget for Button<'_> { button_padding.y = 0.0; } - let (space_available_for_image, right_text_font_height) = if let Some(text) = &text { - let font_height = ui.fonts(|fonts| text.font_height(fonts, ui.style())); - ( - Vec2::splat(font_height), // Reasonable? - font_height, - ) - } else { - ( - (ui.available_size() - 2.0 * button_padding).at_least(Vec2::ZERO), - default_font_height(), - ) - }; + let mut prepared = layout + .frame(Frame::new().inner_margin(button_padding)) + .min_size(min_size) + .allocate(ui); - let image_size = if let Some(image) = &image { - image - .load_and_calc_size(ui, space_available_for_image) - .unwrap_or(space_available_for_image) - } else { - Vec2::ZERO - }; + let response = if ui.is_rect_visible(prepared.response.rect) { + let visuals = ui.style().interact_selectable(&prepared.response, selected); - let gap_before_right_text = ui.spacing().item_spacing.x; - - let mut text_wrap_width = ui.available_width() - 2.0 * button_padding.x; - if image.is_some() { - text_wrap_width -= image_size.x + ui.spacing().icon_spacing; - } - - // Note: we don't wrap the right text - let right_galley = (!right_text.is_empty()).then(|| { - right_text.into_galley( - ui, - Some(TextWrapMode::Extend), - f32::INFINITY, - TextStyle::Button, - ) - }); - - if let Some(right_galley) = &right_galley { - // Leave space for the right text: - text_wrap_width -= gap_before_right_text + right_galley.size().x; - } - - let galley = - text.map(|text| text.into_galley(ui, wrap_mode, text_wrap_width, TextStyle::Button)); - - let mut desired_size = Vec2::ZERO; - if image.is_some() { - desired_size.x += image_size.x; - desired_size.y = desired_size.y.max(image_size.y); - } - if image.is_some() && galley.is_some() { - desired_size.x += ui.spacing().icon_spacing; - } - if let Some(galley) = &galley { - desired_size.x += galley.size().x; - desired_size.y = desired_size.y.max(galley.size().y).max(text_font_height); - } - if let Some(right_galley) = &right_galley { - desired_size.x += gap_before_right_text + right_galley.size().x; - desired_size.y = desired_size - .y - .max(right_galley.size().y) - .max(right_text_font_height); - } - desired_size += 2.0 * button_padding; - if !small { - desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); - } - desired_size = desired_size.at_least(min_size); - - let (rect, mut response) = ui.allocate_at_least(desired_size, sense); - response.widget_info(|| { - let mut widget_info = WidgetInfo::new(WidgetType::Button); - widget_info.enabled = ui.is_enabled(); - - if let Some(galley) = &galley { - widget_info.label = Some(galley.text().to_owned()); - } else if let Some(image) = &image { - widget_info.label = image.alt_text.clone(); + if image_tint_follows_text_color { + prepared.map_images(|image| image.tint(visuals.text_color())); } - widget_info - }); - if ui.is_rect_visible(rect) { - let visuals = ui.style().interact(&response); + prepared.fallback_text_color = visuals.text_color(); - let (frame_expansion, frame_cr, frame_fill, frame_stroke) = if selected { - let selection = ui.visuals().selection; - ( - Vec2::ZERO, - CornerRadius::ZERO, - selection.bg_fill, - selection.stroke, - ) - } else if frame { - let expansion = Vec2::splat(visuals.expansion); - ( - expansion, - visuals.corner_radius, - visuals.weak_bg_fill, - visuals.bg_stroke, - ) - } else { - Default::default() + if has_frame { + let stroke = stroke.unwrap_or(visuals.bg_stroke); + let fill = fill.unwrap_or(visuals.weak_bg_fill); + prepared.frame = prepared + .frame + .inner_margin( + button_padding + Vec2::splat(visuals.expansion) - Vec2::splat(stroke.width), + ) + .outer_margin(-Vec2::splat(visuals.expansion)) + .fill(fill) + .stroke(stroke) + .corner_radius(corner_radius.unwrap_or(visuals.corner_radius)); }; - let frame_cr = corner_radius.unwrap_or(frame_cr); - let frame_fill = fill.unwrap_or(frame_fill); - let frame_stroke = stroke.unwrap_or(frame_stroke); - ui.painter().rect( - rect.expand2(frame_expansion), - frame_cr, - frame_fill, - frame_stroke, - epaint::StrokeKind::Inside, - ); - let mut cursor_x = rect.min.x + button_padding.x; + prepared.paint(ui) + } else { + AtomLayoutResponse::empty(prepared.response) + }; - if let Some(image) = &image { - let mut image_pos = ui - .layout() - .align_size_within_rect(image_size, rect.shrink2(button_padding)) - .min; - if galley.is_some() || right_galley.is_some() { - image_pos.x = cursor_x; - } - let image_rect = Rect::from_min_size(image_pos, image_size); - cursor_x += image_size.x; - let tlr = image.load_for_size(ui.ctx(), image_size); - let mut image_options = image.image_options().clone(); - if image_tint_follows_text_color { - image_options.tint = image_options.tint * visuals.text_color(); - } - widgets::image::paint_texture_load_result( - ui, - &tlr, - image_rect, - image.show_loading_spinner, - &image_options, - None, - ); - response = widgets::image::texture_load_result_response( - &image.source(ui.ctx()), - &tlr, - response, - ); + response.response.widget_info(|| { + if let Some(text) = &text { + WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), text) + } else { + WidgetInfo::new(WidgetType::Button) } - - if image.is_some() && galley.is_some() { - cursor_x += ui.spacing().icon_spacing; - } - - if let Some(galley) = galley { - let mut text_pos = ui - .layout() - .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) - .min; - if image.is_some() || right_galley.is_some() { - text_pos.x = cursor_x; - } - ui.painter().galley(text_pos, galley, visuals.text_color()); - } - - if let Some(right_galley) = right_galley { - // Always align to the right - let layout = if ui.layout().is_horizontal() { - ui.layout().with_main_align(Align::Max) - } else { - ui.layout().with_cross_align(Align::Max) - }; - let right_text_pos = layout - .align_size_within_rect(right_galley.size(), rect.shrink2(button_padding)) - .min; - - ui.painter() - .galley(right_text_pos, right_galley, visuals.text_color()); - } - } - - if let Some(cursor) = ui.visuals().interact_cursor { - if response.hovered() { - ui.ctx().set_cursor_icon(cursor); - } - } + }); response } } + +impl Widget for Button<'_> { + fn ui(self, ui: &mut Ui) -> Response { + self.atom_ui(ui).response + } +} diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs index 97bd97b88..f7498de5a 100644 --- a/crates/egui/src/widgets/checkbox.rs +++ b/crates/egui/src/widgets/checkbox.rs @@ -1,6 +1,6 @@ use crate::{ - epaint, pos2, vec2, NumExt as _, Response, Sense, Shape, TextStyle, Ui, Vec2, Widget, - WidgetInfo, WidgetText, WidgetType, + epaint, pos2, Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, + Vec2, Widget, WidgetInfo, WidgetType, }; // TODO(emilk): allow checkbox without a text label @@ -19,21 +19,21 @@ use crate::{ #[must_use = "You should put this widget in a ui with `ui.add(widget);`"] pub struct Checkbox<'a> { checked: &'a mut bool, - text: WidgetText, + atoms: Atoms<'a>, indeterminate: bool, } impl<'a> Checkbox<'a> { - pub fn new(checked: &'a mut bool, text: impl Into) -> Self { + pub fn new(checked: &'a mut bool, atoms: impl IntoAtoms<'a>) -> Self { Checkbox { checked, - text: text.into(), + atoms: atoms.into_atoms(), indeterminate: false, } } pub fn without_text(checked: &'a mut bool) -> Self { - Self::new(checked, WidgetText::default()) + Self::new(checked, ()) } /// Display an indeterminate state (neither checked nor unchecked) @@ -51,92 +51,88 @@ impl Widget for Checkbox<'_> { fn ui(self, ui: &mut Ui) -> Response { let Checkbox { checked, - text, + mut atoms, indeterminate, } = self; let spacing = &ui.spacing(); let icon_width = spacing.icon_width; - let icon_spacing = spacing.icon_spacing; - let (galley, mut desired_size) = if text.is_empty() { - (None, vec2(icon_width, 0.0)) - } else { - let total_extra = vec2(icon_width + icon_spacing, 0.0); + let mut min_size = Vec2::splat(spacing.interact_size.y); + min_size.y = min_size.y.at_least(icon_width); - let wrap_width = ui.available_width() - total_extra.x; - let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); + // In order to center the checkbox based on min_size we set the icon height to at least min_size.y + let mut icon_size = Vec2::splat(icon_width); + icon_size.y = icon_size.y.at_least(min_size.y); + let rect_id = Id::new("egui::checkbox"); + atoms.push_left(Atom::custom(rect_id, icon_size)); - let mut desired_size = total_extra + galley.size(); - desired_size = desired_size.at_least(spacing.interact_size); + let text = atoms.text().map(String::from); - (Some(galley), desired_size) - }; + let mut prepared = AtomLayout::new(atoms) + .sense(Sense::click()) + .min_size(min_size) + .allocate(ui); - desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); - desired_size.y = desired_size.y.max(icon_width); - let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click()); - - if response.clicked() { + if prepared.response.clicked() { *checked = !*checked; - response.mark_changed(); + prepared.response.mark_changed(); } - response.widget_info(|| { + prepared.response.widget_info(|| { if indeterminate { WidgetInfo::labeled( WidgetType::Checkbox, ui.is_enabled(), - galley.as_ref().map_or("", |x| x.text()), + text.as_deref().unwrap_or(""), ) } else { WidgetInfo::selected( WidgetType::Checkbox, ui.is_enabled(), *checked, - galley.as_ref().map_or("", |x| x.text()), + text.as_deref().unwrap_or(""), ) } }); - if ui.is_rect_visible(rect) { + if ui.is_rect_visible(prepared.response.rect) { // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful - let visuals = ui.style().interact(&response); - let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); - ui.painter().add(epaint::RectShape::new( - big_icon_rect.expand(visuals.expansion), - visuals.corner_radius, - visuals.bg_fill, - visuals.bg_stroke, - epaint::StrokeKind::Inside, - )); + let visuals = *ui.style().interact(&prepared.response); + prepared.fallback_text_color = visuals.text_color(); + let response = prepared.paint(ui); - if indeterminate { - // Horizontal line: - ui.painter().add(Shape::hline( - small_icon_rect.x_range(), - small_icon_rect.center().y, - visuals.fg_stroke, - )); - } else if *checked { - // Check mark: - ui.painter().add(Shape::line( - vec![ - pos2(small_icon_rect.left(), small_icon_rect.center().y), - pos2(small_icon_rect.center().x, small_icon_rect.bottom()), - pos2(small_icon_rect.right(), small_icon_rect.top()), - ], - visuals.fg_stroke, + if let Some(rect) = response.rect(rect_id) { + let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); + ui.painter().add(epaint::RectShape::new( + big_icon_rect.expand(visuals.expansion), + visuals.corner_radius, + visuals.bg_fill, + visuals.bg_stroke, + epaint::StrokeKind::Inside, )); + + if indeterminate { + // Horizontal line: + ui.painter().add(Shape::hline( + small_icon_rect.x_range(), + small_icon_rect.center().y, + visuals.fg_stroke, + )); + } else if *checked { + // Check mark: + ui.painter().add(Shape::line( + vec![ + pos2(small_icon_rect.left(), small_icon_rect.center().y), + pos2(small_icon_rect.center().x, small_icon_rect.bottom()), + pos2(small_icon_rect.right(), small_icon_rect.top()), + ], + visuals.fg_stroke, + )); + } } - if let Some(galley) = galley { - let text_pos = pos2( - rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * galley.size().y, - ); - ui.painter().galley(text_pos, galley, visuals.text_color()); - } + response.response + } else { + prepared.response } - - response } } diff --git a/crates/egui/src/widgets/radio_button.rs b/crates/egui/src/widgets/radio_button.rs index 7c178840d..53dda399f 100644 --- a/crates/egui/src/widgets/radio_button.rs +++ b/crates/egui/src/widgets/radio_button.rs @@ -1,6 +1,6 @@ use crate::{ - epaint, pos2, vec2, NumExt as _, Response, Sense, TextStyle, Ui, Vec2, Widget, WidgetInfo, - WidgetText, WidgetType, + epaint, Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Ui, Vec2, Widget, + WidgetInfo, WidgetType, }; /// One out of several alternatives, either selected or not. @@ -23,89 +23,84 @@ use crate::{ /// # }); /// ``` #[must_use = "You should put this widget in a ui with `ui.add(widget);`"] -pub struct RadioButton { +pub struct RadioButton<'a> { checked: bool, - text: WidgetText, + atoms: Atoms<'a>, } -impl RadioButton { - pub fn new(checked: bool, text: impl Into) -> Self { +impl<'a> RadioButton<'a> { + pub fn new(checked: bool, atoms: impl IntoAtoms<'a>) -> Self { Self { checked, - text: text.into(), + atoms: atoms.into_atoms(), } } } -impl Widget for RadioButton { +impl Widget for RadioButton<'_> { fn ui(self, ui: &mut Ui) -> Response { - let Self { checked, text } = self; + let Self { checked, mut atoms } = self; let spacing = &ui.spacing(); let icon_width = spacing.icon_width; - let icon_spacing = spacing.icon_spacing; - let (galley, mut desired_size) = if text.is_empty() { - (None, vec2(icon_width, 0.0)) - } else { - let total_extra = vec2(icon_width + icon_spacing, 0.0); + let mut min_size = Vec2::splat(spacing.interact_size.y); + min_size.y = min_size.y.at_least(icon_width); - let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); + // In order to center the checkbox based on min_size we set the icon height to at least min_size.y + let mut icon_size = Vec2::splat(icon_width); + icon_size.y = icon_size.y.at_least(min_size.y); + let rect_id = Id::new("egui::radio_button"); + atoms.push_left(Atom::custom(rect_id, icon_size)); - let mut desired_size = total_extra + text.size(); - desired_size = desired_size.at_least(spacing.interact_size); + let text = atoms.text().map(String::from); - (Some(text), desired_size) - }; + let mut prepared = AtomLayout::new(atoms) + .sense(Sense::click()) + .min_size(min_size) + .allocate(ui); - desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); - desired_size.y = desired_size.y.max(icon_width); - let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); - - response.widget_info(|| { + prepared.response.widget_info(|| { WidgetInfo::selected( WidgetType::RadioButton, ui.is_enabled(), checked, - galley.as_ref().map_or("", |x| x.text()), + text.as_deref().unwrap_or(""), ) }); - if ui.is_rect_visible(rect) { + if ui.is_rect_visible(prepared.response.rect) { // let visuals = ui.style().interact_selectable(&response, checked); // too colorful - let visuals = ui.style().interact(&response); + let visuals = *ui.style().interact(&prepared.response); - let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); + prepared.fallback_text_color = visuals.text_color(); + let response = prepared.paint(ui); - let painter = ui.painter(); + if let Some(rect) = response.rect(rect_id) { + let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); - painter.add(epaint::CircleShape { - center: big_icon_rect.center(), - radius: big_icon_rect.width() / 2.0 + visuals.expansion, - fill: visuals.bg_fill, - stroke: visuals.bg_stroke, - }); + let painter = ui.painter(); - if checked { painter.add(epaint::CircleShape { - center: small_icon_rect.center(), - radius: small_icon_rect.width() / 3.0, - fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill - // fill: ui.visuals().selection.stroke.color, // too much color - stroke: Default::default(), + center: big_icon_rect.center(), + radius: big_icon_rect.width() / 2.0 + visuals.expansion, + fill: visuals.bg_fill, + stroke: visuals.bg_stroke, }); - } - if let Some(galley) = galley { - let text_pos = pos2( - rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * galley.size().y, - ); - ui.painter().galley(text_pos, galley, visuals.text_color()); + if checked { + painter.add(epaint::CircleShape { + center: small_icon_rect.center(), + radius: small_icon_rect.width() / 3.0, + fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill + // fill: ui.visuals().selection.stroke.color, // too much color + stroke: Default::default(), + }); + } } + response.response + } else { + prepared.response } - - response } } diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index 57c88b50a..d5bde1f98 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:f7572ec2dad9038c24beb9949e4c05155cd0f5479153de6647c38911ec5c67a0 -size 100779 +oid sha256:e2fae780123389ca0affa762a5c031b84abcdd31c7a830d485c907c8c370b006 +size 100780 diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index c7b6df28a..31f5d279a 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -322,7 +322,10 @@ mod tests { let mut harness = Harness::builder() .with_pixels_per_point(2.0) .with_size(Vec2::new(380.0, 550.0)) - .build_ui(|ui| demo.ui(ui)); + .build_ui(|ui| { + egui_extras::install_image_loaders(ui.ctx()); + demo.ui(ui); + }); harness.fit_contents(); 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 526dc7ace..394bea644 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:c69c211061663cd17756eb0ad5a7720ed883047dbcedb39c493c544cfc644ed3 +oid sha256:0c975c8b646425878b704f32198286010730746caf5d463ca8cbcfe539922816 size 99087 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png index e2899160b..7800f5f5f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18fe761145335a60b1eeb1f7f2072224df86f0e2006caa09d1f3cc4bd263d90c -size 46560 +oid sha256:c47a19d1f56fcc4c30c7e88aada2a50e038d66c1b591b4646b86c11bffb3c66f +size 46563 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index 212a7ccd4..ea8f9c857 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:0fcfee082fe1dcbb7515ca6e3d5457e71fecf91a3efc4f76906a32fdb588adb4 -size 35096 +oid sha256:cdff6256488f3a40c65a3d73c0635377bf661c57927bce4c853b2a5f3b33274e +size 35121 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 17e76840e..49b223e7d 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:b236fe02f6cd52041359cf4b1a00e9812b95560353ce5df4fa6cb20fdbb45307 +oid sha256:4a347875ef98ebbd606774e03baffdb317cb0246882db116fee1aa7685efbb88 size 179653 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 09f8eaa6c..92e94b78f 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:1579351658875af48ad9aafeb08d928d83f1bda42bf092fdcceecd0aa6730e26 -size 115313 +oid sha256:f0e3eeca8abb4fba632cef4621d478fb66af1a0f13e099dda9a79420cc2b6301 +size 115320 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index c11788f40..8a269fd4e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9446da28768cae0b489e0f6243410a8b3acf0ca2a0b70690d65d2a6221bc25b9 -size 30517 +oid sha256:9d27ed8292a2612b337f663bff73cd009a82f806c61f0863bf70a53fd4c281ff +size 75074 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png index b05ebda12..bcb09fe26 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:af75f773e9e4ad2615893babce5b99e7fd127c76dd0976ac8dc95307f38a59dc -size 152854 +oid sha256:ee129f0542f21e12f5aa3c2f9746e7cadd73441a04d580f57c12c1cdd40d8b07 +size 153136 diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 6bb51184f..8d688ae8e 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -511,7 +511,7 @@ struct Highlighter {} #[cfg(not(feature = "syntect"))] impl Highlighter { - #[expect(clippy::unused_self, clippy::unnecessary_wraps)] + #[expect(clippy::unused_self)] fn highlight_impl( &self, theme: &CodeTheme, diff --git a/crates/egui_kittest/tests/menu.rs b/crates/egui_kittest/tests/menu.rs index a01e39dca..fc19e8040 100644 --- a/crates/egui_kittest/tests/menu.rs +++ b/crates/egui_kittest/tests/menu.rs @@ -99,7 +99,7 @@ fn menu_close_on_click_outside() { harness.run(); harness - .get_by_label("Submenu C (CloseOnClickOutside)") + .get_by_label_contains("Submenu C (CloseOnClickOutside)") .hover(); harness.run(); @@ -133,7 +133,7 @@ fn menu_close_on_click() { harness.get_by_label("Menu A").simulate_click(); harness.run(); - harness.get_by_label("Submenu B with icon").hover(); + 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) @@ -154,7 +154,9 @@ fn clicking_submenu_button_should_never_close_menu() { harness.run(); // Clicking the submenu button should not close the menu - harness.get_by_label("Submenu B with icon").simulate_click(); + harness + .get_by_label_contains("Submenu B with icon") + .simulate_click(); harness.run(); harness.get_by_label("Button in Submenu B").simulate_click(); @@ -177,12 +179,12 @@ fn menu_snapshots() { results.add(harness.try_snapshot("menu/opened")); harness - .get_by_label("Submenu C (CloseOnClickOutside)") + .get_by_label_contains("Submenu C (CloseOnClickOutside)") .hover(); harness.run(); results.add(harness.try_snapshot("menu/submenu")); - harness.get_by_label("Submenu D").hover(); + harness.get_by_label_contains("Submenu D").hover(); harness.run(); results.add(harness.try_snapshot("menu/subsubmenu")); } diff --git a/tests/egui_tests/tests/snapshots/grow_all.png b/tests/egui_tests/tests/snapshots/grow_all.png new file mode 100644 index 000000000..7ef69ea16 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/grow_all.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34f0c49cef96c7c3d08dbe835efd9366a4ced6ad2c6aa7facb0de08fd1a44648 +size 14011 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_image.png b/tests/egui_tests/tests/snapshots/layout/atoms_image.png new file mode 100644 index 000000000..3d9efa8a1 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/atoms_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39a13fdac498d6f851a28ea3ca19d523235d5e0ab8e765ea980cf8fb2f64ba35 +size 387619 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png new file mode 100644 index 000000000..1eb0a8348 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a09e926d25e2b6f63dc6df00ab5e5b76745aae1f288231f1a602421b2bbb53b +size 384721 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png new file mode 100644 index 000000000..87211765f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14a1dc826aeced98cab1413f915dcbbe904b5b1eadfc4d811232bc8ccbe7f550 +size 299556 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png index 737f0670c..fb6ff3b34 100644 --- a/tests/egui_tests/tests/snapshots/layout/button_image.png +++ b/tests/egui_tests/tests/snapshots/layout/button_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:975c279d6da2a2cb000df72bf5d9f3bdd200bb20adc00e29e8fd9ed4d2c6f6b1 -size 340923 +oid sha256:01309596ac9eb90b2dfc00074cfd39d26e3f6d1f83299f227cb4bbea9ccd3b66 +size 339917 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png index 66ae8115f..9c6fb4c07 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaf9b032037d0708894e568cc8e256b32be9cfb586eaffdc6167143b85562b37 -size 415016 +oid sha256:1d842f88b6a94f19aa59bdae9dbbf42f4662aaead1b8f73ac0194f183112e1b8 +size 415066 diff --git a/tests/egui_tests/tests/snapshots/max_width.png b/tests/egui_tests/tests/snapshots/max_width.png new file mode 100644 index 000000000..bab2f3876 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/max_width.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90cfa6e9be28ef538491ad94615e162ecc107df6a320084ec30840a75660ac35 +size 8759 diff --git a/tests/egui_tests/tests/snapshots/max_width_and_grow.png b/tests/egui_tests/tests/snapshots/max_width_and_grow.png new file mode 100644 index 000000000..077bccbd8 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/max_width_and_grow.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:effb4a69a7a6af12614be59a0afb0be2d2ebad402da3d7ee99fa25ae350bf4a0 +size 8761 diff --git a/tests/egui_tests/tests/snapshots/shrink_first_text.png b/tests/egui_tests/tests/snapshots/shrink_first_text.png new file mode 100644 index 000000000..c9196ea24 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/shrink_first_text.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf5032b2a08f993ae023934715222fe8d35a3a2e5cc09026d9e7ea3c296a9dc7 +size 11609 diff --git a/tests/egui_tests/tests/snapshots/shrink_last_text.png b/tests/egui_tests/tests/snapshots/shrink_last_text.png new file mode 100644 index 000000000..038b70a2f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/shrink_last_text.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84d0c37a198fb56d8608a201dbe7ad19e7de7802bd5110316b36228e14b5f330 +size 12140 diff --git a/tests/egui_tests/tests/snapshots/size_max_size.png b/tests/egui_tests/tests/snapshots/size_max_size.png new file mode 100644 index 000000000..3ea8feab0 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/size_max_size.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6a7555290f6121d6e48657e3ae810976b540ee9328909aca2d6c078b3d76ab4 +size 8735 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 3ff34c6be..114baa35d 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:09c5904877c8895d3ad41b7082019ef87db40c6a91ad47401bb9b8ac79a62bdc -size 12914 +oid sha256:f9151f1c9d8a769ac2143a684cabf5d9ed1e453141fff555da245092003f1df1 +size 13563 diff --git a/tests/egui_tests/tests/test_atoms.rs b/tests/egui_tests/tests/test_atoms.rs new file mode 100644 index 000000000..abc9f2d05 --- /dev/null +++ b/tests/egui_tests/tests/test_atoms.rs @@ -0,0 +1,71 @@ +use egui::{Align, AtomExt as _, Button, Layout, TextWrapMode, Ui, Vec2}; +use egui_kittest::{HarnessBuilder, SnapshotResult, SnapshotResults}; + +#[test] +fn test_atoms() { + let mut results = SnapshotResults::new(); + + results.add(single_test("max_width", |ui| { + ui.add(Button::new(( + "max width not grow".atom_max_width(30.0), + "other text", + ))); + })); + results.add(single_test("max_width_and_grow", |ui| { + ui.add(Button::new(( + "max width and grow".atom_max_width(30.0).atom_grow(true), + "other text", + ))); + })); + results.add(single_test("shrink_first_text", |ui| { + ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); + ui.add(Button::new(("this should shrink", "this shouldn't"))); + })); + results.add(single_test("shrink_last_text", |ui| { + ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); + ui.add(Button::new(( + "this shouldn't shrink", + "this should".atom_shrink(true), + ))); + })); + results.add(single_test("grow_all", |ui| { + ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); + ui.add(Button::new(( + "I grow".atom_grow(true), + "I also grow".atom_grow(true), + "I grow as well".atom_grow(true), + ))); + })); + results.add(single_test("size_max_size", |ui| { + ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); + ui.add(Button::new(( + "size and max size" + .atom_size(Vec2::new(80.0, 80.0)) + .atom_max_size(Vec2::new(20.0, 20.0)), + "other text".atom_grow(true), + ))); + })); +} + +fn single_test(name: &str, mut f: impl FnMut(&mut Ui)) -> SnapshotResult { + let mut harness = HarnessBuilder::default() + .with_size(Vec2::new(400.0, 200.0)) + .build_ui(move |ui| { + ui.label("Normal"); + let normal_width = ui.horizontal(&mut f).response.rect.width(); + + ui.label("Justified"); + ui.with_layout( + Layout::left_to_right(Align::Min).with_main_justify(true), + &mut f, + ); + + ui.label("Shrunk"); + ui.scope(|ui| { + ui.set_max_width(normal_width / 2.0); + f(ui); + }); + }); + + harness.try_snapshot(name) +} diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs index 96015cbd9..110eff810 100644 --- a/tests/egui_tests/tests/test_widgets.rs +++ b/tests/egui_tests/tests/test_widgets.rs @@ -1,8 +1,8 @@ use egui::load::SizedTexture; use egui::{ - include_image, Align, Button, Color32, ColorImage, Direction, DragValue, Event, Grid, Layout, - PointerButton, Pos2, Response, Slider, Stroke, StrokeKind, TextWrapMode, TextureHandle, - TextureOptions, Ui, UiBuilder, Vec2, Widget as _, + include_image, Align, AtomExt as _, AtomLayout, Button, Color32, ColorImage, Direction, + DragValue, Event, Grid, IntoAtoms as _, Layout, PointerButton, Pos2, Response, Slider, Stroke, + StrokeKind, TextWrapMode, TextureHandle, TextureOptions, Ui, UiBuilder, Vec2, Widget as _, }; use egui_kittest::kittest::{by, Node, Queryable as _}; use egui_kittest::{Harness, SnapshotResult, SnapshotResults}; @@ -92,6 +92,25 @@ fn widget_tests() { }, &mut results, ); + + let source = include_image!("../../../crates/eframe/data/icon.png"); + let interesting_atoms = vec![ + ("minimal", ("Hello World!").into_atoms()), + ( + "image", + (source.clone().atom_max_height(12.0), "With Image").into_atoms(), + ), + ( + "multi_grow", + ("g".atom_grow(true), "2", "g".atom_grow(true), "4").into_atoms(), + ), + ]; + + for atoms in interesting_atoms { + results.add(test_widget_layout(&format!("atoms_{}", atoms.0), |ui| { + AtomLayout::new(atoms.1.clone()).ui(ui) + })); + } } fn test_widget(name: &str, mut w: impl FnMut(&mut Ui) -> Response, results: &mut SnapshotResults) { From 5bc19f3ca3b5725f0f8219c7d45fddbaca3dca6f Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Fri, 13 Jun 2025 13:54:07 +0200 Subject: [PATCH 083/388] Report image alt text as text if widget contains no other text (#7142) - Same as https://github.com/emilk/egui/pull/7136 but now for atomics --- crates/egui/src/atomics/atoms.rs | 9 +++++++++ tests/egui_tests/tests/regression_tests.rs | 14 ++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/egui_tests/tests/regression_tests.rs diff --git a/crates/egui/src/atomics/atoms.rs b/crates/egui/src/atomics/atoms.rs index 3752ace70..635b2a132 100644 --- a/crates/egui/src/atomics/atoms.rs +++ b/crates/egui/src/atomics/atoms.rs @@ -42,6 +42,15 @@ impl<'a> Atoms<'a> { } } } + + // If there is no text, try to find an image with alt text. + if string.is_none() { + string = self.iter().find_map(|a| match &a.kind { + AtomKind::Image(image) => image.alt_text.as_deref().map(Cow::Borrowed), + _ => None, + }); + } + string } diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs new file mode 100644 index 000000000..d67759427 --- /dev/null +++ b/tests/egui_tests/tests/regression_tests.rs @@ -0,0 +1,14 @@ +use egui::{include_image, Image}; +use egui_kittest::kittest::Queryable as _; +use egui_kittest::Harness; + +#[test] +fn image_button_should_have_alt_text() { + let harness = Harness::new_ui(|ui| { + _ = ui.button( + Image::new(include_image!("../../../crates/eframe/data/icon.png")).alt_text("Egui"), + ); + }); + + harness.get_by_label("Egui"); +} From 4c04996a72e6dbff67db5cca6bee3682952dfc3e Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Fri, 13 Jun 2025 14:06:50 +0200 Subject: [PATCH 084/388] Fix missing repaint after `consume_key` (#7134) Usually input events automatically trigger a repaint. But since consume_key would remove the event egui would think there were no events and not trigger a repaint. This fixes it by setting a flag on InputState on consume_key. * related: https://github.com/rerun-io/rerun/issues/10165 --- crates/egui/src/context.rs | 8 +++++--- crates/egui/src/input_state/mod.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 3204927b8..27a3fcd9b 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -492,6 +492,7 @@ impl ContextImpl { pixels_per_point, self.memory.options.input_options, ); + let repaint_after = viewport.input.wants_repaint_after(); let screen_rect = viewport.input.screen_rect; @@ -553,6 +554,10 @@ impl ContextImpl { } self.update_fonts_mut(); + + if let Some(delay) = repaint_after { + self.request_repaint_after(delay, viewport_id, RepaintCause::new()); + } } /// Load fonts unless already loaded. @@ -2398,10 +2403,7 @@ impl ContextImpl { if repaint_needed { self.request_repaint(ended_viewport_id, RepaintCause::new()); - } else if let Some(delay) = viewport.input.wants_repaint_after() { - self.request_repaint_after(delay, ended_viewport_id, RepaintCause::new()); } - // ------------------- let all_viewport_ids = self.all_viewport_ids(); diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index c871a8452..7fd073167 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -597,10 +597,14 @@ impl InputState { (self.time - self.last_scroll_time) as f32 } - /// The [`crate::Context`] will call this at the end of each frame to see if we need a repaint. + /// The [`crate::Context`] will call this at the beginning of each frame to see if we need a repaint. /// /// Returns how long to wait for a repaint. - pub fn wants_repaint_after(&self) -> Option { + /// + /// NOTE: It's important to call this immediately after [`Self::begin_pass`] since calls to + /// [`Self::consume_key`] will remove events from the vec, meaning those key presses wouldn't + /// cause a repaint. + pub(crate) fn wants_repaint_after(&self) -> Option { if self.pointer.wants_repaint() || self.unprocessed_scroll_delta.abs().max_elem() > 0.2 || self.unprocessed_scroll_delta_for_zoom.abs() > 0.2 From a126be4dc15f30d692a8fd176bc42972009376a9 Mon Sep 17 00:00:00 2001 From: Gerhard de Clercq <11624490+Gerharddc@users.noreply.github.com> Date: Mon, 16 Jun 2025 01:28:04 +0200 Subject: [PATCH 085/388] Mention VTK 3D integration example (#7086) This commit adds a reference to an additional 3D integration example (using the VTK C++ library) to the README. ![Demo](https://github.com/user-attachments/assets/99cbe4e6-0aaf-41c2-9b18-179d58047284) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4f1e62c4e..0bcff697e 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,7 @@ You can also render your 3D scene to a texture and display it using [`ui.image( Examples: * Using [`egui-miniquad`]( https://github.com/not-fl3/egui-miniquad): https://github.com/not-fl3/egui-miniquad/blob/master/examples/render_to_egui_image.rs +* Using [`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe) + [`VTK (C++)`](https://vtk.org/): https://github.com/Gerharddc/vtk-egui-demo ## Other From 742da95bd7a6f18c1aacc49fb1ce93c537e38576 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Mon, 16 Jun 2025 02:28:27 +0300 Subject: [PATCH 086/388] Support for Back button Key on Android (#7073) When your press a Back button on Android (for example at `native-activity`), [Winit translates this key](https://github.com/rust-windowing/winit/blob/47b938dbe78702d521c2c7a43b6f741a3bb8cb0b/src/platform_impl/android/keycodes.rs#L237C42-L237C53) as `NamedKey::BrowserBack`. Added convertion to `Key::Escape` at `egui-winit` module. --------- Co-authored-by: Advocat --- crates/egui-winit/src/lib.rs | 2 ++ crates/egui/src/data/key.rs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index c196acaa9..f205c3108 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1144,6 +1144,8 @@ fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option NamedKey::F33 => Key::F33, NamedKey::F34 => Key::F34, NamedKey::F35 => Key::F35, + + NamedKey::BrowserBack => Key::BrowserBack, _ => { log::trace!("Unknown key: {named_key:?}"); return None; diff --git a/crates/egui/src/data/key.rs b/crates/egui/src/data/key.rs index 602ec5bda..2a0f33fc3 100644 --- a/crates/egui/src/data/key.rs +++ b/crates/egui/src/data/key.rs @@ -183,6 +183,11 @@ pub enum Key { F33, F34, F35, + + /// Back navigation key from multimedia keyboard. + /// Android sends this key on Back button press. + /// Does not work on Web. + BrowserBack, // When adding keys, remember to also update: // * crates/egui-winit/src/lib.rs // * Key::ALL @@ -307,6 +312,8 @@ impl Key { Self::F33, Self::F34, Self::F35, + // Navigation keys: + Self::BrowserBack, ]; /// Converts `"A"` to `Key::A`, `Space` to `Key::Space`, etc. @@ -435,6 +442,8 @@ impl Key { "F34" => Self::F34, "F35" => Self::F35, + "BrowserBack" => Self::BrowserBack, + _ => return None, }) } @@ -588,6 +597,8 @@ impl Key { Self::F33 => "F33", Self::F34 => "F34", Self::F35 => "F35", + + Self::BrowserBack => "BrowserBack", } } } @@ -596,7 +607,7 @@ impl Key { fn test_key_from_name() { assert_eq!( Key::ALL.len(), - Key::F35 as usize + 1, + Key::BrowserBack as usize + 1, "Some keys are missing in Key::ALL" ); From f33ff2c83d5fe38592ed3f93182b67cdb6cf77ef Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 16 Jun 2025 01:40:42 +0200 Subject: [PATCH 087/388] Make `HSVA` derive serde (#7132) This pull request introduces a change to the `Hsva` struct in the `crates/ecolor/src/hsva.rs` file to enable serialization and deserialization when the `serde` feature is enabled. * Closes * [x] I have followed the instructions in the PR template --- crates/ecolor/src/hsva.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ecolor/src/hsva.rs b/crates/ecolor/src/hsva.rs index 02ffd67d6..8388e4139 100644 --- a/crates/ecolor/src/hsva.rs +++ b/crates/ecolor/src/hsva.rs @@ -4,6 +4,7 @@ use crate::{ /// Hue, saturation, value, alpha. All in the range [0, 1]. /// No premultiplied alpha. +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Hsva { /// hue 0-1 From df2c16ef0a895dac6d269f36f419f6e495e16c15 Mon Sep 17 00:00:00 2001 From: Patrick Marks Date: Mon, 16 Jun 2025 01:42:01 +0200 Subject: [PATCH 088/388] Add anchored text rotation method, and clarify related docs (#7130) Add a helper method to perform rotation about a specified anchor. * Closes #7051 --- .../src/demo/misc_demo_window.rs | 102 +++++++++++++++++- .../tests/snapshots/demos/Misc Demos.png | 4 +- crates/epaint/src/shapes/text_shape.rs | 15 ++- 3 files changed, 116 insertions(+), 5 deletions(-) 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 edb19c3ea..99d85aeeb 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, Checkbox, CollapsingHeader, Color32, Context, FontId, Resize, RichText, Sense, - Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, + vec2, Align, Align2, Checkbox, CollapsingHeader, Color32, ComboBox, Context, FontId, Resize, + RichText, Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, }; /// Showcase some ui code @@ -16,6 +16,7 @@ pub struct MiscDemoWindow { custom_collapsing_header: CustomCollapsingHeader, tree: Tree, box_painting: BoxPainting, + text_rotation: TextRotation, dummy_bool: bool, dummy_usize: usize, @@ -32,6 +33,7 @@ impl Default for MiscDemoWindow { custom_collapsing_header: Default::default(), tree: Tree::demo(), box_painting: Default::default(), + text_rotation: Default::default(), dummy_bool: false, dummy_usize: 0, @@ -79,6 +81,10 @@ impl View for MiscDemoWindow { }); }); + CollapsingHeader::new("Text rotation") + .default_open(false) + .show(ui, |ui| self.text_rotation.ui(ui)); + CollapsingHeader::new("Colors") .default_open(false) .show(ui, |ui| { @@ -729,3 +735,95 @@ fn text_layout_demo(ui: &mut Ui) { ui.label(job); } + +// ---------------------------------------------------------------------------- + +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +struct TextRotation { + size: Vec2, + angle: f32, + align: egui::Align2, +} + +impl Default for TextRotation { + fn default() -> Self { + Self { + size: vec2(200.0, 200.0), + angle: 0.0, + align: egui::Align2::LEFT_TOP, + } + } +} + +impl TextRotation { + pub fn ui(&mut self, ui: &mut Ui) { + ui.add(Slider::new(&mut self.angle, 0.0..=2.0 * std::f32::consts::PI).text("angle")); + + let default_color = if ui.visuals().dark_mode { + Color32::LIGHT_GRAY + } else { + Color32::DARK_GRAY + }; + + let aligns = [ + (Align2::LEFT_TOP, "LEFT_TOP"), + (Align2::LEFT_CENTER, "LEFT_CENTER"), + (Align2::LEFT_BOTTOM, "LEFT_BOTTOM"), + (Align2::CENTER_TOP, "CENTER_TOP"), + (Align2::CENTER_CENTER, "CENTER_CENTER"), + (Align2::CENTER_BOTTOM, "CENTER_BOTTOM"), + (Align2::RIGHT_TOP, "RIGHT_TOP"), + (Align2::RIGHT_CENTER, "RIGHT_CENTER"), + (Align2::RIGHT_BOTTOM, "RIGHT_BOTTOM"), + ]; + + ComboBox::new("anchor", "Anchor") + .selected_text(aligns.iter().find(|(a, _)| *a == self.align).unwrap().1) + .show_ui(ui, |ui| { + for (align2, name) in &aligns { + ui.selectable_value(&mut self.align, *align2, *name); + } + }); + + ui.horizontal_wrapped(|ui| { + let (response, painter) = ui.allocate_painter(self.size, Sense::empty()); + let rect = response.rect; + + let start_pos = self.size / 2.0; + + let s = ui.ctx().fonts(|f| { + let mut t = egui::Shape::text( + f, + rect.min + start_pos, + egui::Align2::LEFT_TOP, + "sample_text", + egui::FontId::new(12.0, egui::FontFamily::Proportional), + default_color, + ); + + if let egui::epaint::Shape::Text(ts) = &mut t { + let new = ts.clone().with_angle_and_anchor(self.angle, self.align); + *ts = new; + }; + + t + }); + + if let egui::epaint::Shape::Text(ts) = &s { + let align_pt = + rect.min + start_pos + self.align.pos_in_rect(&ts.galley.rect).to_vec2(); + painter.circle(align_pt, 2.0, Color32::RED, (0.0, Color32::RED)); + }; + + painter.rect( + rect, + 0.0, + default_color.gamma_multiply(0.3), + (0.0, Color32::BLACK), + egui::StrokeKind::Middle, + ); + painter.add(s); + }); + } +} diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 267fa6be7..f11c5d8a7 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:880344367ed65f83898ceca4843b1b6259d1690242ced0d29ac8dc48100a8faa -size 62956 +oid sha256:116a53258be27d9c7c56538e5f83202ea731f19887fabadc0449d24fde4d80d9 +size 64494 diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index 4ea0ac352..bf9db964b 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use emath::{Align2, Rot2}; + use crate::*; /// How to paint some text on screen. @@ -78,7 +80,7 @@ impl TextShape { self } - /// Rotate text by this many radians clockwise. + /// Set text rotation to `angle` radians clockwise. /// The pivot is `pos` (the upper left corner of the text). #[inline] pub fn with_angle(mut self, angle: f32) -> Self { @@ -86,6 +88,17 @@ impl TextShape { self } + /// Set the text rotation to the `angle` radians clockwise. + /// The pivot is determined by the given `anchor` point on the text bounding box. + #[inline] + pub fn with_angle_and_anchor(mut self, angle: f32, anchor: Align2) -> Self { + self.angle = angle; + let a0 = anchor.pos_in_rect(&self.galley.rect).to_vec2(); + let a1 = Rot2::from_angle(angle) * a0; + self.pos += a0 - a1; + self + } + /// Render text with this opacity in gamma space #[inline] pub fn with_opacity_factor(mut self, opacity_factor: f32) -> Self { From 54fded362dfc82f0ea188a110fc950d9adbb2f13 Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Sun, 15 Jun 2025 19:53:00 -0400 Subject: [PATCH 089/388] Clamp text cursor positions in the same places where we used to (#7081) Closes #7077. This fixes the problem shown in #7077 where clearing a `TextEdit` wouldn't reset its cursor position. I've fixed that by adding back the `TextCursorState::range` method, which clamps the selection range to that of the passed `Galley`, and calling it in the same places where it was called before #5785. (/cc @juancampa) * [x] I have followed the instructions in the PR template --- .../src/text_selection/label_text_selection.rs | 8 ++++---- .../egui/src/text_selection/text_cursor_state.rs | 14 ++++++++++++-- crates/egui/src/widgets/text_edit/builder.rs | 8 ++++---- crates/epaint/src/text/text_layout_types.rs | 4 ++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index 9ce8fbd5b..a315c2354 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -530,7 +530,7 @@ impl LabelSelectionState { let mut cursor_state = self.cursor_for(ui, response, global_from_galley, galley); - let old_range = cursor_state.char_range(); + let old_range = cursor_state.range(galley); if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { if response.contains_pointer() { @@ -544,7 +544,7 @@ impl LabelSelectionState { } } - if let Some(mut cursor_range) = cursor_state.char_range() { + 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); @@ -562,7 +562,7 @@ impl LabelSelectionState { } // Look for changes due to keyboard and/or mouse interaction: - let new_range = cursor_state.char_range(); + let new_range = cursor_state.range(galley); let selection_changed = old_range != new_range; if let (true, Some(range)) = (selection_changed, new_range) { @@ -632,7 +632,7 @@ impl LabelSelectionState { } } - let cursor_range = cursor_state.char_range(); + let cursor_range = cursor_state.range(galley); let mut new_vertex_indices = vec![]; diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index 298d8abfb..d2158c6bd 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -35,6 +35,16 @@ impl TextCursorState { self.ccursor_range } + /// The currently selected range of characters, clamped within the character + /// range of the given [`Galley`]. + pub fn range(&self, galley: &Galley) -> Option { + self.ccursor_range.map(|mut range| { + range.primary = galley.clamp_cursor(&range.primary); + range.secondary = galley.clamp_cursor(&range.secondary); + range + }) + } + /// Sets the currently selected range of characters. pub fn set_char_range(&mut self, ccursor_range: Option) { self.ccursor_range = ccursor_range; @@ -69,7 +79,7 @@ impl TextCursorState { if response.hovered() && ui.input(|i| i.pointer.any_pressed()) { // The start of a drag (or a click). if ui.input(|i| i.modifiers.shift) { - if let Some(mut cursor_range) = self.char_range() { + if let Some(mut cursor_range) = self.range(galley) { cursor_range.primary = cursor_at_pointer; self.set_char_range(Some(cursor_range)); } else { @@ -81,7 +91,7 @@ impl TextCursorState { true } else if is_being_dragged { // Drag to select text: - if let Some(mut cursor_range) = self.char_range() { + if let Some(mut cursor_range) = self.range(galley) { cursor_range.primary = cursor_at_pointer; self.set_char_range(Some(cursor_range)); } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 29f4b2cbb..e21c512a9 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -617,7 +617,7 @@ impl TextEdit<'_> { } let mut cursor_range = None; - let prev_cursor_range = state.cursor.char_range(); + let prev_cursor_range = state.cursor.range(&galley); if interactive && ui.memory(|mem| mem.has_focus(id)) { ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter)); @@ -720,7 +720,7 @@ impl TextEdit<'_> { let has_focus = ui.memory(|mem| mem.has_focus(id)); if has_focus { - if let Some(cursor_range) = state.cursor.char_range() { + if let Some(cursor_range) = state.cursor.range(&galley) { // Add text selection rectangles to the galley: paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None); } @@ -742,7 +742,7 @@ impl TextEdit<'_> { painter.galley(galley_pos, galley.clone(), text_color); if has_focus { - if let Some(cursor_range) = state.cursor.char_range() { + if let Some(cursor_range) = state.cursor.range(&galley) { let primary_cursor_rect = cursor_rect(&galley, &cursor_range.primary, row_height) .translate(galley_pos.to_vec2()); @@ -898,7 +898,7 @@ fn events( ) -> (bool, CCursorRange) { let os = ui.ctx().os(); - let mut cursor_range = state.cursor.char_range().unwrap_or(default_cursor_range); + let mut cursor_range = state.cursor.range(galley).unwrap_or(default_cursor_range); // We feed state to the undoer both before and after handling input // so that the undoer creates automatic saves even when there are no events for a while. diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 6b7863426..f7e11911b 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -1104,6 +1104,10 @@ impl Galley { } } + pub fn clamp_cursor(&self, cursor: &CCursor) -> CCursor { + self.cursor_from_layout(self.layout_from_cursor(*cursor)) + } + pub fn cursor_up_one_row( &self, cursor: &CCursor, From 96c34139fdf59b522b7816609f85f4a3ffe8e5e6 Mon Sep 17 00:00:00 2001 From: Azkellas Date: Mon, 16 Jun 2025 02:11:26 +0200 Subject: [PATCH 090/388] Select all text in DragValue when gaining focus via keyboard (#7107) Previously, the `DragValue` widget selected all text when focus was gained via a mouse click, but didn't when focus was gained via keyboard. https://github.com/user-attachments/assets/5e82ca2c-0214-4201-ad2d-056dabc05e92 This PR makes both gained focus behaving the same way by selecting the text on focus gained via keyboard. https://github.com/user-attachments/assets/f246c779-3368-428c-a6b2-cec20dbc20a6 - [x] I have followed the instructions in the PR template Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widgets/drag_value.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 864222ae1..a9d971916 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -3,7 +3,7 @@ use std::{cmp::Ordering, ops::RangeInclusive}; use crate::{ - emath, text, Button, CursorIcon, Key, Modifiers, NumExt as _, Response, RichText, Sense, + emath, text, Button, CursorIcon, Id, Key, Modifiers, NumExt as _, Response, RichText, Sense, TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, MINUS_CHAR_STR, }; @@ -569,6 +569,11 @@ impl Widget for DragValue<'_> { .font(text_style), ); + // Select all text when the edit gains focus. + if ui.memory_mut(|mem| mem.gained_focus(id)) { + select_all_text(ui, id, response.id, &value_text); + } + let update = if update_while_editing { // Update when the edit content has changed. response.changed() @@ -623,12 +628,7 @@ impl Widget for DragValue<'_> { if response.clicked() { ui.data_mut(|data| data.remove::(id)); ui.memory_mut(|mem| mem.request_focus(id)); - let mut state = TextEdit::load_state(ui.ctx(), id).unwrap_or_default(); - state.cursor.set_char_range(Some(text::CCursorRange::two( - text::CCursor::default(), - text::CCursor::new(value_text.chars().count()), - ))); - state.store(ui.ctx(), response.id); + select_all_text(ui, id, response.id, &value_text); } else if response.dragged() { ui.ctx().set_cursor_icon(cursor_icon); @@ -759,6 +759,16 @@ pub(crate) fn clamp_value_to_range(x: f64, range: RangeInclusive) -> f64 { } } +/// Select all text in the `DragValue` text edit widget. +fn select_all_text(ui: &Ui, widget_id: Id, response_id: Id, value_text: &str) { + let mut state = TextEdit::load_state(ui.ctx(), widget_id).unwrap_or_default(); + state.cursor.set_char_range(Some(text::CCursorRange::two( + text::CCursor::default(), + text::CCursor::new(value_text.chars().count()), + ))); + state.store(ui.ctx(), response_id); +} + #[cfg(test)] mod tests { use super::clamp_value_to_range; From 699be07978b80f439d20c16232096cce110c8538 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 15 Jun 2025 18:01:48 -0700 Subject: [PATCH 091/388] Add Vec2::ONE --- crates/emath/src/vec2.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 9a173348b..4771343e8 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -140,6 +140,7 @@ impl Vec2 { pub const DOWN: Self = Self { x: 0.0, y: 1.0 }; pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; + pub const ONE: Self = Self { x: 1.0, y: 1.0 }; pub const INFINITY: Self = Self::splat(f32::INFINITY); pub const NAN: Self = Self::splat(f32::NAN); From 06760e1b0869459ef78840c2a380f594e7a9d1e3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 16 Jun 2025 08:30:46 +0200 Subject: [PATCH 092/388] Change API of `Tooltip` slightly (#7151) We try to be consistent with our parameter order to reduce surprise for users. I also renamed a few things to clarify what is what --- CONTRIBUTING.md | 3 +- crates/egui/src/containers/old_popup.rs | 6 ++-- crates/egui/src/containers/tooltip.rs | 39 ++++++++++++++----------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 613de20c5..7ddedc378 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ You can test your code locally by running `./scripts/check.sh`. 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 +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). 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. @@ -125,6 +125,7 @@ While using an immediate mode gui is simple, implementing one is a lot more tric * Flip `if !condition {} else {}` * Sets of things should be lexicographically sorted (e.g. crate dependencies in `Cargo.toml`) * Put each type in their own file, unless they are trivial (e.g. a `struct` with no `impl`) +* Put most generic arguments first (e.g. `Context`), and most specific last * Break the above rules when it makes sense diff --git a/crates/egui/src/containers/old_popup.rs b/crates/egui/src/containers/old_popup.rs index 3e5de650f..cc75494e1 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(widget_id, ctx.clone(), PopupAnchor::Pointer, parent_layer) + Tooltip::new(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(widget_id, ctx.clone(), *widget_rect, parent_layer) + Tooltip::new(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(widget_id, ctx.clone(), suggested_position, parent_layer) + Tooltip::new(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 c85739d75..a6cb3199e 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -7,26 +7,31 @@ use emath::Vec2; pub struct Tooltip<'a> { pub popup: Popup<'a>, - layer_id: LayerId, - widget_id: Id, + + /// The layer of the parent widget. + parent_layer: LayerId, + + /// The id of the widget that owns this tooltip. + parent_widget: Id, } impl Tooltip<'_> { - /// Show a tooltip that is always open + /// Show a tooltip that is always open. pub fn new( - widget_id: Id, ctx: Context, + parent_layer: LayerId, + parent_widget: Id, anchor: impl Into, - layer_id: LayerId, ) -> Self { + let width = ctx.style().spacing.tooltip_width; Self { - // TODO(lucasmerlin): Set width somehow (we're missing context here) - popup: Popup::new(widget_id, ctx, anchor.into(), layer_id) + popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer) .kind(PopupKind::Tooltip) .gap(4.0) + .width(width) .sense(Sense::hover()), - layer_id, - widget_id, + parent_layer, + parent_widget, } } @@ -39,8 +44,8 @@ impl Tooltip<'_> { .sense(Sense::hover()); Self { popup, - layer_id: response.layer_id, - widget_id: response.id, + parent_layer: response.layer_id, + parent_widget: response.id, } } @@ -96,8 +101,8 @@ impl Tooltip<'_> { pub fn show(self, content: impl FnOnce(&mut crate::Ui) -> R) -> Option> { let Self { mut popup, - layer_id: parent_layer, - widget_id, + parent_layer, + parent_widget, } = self; if !popup.is_open() { @@ -111,11 +116,11 @@ impl Tooltip<'_> { fs.layers .entry(parent_layer) .or_default() - .widget_with_tooltip = Some(widget_id); + .widget_with_tooltip = Some(parent_widget); fs.tooltips .widget_tooltips - .get(&widget_id) + .get(&parent_widget) .copied() .unwrap_or(PerWidgetTooltipState { bounding_rect: rect, @@ -123,7 +128,7 @@ impl Tooltip<'_> { }) }); - let tooltip_area_id = Self::tooltip_id(widget_id, state.tooltip_count); + let tooltip_area_id = Self::tooltip_id(parent_widget, state.tooltip_count); popup = popup.anchor(state.bounding_rect).id(tooltip_area_id); let response = popup.show(|ui| { @@ -144,7 +149,7 @@ impl Tooltip<'_> { response .response .ctx - .pass_state_mut(|fs| fs.tooltips.widget_tooltips.insert(widget_id, state)); + .pass_state_mut(|fs| fs.tooltips.widget_tooltips.insert(parent_widget, state)); Self::remember_that_tooltip_was_shown(&response.response.ctx); } From 5194c0df3ef162ec6e2df9bb29bbe944d6d68aa9 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 16 Jun 2025 08:42:17 +0200 Subject: [PATCH 093/388] Minor atoms improvements (#7145) Improve some lifetime bounds and add some convenience constructors --- crates/egui/src/atomics/atoms.rs | 48 ++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/atomics/atoms.rs b/crates/egui/src/atomics/atoms.rs index 635b2a132..4b19c9e26 100644 --- a/crates/egui/src/atomics/atoms.rs +++ b/crates/egui/src/atomics/atoms.rs @@ -54,15 +54,15 @@ impl<'a> Atoms<'a> { string } - pub fn iter_kinds(&'a self) -> impl Iterator> { + pub fn iter_kinds(&self) -> impl Iterator> { self.0.iter().map(|atom| &atom.kind) } - pub fn iter_kinds_mut(&'a mut self) -> impl Iterator> { + pub fn iter_kinds_mut(&mut self) -> impl Iterator> { self.0.iter_mut().map(|atom| &mut atom.kind) } - pub fn iter_images(&'a self) -> impl Iterator> { + pub fn iter_images(&self) -> impl Iterator> { self.iter_kinds().filter_map(|kind| { if let AtomKind::Image(image) = kind { Some(image) @@ -72,7 +72,7 @@ impl<'a> Atoms<'a> { }) } - pub fn iter_images_mut(&'a mut self) -> impl Iterator> { + pub fn iter_images_mut(&mut self) -> impl Iterator> { self.iter_kinds_mut().filter_map(|kind| { if let AtomKind::Image(image) = kind { Some(image) @@ -82,7 +82,7 @@ impl<'a> Atoms<'a> { }) } - pub fn iter_texts(&'a self) -> impl Iterator { + pub fn iter_texts(&self) -> impl Iterator + use<'_, 'a> { self.iter_kinds().filter_map(|kind| { if let AtomKind::Text(text) = kind { Some(text) @@ -92,7 +92,7 @@ impl<'a> Atoms<'a> { }) } - pub fn iter_texts_mut(&'a mut self) -> impl Iterator { + pub fn iter_texts_mut(&mut self) -> impl Iterator + use<'a, '_> { self.iter_kinds_mut().filter_map(|kind| { if let AtomKind::Text(text) = kind { Some(text) @@ -107,7 +107,7 @@ impl<'a> Atoms<'a> { .for_each(|atom| *atom = f(std::mem::take(atom))); } - pub fn map_kind(&'a mut self, mut f: F) + pub fn map_kind(&mut self, mut f: F) where F: FnMut(AtomKind<'a>) -> AtomKind<'a>, { @@ -116,7 +116,7 @@ impl<'a> Atoms<'a> { } } - pub fn map_images(&'a mut self, mut f: F) + pub fn map_images(&mut self, mut f: F) where F: FnMut(Image<'a>) -> Image<'a>, { @@ -129,7 +129,7 @@ impl<'a> Atoms<'a> { }); } - pub fn map_texts(&'a mut self, mut f: F) + pub fn map_texts(&mut self, mut f: F) where F: FnMut(WidgetText) -> WidgetText, { @@ -227,3 +227,33 @@ impl DerefMut for Atoms<'_> { &mut self.0 } } + +impl<'a, T: Into>> From> for Atoms<'a> { + fn from(vec: Vec) -> Self { + Atoms(vec.into_iter().map(Into::into).collect()) + } +} + +impl<'a, T: Into> + Clone> From<&[T]> for Atoms<'a> { + fn from(slice: &[T]) -> Self { + Atoms(slice.iter().cloned().map(Into::into).collect()) + } +} + +impl<'a, Item: Into>> FromIterator for Atoms<'a> { + fn from_iter>(iter: T) -> Self { + Atoms(iter.into_iter().map(Into::into).collect()) + } +} + +#[cfg(test)] +mod tests { + use crate::Atoms; + + #[test] + fn collect_atoms() { + let _: Atoms<'_> = ["Hello", "World"].into_iter().collect(); + let _ = Atoms::from(vec!["Hi"]); + let _ = Atoms::from(["Hi"].as_slice()); + } +} 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 094/388] 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 095/388] 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 096/388] 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 097/388] 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 098/388] 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 099/388] 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 100/388] 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 101/388] 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 102/388] 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 103/388] 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 104/388] 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 105/388] 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 106/388] 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 107/388] 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 108/388] 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 109/388] 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 110/388] 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 111/388] 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 112/388] 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 113/388] 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 114/388] `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 115/388] 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 116/388] 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 117/388] 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 118/388] 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 119/388] 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 120/388] 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 121/388] 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 122/388] 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 123/388] `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 124/388] 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 125/388] 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 126/388] 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 127/388] 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 128/388] 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 129/388] 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 130/388] 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 131/388] 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 132/388] 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 133/388] 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 134/388] 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 135/388] 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 136/388] 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 137/388] 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 138/388] 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 139/388] 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 140/388] 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 141/388] 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 142/388] 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 143/388] 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 144/388] 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 145/388] 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 146/388] 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 147/388] 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 148/388] 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 149/388] 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` From 7fb13d85ec728fd9cee9a7ce98e561efe018cadf Mon Sep 17 00:00:00 2001 From: Tamo Date: Tue, 5 Aug 2025 11:31:05 +0200 Subject: [PATCH 150/388] Request a redraw when the url change through the `popstate` event listener (#7403) Hey, I added an event listener on the [`popstate` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event). That fixed my issue https://github.com/user-attachments/assets/a621dac9-b7c3-426a-968b-dc73c5702eea * Closes https://github.com/emilk/egui/issues/7402 * [x] I have followed the instructions in the PR template --- crates/eframe/src/web/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 168d6123a..0d01fcc6c 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -405,7 +405,7 @@ fn install_window_events(runner_ref: &WebRunner, window: &EventTarget) -> Result // No need to subscribe to "resize": we already subscribe to the canvas // size using a ResizeObserver, and we also subscribe to DPR changes of the monitor. - for event_name in &["load", "pagehide", "pageshow"] { + for event_name in &["load", "pagehide", "pageshow", "popstate"] { runner_ref.add_event_listener(window, event_name, move |_: web_sys::Event, runner| { if DEBUG_RESIZE { log::debug!("{event_name:?}"); From c38d38e89d8eaa5a9b98d9648a7b60e86d498bd6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 5 Aug 2025 13:04:30 +0300 Subject: [PATCH 151/388] Align `Color32` to 4 bytes (#7318) It could be useful when you want to preprocess an image using SIMD instructions without extra copying to convert it to a texture. Possibly we might add it as a feature. * Closes * [x] I have followed the instructions in the PR template Co-authored-by: Emil Ernerfeldt --- crates/ecolor/src/color32.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index eaa1ce2c3..e73720703 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -24,6 +24,7 @@ use crate::{Rgba, fast_round, linear_f32_from_linear_u8}; /// /// An `alpha=0` means the color is to be treated as an additive color. #[repr(C)] +#[repr(align(4))] #[derive(Clone, Copy, Default, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] From b9a5081490f4349598ecb833784d391dd2d43488 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 5 Aug 2025 13:03:39 +0200 Subject: [PATCH 152/388] Fix glyph rendering: clamp coverage to [0, 1] (#7415) * Closes #7366 * Closes https://github.com/emilk/egui/pull/7388 --- crates/epaint/src/image.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index e14ea869e..059d9d769 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -384,6 +384,7 @@ impl AlphaFromCoverage { /// Convert coverage to alpha. #[inline(always)] pub fn alpha_from_coverage(&self, coverage: f32) -> f32 { + let coverage = coverage.clamp(0.0, 1.0); match self { Self::Linear => coverage, Self::Gamma(gamma) => coverage.powf(*gamma), From 31eb4d498be8ca37c37add28e11f6e4ec259ff04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20G=C5=82uchowski?= Date: Tue, 5 Aug 2025 13:11:45 +0200 Subject: [PATCH 153/388] Fix multi-line `TextShape` rotation (#7404) * Closes * [X] I have followed the instructions in the PR template I do admit I got a peak NixOS `RequestDeviceError` and deemed it entirely not worth it to think about that. https://github.com/emilk/egui/pull/5411 broke rotation of multi-line `TextShape`s because `PlacedRow::pos` was no longer being rotated, so let's rotate it. --------- Co-authored-by: Emil Ernerfeldt Co-authored-by: Lucas Meurer --- crates/epaint/src/tessellator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 4b1b23ba0..4670d0b2e 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -2031,7 +2031,7 @@ impl Tessellator { continue; } - let final_row_pos = galley_pos + row.pos.to_vec2(); + let final_row_pos = galley_pos + rotator * row.pos.to_vec2(); let mut row_rect = row.visuals.mesh_bounds; if *angle != 0.0 { From 8333615072318b02cd846259a794cf28558b9e86 Mon Sep 17 00:00:00 2001 From: Vanadiae Date: Tue, 5 Aug 2025 14:41:32 +0200 Subject: [PATCH 154/388] Fix memory leak when `forget_image` is called while loading (#7380) This is the same issue that was fixed for the http bytes loader in 239ade9a59692f23a6b1d779a9c2631cf774896a * [x] I have followed the instructions in the PR template ---------------- Funnily, [this comment](https://github.com/emilk/egui/issues/3747#issuecomment-1872192997) describes exactly how I encountered this issue: > That assert is wrong if something calls forget between the start of the request and the end of it. I'm displaying lots of images in a scrolling grid (20 or so visible at a time). It seems like image textures are never freed up automatically so it stacks up a lot meaning I have to unload the image textures manually with `egui::Context::forget_image()` in each `eframe::App::update()` call for the images that are no longer shown when scrolling. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui_extras/src/loaders/ehttp_loader.rs | 16 +++++++++++++--- crates/egui_extras/src/loaders/image_loader.rs | 16 +++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/crates/egui_extras/src/loaders/ehttp_loader.rs b/crates/egui_extras/src/loaders/ehttp_loader.rs index abe5d96f1..7310cc6e1 100644 --- a/crates/egui_extras/src/loaders/ehttp_loader.rs +++ b/crates/egui_extras/src/loaders/ehttp_loader.rs @@ -94,9 +94,19 @@ impl BytesLoader for EhttpLoader { Err(format!("Failed to load {uri:?}")) } }; - log::trace!("finished loading {uri:?}"); - cache.lock().insert(uri, Poll::Ready(result)); - ctx.request_repaint(); + 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!( + "Canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading." + ); + } } }); diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 18e1e483b..82df87b38 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -100,14 +100,16 @@ impl ImageLoader for ImageCrateLoader { let result = crate::image::load_image_bytes(&bytes) .map(Arc::new) .map_err(|err| err.to_string()); - log::trace!("ImageLoader - finished loading {uri:?}"); - let prev = cache.lock().insert(uri, Poll::Ready(result)); - debug_assert!( - matches!(prev, Some(Poll::Pending)), - "Expected previous state to be Pending" - ); + let mut cache = cache.lock(); - ctx.request_repaint(); + 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!("ImageLoader - finished loading {uri:?}"); + } else { + log::trace!("ImageLoader - canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); + } } }) .expect("failed to spawn thread"); From a1e5501db8902aa08d8462abf5ed3e0ac95889c5 Mon Sep 17 00:00:00 2001 From: Michael Grupp Date: Tue, 5 Aug 2025 14:43:02 +0200 Subject: [PATCH 155/388] Improve `debug_assert` when calling `add_space()` in `Grid` layout (#7399) Makes it clearer to understand than the lower-level `advance_cursor` assert for an user who accidentally does this. --- crates/egui/src/placer.rs | 2 ++ crates/egui/src/ui.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/crates/egui/src/placer.rs b/crates/egui/src/placer.rs index a56bcdb29..b5f68f72d 100644 --- a/crates/egui/src/placer.rs +++ b/crates/egui/src/placer.rs @@ -145,6 +145,8 @@ impl Placer { /// Advance the cursor by this many points. /// [`Self::min_rect`] will expand to contain the cursor. + /// + /// Note that `advance_cursor` isn't supported when in a grid layout. pub(crate) fn advance_cursor(&mut self, amount: f32) { debug_assert!( self.grid.is_none(), diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 56392156a..5586734bd 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1877,6 +1877,7 @@ impl Ui { /// Add extra space before the next widget. /// /// The direction is dependent on the layout. + /// Note that `add_space` isn't supported when in a grid layout. /// /// This will be in addition to the [`crate::style::Spacing::item_spacing`] /// that is always added, but `item_spacing` won't be added _again_ by `add_space`. @@ -1884,6 +1885,7 @@ impl Ui { /// [`Self::min_rect`] will expand to contain the space. #[inline] pub fn add_space(&mut self, amount: f32) { + debug_assert!(!self.is_grid(), "add_space makes no sense in a grid layout"); self.placer.advance_cursor(amount.round_ui()); } From 3aee52575523d86760403fdbe93b201588cbceca Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 5 Aug 2025 14:55:24 +0200 Subject: [PATCH 156/388] Add `ComboBox::popup_style` (#7360) * Closes #7349 * [x] I have followed the instructions in the PR template --- crates/egui/src/containers/combo_box.rs | 19 ++++++++++++++++++- crates/egui/src/containers/popup.rs | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 6ef928849..8195024fb 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -44,6 +44,7 @@ pub struct ComboBox { icon: Option, wrap_mode: Option, close_behavior: Option, + popup_style: StyleModifier, } impl ComboBox { @@ -58,6 +59,7 @@ impl ComboBox { icon: None, wrap_mode: None, close_behavior: None, + popup_style: StyleModifier::default(), } } @@ -73,6 +75,7 @@ impl ComboBox { icon: None, wrap_mode: None, close_behavior: None, + popup_style: StyleModifier::default(), } } @@ -87,6 +90,7 @@ impl ComboBox { icon: None, wrap_mode: None, close_behavior: None, + popup_style: StyleModifier::default(), } } @@ -191,6 +195,16 @@ impl ComboBox { self } + /// Set the style of the popup menu. + /// + /// Could for example be used with [`crate::containers::menu::menu_style`] to get the frame-less + /// menu button style. + #[inline] + pub fn popup_style(mut self, popup_style: StyleModifier) -> Self { + self.popup_style = popup_style; + self + } + /// Show the combo box, with the given ui code for the menu contents. /// /// Returns `InnerResponse { inner: None }` if the combo box is closed. @@ -216,6 +230,7 @@ impl ComboBox { icon, wrap_mode, close_behavior, + popup_style, } = self; let button_id = ui.make_persistent_id(id_salt); @@ -229,6 +244,7 @@ impl ComboBox { icon, wrap_mode, close_behavior, + popup_style, (width, height), ); if let Some(label) = label { @@ -311,6 +327,7 @@ fn combo_box_dyn<'c, R>( icon: Option, wrap_mode: Option, close_behavior: Option, + popup_style: StyleModifier, (width, height): (Option, Option), ) -> InnerResponse> { let popup_id = ComboBox::widget_to_popup_id(button_id); @@ -379,9 +396,9 @@ fn combo_box_dyn<'c, R>( let inner = Popup::menu(&button_response) .id(popup_id) - .style(StyleModifier::default()) .width(button_response.rect.width()) .close_behavior(close_behavior) + .style(popup_style) .show(|ui| { ui.set_min_width(ui.available_width()); diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 9c2804138..520fb408e 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -500,6 +500,7 @@ impl<'a> Popup<'a> { } /// Show the popup. + /// /// 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> { From e9afd3c52dd575ea5528f7ce1e2c923ac379c01c Mon Sep 17 00:00:00 2001 From: Icekey Date: Tue, 5 Aug 2025 19:26:52 +0200 Subject: [PATCH 157/388] Add `UiBuilder::global_scope` and `UiBuilder::id` (#7372) I added a new flag to the UiBuilder so that it is possible to move child widgets around the ui tree without losing state information. Currently there is no way to create child widgets with the same id at different locations in the ui tree since ids change in relation the the parent id. With the new flag a unique global scope can be created which always results in the same ids even at different locations. You still need to ensure that the widgets only get rendered once in frame. This feature can be used to fix a issue i am having with the https://github.com/lucasmerlin/hello_egui crate. * Closes https://github.com/lucasmerlin/hello_egui/issues/75 * [X] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/ui.rs | 12 ++++++++++-- crates/egui/src/ui_builder.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 5586734bd..1371f1a7c 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -124,6 +124,7 @@ impl Ui { pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self { let UiBuilder { id_salt, + global_scope: _, ui_stack_info, layer_id, max_rect, @@ -250,6 +251,7 @@ impl Ui { pub fn new_child(&mut self, ui_builder: UiBuilder) -> Self { let UiBuilder { id_salt, + global_scope, ui_stack_info, layer_id, max_rect, @@ -287,8 +289,14 @@ impl Ui { } debug_assert!(!max_rect.any_nan(), "max_rect is NaN: {max_rect:?}"); - let stable_id = self.id.with(id_salt); - let unique_id = stable_id.with(self.next_auto_id_salt); + let (stable_id, unique_id) = if global_scope { + (id_salt, id_salt) + } else { + let stable_id = self.id.with(id_salt); + let unique_id = stable_id.with(self.next_auto_id_salt); + + (stable_id, unique_id) + }; let next_auto_id_salt = unique_id.value().wrapping_add(1); self.next_auto_id_salt = self.next_auto_id_salt.wrapping_add(1); diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index 549170182..e83148da6 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -14,6 +14,7 @@ use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo}; #[derive(Clone, Default)] pub struct UiBuilder { pub id_salt: Option, + pub global_scope: bool, pub ui_stack_info: UiStackInfo, pub layer_id: Option, pub max_rect: Option, @@ -42,6 +43,34 @@ impl UiBuilder { self } + /// Set an id of the new `Ui` that is independent of the parent `Ui`. + /// This way child widgets can be moved in the ui tree without losing state. + /// You have to ensure that in a frame the child widgets do not get rendered in multiple places. + /// + /// You should set the same unique `id` at every place in the ui tree where you want the + /// child widgets to share state. + /// If the child widgets are not moved in the ui tree, use [`UiBuilder::id_salt`] instead. + /// + /// This is a shortcut for `.id_salt(my_id).global_scope(true)`. + #[inline] + pub fn id(mut self, id: impl Hash) -> Self { + self.id_salt = Some(Id::new(id)); + self.global_scope = true; + self + } + + /// Make the new `Ui` child ids independent of the parent `Ui`. + /// This way child widgets can be moved in the ui tree without losing state. + /// You have to ensure that in a frame the child widgets do not get rendered in multiple places. + /// + /// You should set the same globally unique `id_salt` at every place in the ui tree where you want the + /// child widgets to share state. + #[inline] + pub fn global_scope(mut self, global_scope: bool) -> Self { + self.global_scope = global_scope; + self + } + /// Provide some information about the new `Ui` being built. #[inline] pub fn ui_stack_info(mut self, ui_stack_info: UiStackInfo) -> Self { From ef039aa56639411d20afda28cb279e062f9e9a35 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 5 Aug 2025 19:47:26 +0200 Subject: [PATCH 158/388] Enable more clippy lints (#7418) More is more! --- Cargo.toml | 14 +++++++++++++- crates/egui/src/hit_test.rs | 2 ++ crates/egui/src/viewport.rs | 2 ++ crates/egui/src/widget_text.rs | 10 ++++++---- crates/egui/src/widgets/image.rs | 6 ++---- crates/egui/src/widgets/text_edit/builder.rs | 6 ++---- crates/egui_kittest/src/snapshot.rs | 2 ++ crates/emath/src/rect.rs | 1 + crates/epaint/src/shapes/bezier_shape.rs | 2 ++ crates/epaint/src/text/fonts.rs | 1 + crates/epaint/src/text/text_layout.rs | 2 ++ examples/custom_keypad/src/keypad.rs | 5 ++--- examples/external_eventloop_async/src/main.rs | 1 + 13 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 498aefc08..9914b0503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,16 +136,20 @@ broken_intra_doc_links = "warn" # See also clippy.toml [workspace.lints.clippy] +all = { level = "warn", priority = -1 } + allow_attributes = "warn" as_ptr_cast_mut = "warn" await_holding_lock = "warn" bool_to_int_with_if = "warn" +branches_sharing_code = "warn" char_lit_as_u8 = "warn" checked_conversions = "warn" clear_with_drain = "warn" cloned_instead_of_copied = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" +default_union_representation = "warn" derive_partial_eq_without_eq = "warn" disallowed_macros = "warn" # See clippy.toml disallowed_methods = "warn" # See clippy.toml @@ -155,6 +159,7 @@ disallowed_types = "warn" # See clippy.toml doc_link_with_quotes = "warn" doc_markdown = "warn" empty_enum = "warn" +empty_line_after_outer_attr = "warn" empty_enum_variants_with_brackets = "warn" enum_glob_use = "warn" equatable_if_let = "warn" @@ -171,6 +176,7 @@ 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" @@ -211,6 +217,7 @@ match_same_arms = "warn" match_wild_err_arm = "warn" match_wildcard_for_single_variants = "warn" mem_forget = "warn" +mismatched_target_os = "warn" mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_enforced_import_renames = "warn" @@ -230,8 +237,9 @@ nonstandard_macro_braces = "warn" option_as_ref_cloned = "warn" option_option = "warn" path_buf_push_overwrite = "warn" -print_stderr = "warn" pathbuf_init_then_push = "warn" +print_stderr = "warn" +print_stdout = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" pub_underscore_fields = "warn" @@ -261,6 +269,7 @@ todo = "warn" too_long_first_doc_paragraph = "warn" trailing_empty_array = "warn" trait_duplication_in_bounds = "warn" +transmute_ptr_to_ptr = "warn" tuple_array_conversions = "warn" unchecked_duration_subtraction = "warn" undocumented_unsafe_blocks = "warn" @@ -268,8 +277,10 @@ unimplemented = "warn" uninhabited_references = "warn" uninlined_format_args = "warn" unnecessary_box_returns = "warn" +unnecessary_safety_comment = "warn" unnecessary_literal_bound = "warn" unnecessary_safety_doc = "warn" +unnecessary_self_imports = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" @@ -278,6 +289,7 @@ unused_rounding = "warn" unused_self = "warn" unused_trait_names = "warn" use_self = "warn" +useless_let_if_seq = "warn" useless_transmute = "warn" verbose_file_reads = "warn" wildcard_dependencies = "warn" diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index 1361c1c49..d1122350e 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -468,6 +468,8 @@ fn should_prioritize_hits_on_back(back: Rect, front: Rect) -> bool { #[cfg(test)] mod tests { + #![expect(clippy::print_stdout)] + use emath::{Rect, pos2, vec2}; use crate::{Id, Sense}; diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index bf09fc845..8818a1715 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -648,6 +648,8 @@ impl ViewportBuilder { /// returning a list of commands and a bool indicating if the window needs to be recreated. #[must_use] pub fn patch(&mut self, new_vp_builder: Self) -> (Vec, bool) { + #![expect(clippy::useless_let_if_seq)] // False positive + let Self { title: new_title, app_id: new_app_id, diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 75bf36d83..bf7cbde34 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -422,10 +422,12 @@ impl RichText { font_id }; - let mut background_color = background_color; - if code { - background_color = style.visuals.code_bg_color; - } + let background_color = if code { + style.visuals.code_bg_color + } else { + background_color + }; + let underline = if underline { crate::Stroke::new(1.0, line_color) } else { diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 08b377843..167920adc 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -938,11 +938,9 @@ fn animated_image_frame_index(ctx: &Context, uri: &str) -> usize { return index; } } - - 0 - } else { - 0 } + + 0 } /// Checks if uri is a gif file diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index bcd65c7bf..4df736e34 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -929,12 +929,10 @@ fn events( event if cursor_range.on_event(os, event, galley, id) => None, Event::Copy => { - if cursor_range.is_empty() { - None - } else { + if !cursor_range.is_empty() { copy_if_not_password(ui, cursor_range.slice_str(text.as_str()).to_owned()); - None } + None } Event::Cut => { if cursor_range.is_empty() { diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 8a457ec52..c27a14887 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -321,6 +321,8 @@ fn try_image_snapshot_options_impl( name: String, options: &SnapshotOptions, ) -> SnapshotResult { + #![expect(clippy::print_stdout)] + let SnapshotOptions { threshold, output_path, diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 089e81671..b9e45c52d 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -830,6 +830,7 @@ mod tests { ); } + #[expect(clippy::print_stdout)] #[test] fn test_ray_intersection() { let rect = Rect::from_min_max(pos2(1.0, 1.0), pos2(3.0, 3.0)); diff --git a/crates/epaint/src/shapes/bezier_shape.rs b/crates/epaint/src/shapes/bezier_shape.rs index 5823c3796..20f7a4e18 100644 --- a/crates/epaint/src/shapes/bezier_shape.rs +++ b/crates/epaint/src/shapes/bezier_shape.rs @@ -613,6 +613,8 @@ struct FlatteningParameters { impl FlatteningParameters { // https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html pub fn from_curve(curve: &QuadraticBezierShape, tolerance: f32) -> Self { + #![expect(clippy::useless_let_if_seq)] + // Map the quadratic bézier segment to y = x^2 parabola. let from = curve.points[0]; let ctrl = curve.points[1]; diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index e59f9bc26..f0d1c9c3a 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -1165,6 +1165,7 @@ mod tests { ] } + #[expect(clippy::print_stdout)] #[test] fn test_split_paragraphs() { for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] { diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 8d4a90fb7..fd71506ef 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -554,6 +554,8 @@ fn halign_and_justify_row( wrap_width: f32, justify: bool, ) { + #![expect(clippy::useless_let_if_seq)] // False positive + let row = Arc::make_mut(&mut placed_row.row); if row.glyphs.is_empty() { diff --git a/examples/custom_keypad/src/keypad.rs b/examples/custom_keypad/src/keypad.rs index ddc675461..df8d1f484 100644 --- a/examples/custom_keypad/src/keypad.rs +++ b/examples/custom_keypad/src/keypad.rs @@ -179,8 +179,8 @@ impl Keypad { ) }); - let mut is_first_show = false; - if ctx.wants_keyboard_input() && state.focus != focus { + let is_first_show = ctx.wants_keyboard_input() && state.focus != focus; + if is_first_show { let y = ctx.style().spacing.interact_size.y * 1.25; state.open = true; state.start_pos = ctx.input(|i| { @@ -189,7 +189,6 @@ impl Keypad { .map_or(pos2(100.0, 100.0), |p| p + vec2(0.0, y)) }); state.focus = focus; - is_first_show = true; } if state.close_on_next_frame { diff --git a/examples/external_eventloop_async/src/main.rs b/examples/external_eventloop_async/src/main.rs index bbb52084f..f77dea941 100644 --- a/examples/external_eventloop_async/src/main.rs +++ b/examples/external_eventloop_async/src/main.rs @@ -11,5 +11,6 @@ fn main() -> std::io::Result<()> { // Do not check `app` on unsupported platforms when check "--all-features" is used in CI. #[cfg(not(target_os = "linux"))] fn main() { + #![expect(clippy::print_stdout)] println!("This example only supports Linux."); } From 2c6611d0c6a862d996806722e72f56932f575dd8 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 5 Aug 2025 21:21:00 +0200 Subject: [PATCH 159/388] Enable wgpu default features in eframe / egui_wgpu default features (#7344) --- .github/workflows/rust.yml | 2 +- crates/eframe/Cargo.toml | 16 +++++----------- crates/egui-wgpu/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 493cc2fcf..fece7f88d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -127,7 +127,7 @@ jobs: components: rust-src - name: Check wasm32+atomics eframe with wgpu - run: RUSTFLAGS='-C target-feature=+atomics' cargo +${{env.NIGHTLY_VERSION}} check -p eframe --lib --no-default-features --features wgpu --target wasm32-unknown-unknown -Z build-std=std,panic_abort + run: RUSTFLAGS='-C target-feature=+atomics' cargo +${{env.NIGHTLY_VERSION}} check -p eframe --lib --no-default-features --features wgpu,wgpu/webgpu --target wasm32-unknown-unknown -Z build-std=std,panic_abort # --------------------------------------------------------------------------- diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 8f24bcd00..f1146058d 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -40,6 +40,9 @@ default = [ "winit/default", "x11", "egui-wgpu?/fragile-send-sync-non-atomic-wasm", + # Let's enable some backends so that users can use `eframe` out-of-the-box + # without having to explicitly opt-in to backends + "egui-wgpu?/default" ] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). @@ -167,12 +170,7 @@ glutin-winit = { workspace = true, optional = true, default-features = false, fe "wgl", ] } home = { workspace = true, optional = true } -wgpu = { workspace = true, optional = true, features = [ - # Let's enable some backends so that users can use `eframe` out-of-the-box - # without having to explicitly opt-in to backends - "metal", - "webgpu", -] } +wgpu = { workspace = true, optional = true } # mac: [target.'cfg(any(target_os = "macos"))'.dependencies] @@ -268,11 +266,7 @@ web-sys = { workspace = true, features = [ # optional web: egui-wgpu = { workspace = true, optional = true } # if wgpu is used, use it without (!) winit -wgpu = { workspace = true, optional = true, features = [ - # Let's enable some backends so that users can use `eframe` out-of-the-box - # without having to explicitly opt-in to backends - "webgpu", -] } +wgpu = { workspace = true, optional = true } # Native dev dependencies for testing [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index e637a878c..94189f904 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -31,7 +31,7 @@ all-features = true rustdoc-args = ["--generate-link-to-definition"] [features] -default = ["fragile-send-sync-non-atomic-wasm"] +default = ["fragile-send-sync-non-atomic-wasm", "wgpu/default"] ## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11` winit = ["dep:winit", "winit/rwh_06"] From c8e6f6bfe5fb4fa280644c19693554051e77fde3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 6 Aug 2025 11:45:02 +0200 Subject: [PATCH 160/388] Remove deprecated clippy lint --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9914b0503..83bed598c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -217,7 +217,6 @@ match_same_arms = "warn" match_wild_err_arm = "warn" match_wildcard_for_single_variants = "warn" mem_forget = "warn" -mismatched_target_os = "warn" mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_enforced_import_renames = "warn" @@ -277,8 +276,8 @@ unimplemented = "warn" uninhabited_references = "warn" uninlined_format_args = "warn" unnecessary_box_returns = "warn" -unnecessary_safety_comment = "warn" unnecessary_literal_bound = "warn" +unnecessary_safety_comment = "warn" unnecessary_safety_doc = "warn" unnecessary_self_imports = "warn" unnecessary_struct_initialization = "warn" From 72fcbb861054cd3073c8e22eaa61f6101e7c5a5c Mon Sep 17 00:00:00 2001 From: satnam72 <125819218+satnam72@users.noreply.github.com> Date: Wed, 6 Aug 2025 07:27:51 -0400 Subject: [PATCH 161/388] Use `Frame::NONE` in docstring (#7396) - Replaced deprecated `egui::Frame::none()` with `egui::Frame::NONE` * Closes * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/containers/frame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index d4b677693..e4de2ca48 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -46,7 +46,7 @@ use epaint::{Color32, CornerRadius, Margin, MarginF32, Rect, Shadow, Shape, Stro /// /// ``` /// # egui::__run_test_ui(|ui| { -/// egui::Frame::none() +/// egui::Frame::NONE /// .fill(egui::Color32::RED) /// .show(ui, |ui| { /// ui.label("Label with red background"); From d42ea3800f93c98381d0667508585b4745321c99 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 6 Aug 2025 13:53:59 +0200 Subject: [PATCH 162/388] Update to winit 0.30.12 (#7420) --- Cargo.lock | 94 +++++++++++++++++++++++++++++++++++------------------- Cargo.toml | 2 +- 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1ed18559..2efc4e613 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,7 +352,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.38", "slab", "tracing", "windows-sys 0.59.0", @@ -395,7 +395,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.38.38", "tracing", ] @@ -422,7 +422,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.38", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -683,7 +683,7 @@ dependencies = [ "bitflags 2.9.0", "log", "polling", - "rustix", + "rustix 0.38.38", "slab", "thiserror 1.0.66", ] @@ -695,7 +695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix", + "rustix 0.38.38", "wayland-backend", "wayland-client", ] @@ -1568,12 +1568,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2501,6 +2501,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litemap" version = "0.7.4" @@ -3223,7 +3229,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.38", "tracing", "windows-sys 0.59.0", ] @@ -3374,6 +3380,15 @@ dependencies = [ "serde", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.37" @@ -3616,10 +3631,23 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.23.18" @@ -3870,7 +3898,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix", + "rustix 0.38.38", "thiserror 1.0.66", "wayland-backend", "wayland-client", @@ -4033,7 +4061,7 @@ dependencies = [ "cfg-if", "fastrand", "once_cell", - "rustix", + "rustix 0.38.38", "windows-sys 0.59.0", ] @@ -4574,13 +4602,13 @@ checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 1.0.8", "scoped-tls", "smallvec", "wayland-sys", @@ -4588,12 +4616,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.7" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.0", - "rustix", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] @@ -4615,16 +4643,16 @@ version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" dependencies = [ - "rustix", + "rustix 0.38.38", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.5" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ "bitflags 2.9.0", "wayland-backend", @@ -4634,9 +4662,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ "bitflags 2.9.0", "wayland-backend", @@ -4660,20 +4688,20 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", - "quick-xml 0.36.2", + "quick-xml 0.37.5", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -4912,7 +4940,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -5311,9 +5339,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.7" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba50bc8ef4b6f1a75c9274fb95aa9a8f63fbc66c56f391bd85cf68d51e7b1a3" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash", "android-activity", @@ -5341,7 +5369,7 @@ dependencies = [ "pin-project", "raw-window-handle 0.6.2", "redox_syscall 0.4.1", - "rustix", + "rustix 0.38.38", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -5422,7 +5450,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix", + "rustix 0.38.38", "x11rb-protocol", ] diff --git a/Cargo.toml b/Cargo.toml index 83bed598c..287f3b4a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ web-sys = "0.3.73" web-time = "1.1.0" # Timekeeping for native and web wgpu = { version = "25.0.0", default-features = false } windows-sys = "0.59" -winit = { version = "0.30.7", default-features = false } +winit = { version = "0.30.12", default-features = false } [workspace.lints.rust] unsafe_code = "deny" From 36a4981f29644fdc51cde965544b0fc96b1d2b89 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 6 Aug 2025 13:55:53 +0200 Subject: [PATCH 163/388] Enable `clippy::iter_over_hash_type` lint (#7421) This helped discover a few things that _might_ have been buggy. --- Cargo.lock | 2 -- Cargo.toml | 2 +- crates/eframe/src/native/glow_integration.rs | 30 ++++++++++--------- crates/eframe/src/native/wgpu_integration.rs | 29 ++++++++++-------- crates/eframe/src/web/events.rs | 2 ++ crates/egui-winit/Cargo.toml | 1 - crates/egui-winit/src/lib.rs | 13 ++++---- crates/egui/src/context.rs | 3 ++ crates/egui/src/data/input.rs | 8 +++-- crates/egui/src/data/output.rs | 10 ++++--- crates/egui/src/layers.rs | 9 +++--- crates/egui/src/memory/mod.rs | 15 ++++++---- crates/egui/src/util/id_type_map.rs | 2 ++ crates/egui/src/viewport.rs | 17 +++++++++++ crates/egui/src/widget_rect.rs | 1 + .../egui_demo_lib/src/demo/extra_viewport.rs | 6 +++- crates/egui_glow/Cargo.toml | 1 - crates/egui_glow/src/painter.rs | 1 + crates/egui_glow/src/winit.rs | 7 ++--- crates/epaint/src/lib.rs | 12 ++++++++ crates/epaint/src/shapes/shape.rs | 2 +- tests/test_viewports/src/main.rs | 1 + 22 files changed, 114 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2efc4e613..e197450b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1290,7 +1290,6 @@ name = "egui-winit" version = "0.32.0" dependencies = [ "accesskit_winit", - "ahash", "arboard", "bytemuck", "document-features", @@ -1374,7 +1373,6 @@ dependencies = [ name = "egui_glow" version = "0.32.0" dependencies = [ - "ahash", "bytemuck", "document-features", "egui", diff --git a/Cargo.toml b/Cargo.toml index 287f3b4a1..b6b6b2628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,6 +190,7 @@ iter_filter_is_some = "warn" iter_not_returning_iterator = "warn" iter_on_empty_collections = "warn" iter_on_single_items = "warn" +iter_over_hash_type = "warn" iter_without_into_iter = "warn" large_digit_groups = "warn" large_include_file = "warn" @@ -296,7 +297,6 @@ zero_sized_map_values = "warn" # TODO(emilk): maybe enable more of these lints? -iter_over_hash_type = "allow" should_panic_without_expect = "allow" too_many_lines = "allow" unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index c83a6f14b..661308843 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -23,10 +23,10 @@ use winit::{ window::{Window, WindowId}, }; -use ahash::{HashMap, HashSet}; +use ahash::HashMap; use egui::{ - DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, ViewportClass, ViewportId, - ViewportIdMap, ViewportIdPair, ViewportInfo, ViewportOutput, + DeferredViewportUiCallback, ImmediateViewport, OrderedViewportIdMap, ViewportBuilder, + ViewportClass, ViewportId, ViewportIdPair, ViewportInfo, ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -94,9 +94,9 @@ struct GlutinWindowContext { current_gl_context: Option, not_current_gl_context: Option, - viewports: ViewportIdMap, + viewports: OrderedViewportIdMap, viewport_from_window: HashMap, - window_from_viewport: ViewportIdMap, + window_from_viewport: OrderedViewportIdMap, focused_viewport: Option, } @@ -107,7 +107,7 @@ struct Viewport { builder: ViewportBuilder, deferred_commands: Vec, info: ViewportInfo, - actions_requested: HashSet, + actions_requested: Vec, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -671,7 +671,7 @@ impl GlowWinitRunning<'_> { ); { - for action in viewport.actions_requested.drain() { + for action in viewport.actions_requested.drain(..) { match action { ActionRequested::Screenshot(user_data) => { let screenshot = painter.read_screen_rgba(screen_size_in_pixels); @@ -1031,7 +1031,7 @@ impl GlutinWindowContext { let not_current_gl_context = Some(gl_context); let mut viewport_from_window = HashMap::default(); - let mut window_from_viewport = ViewportIdMap::default(); + let mut window_from_viewport = OrderedViewportIdMap::default(); let mut info = ViewportInfo::default(); if let Some(window) = &window { viewport_from_window.insert(window.id(), ViewportId::ROOT); @@ -1039,7 +1039,7 @@ impl GlutinWindowContext { egui_winit::update_viewport_info(&mut info, egui_ctx, window, true); } - let mut viewports = ViewportIdMap::default(); + let mut viewports = OrderedViewportIdMap::default(); viewports.insert( ViewportId::ROOT, Viewport { @@ -1265,7 +1265,7 @@ impl GlutinWindowContext { pub(crate) fn remove_viewports_not_in( &mut self, - viewport_output: &ViewportIdMap, + viewport_output: &OrderedViewportIdMap, ) { // GC old viewports self.viewports @@ -1280,7 +1280,7 @@ impl GlutinWindowContext { &mut self, event_loop: &ActiveEventLoop, egui_ctx: &egui::Context, - viewport_output: &ViewportIdMap, + viewport_output: &OrderedViewportIdMap, ) { profiling::function_scope!(); @@ -1337,7 +1337,7 @@ impl GlutinWindowContext { } fn initialize_or_update_viewport( - viewports: &mut ViewportIdMap, + viewports: &mut OrderedViewportIdMap, ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, @@ -1345,6 +1345,8 @@ fn initialize_or_update_viewport( ) -> &mut Viewport { profiling::function_scope!(); + use std::collections::btree_map::Entry; + if builder.icon.is_none() { // Inherit icon from parent builder.icon = viewports @@ -1353,7 +1355,7 @@ fn initialize_or_update_viewport( } match viewports.entry(ids.this) { - std::collections::hash_map::Entry::Vacant(entry) => { + Entry::Vacant(entry) => { // New viewport: log::debug!("Creating new viewport {:?} ({:?})", ids.this, builder.title); entry.insert(Viewport { @@ -1370,7 +1372,7 @@ fn initialize_or_update_viewport( }) } - std::collections::hash_map::Entry::Occupied(mut entry) => { + Entry::Occupied(mut entry) => { // Patch an existing viewport: let viewport = entry.get_mut(); diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index f387779d7..0bf4fb5cc 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -15,10 +15,11 @@ use winit::{ window::{Window, WindowId}, }; -use ahash::{HashMap, HashSet, HashSetExt as _}; +use ahash::HashMap; use egui::{ - DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, ViewportClass, - ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, + DeferredViewportUiCallback, FullOutput, ImmediateViewport, OrderedViewportIdMap, + ViewportBuilder, ViewportClass, ViewportId, ViewportIdPair, ViewportIdSet, ViewportInfo, + ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -72,7 +73,7 @@ pub struct SharedState { focused_viewport: Option, } -pub type Viewports = ViewportIdMap; +pub type Viewports = egui::OrderedViewportIdMap; pub struct Viewport { ids: ViewportIdPair, @@ -80,7 +81,7 @@ pub struct Viewport { builder: ViewportBuilder, deferred_commands: Vec, info: ViewportInfo, - actions_requested: HashSet, + actions_requested: Vec, /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -679,7 +680,7 @@ impl WgpuWinitRunning<'_> { screenshot_commands, ); - for action in viewport.actions_requested.drain() { + for action in viewport.actions_requested.drain(..) { match action { ActionRequested::Screenshot { .. } => { // already handled above @@ -1034,10 +1035,10 @@ fn render_immediate_viewport( } pub(crate) fn remove_viewports_not_in( - viewports: &mut ViewportIdMap, + viewports: &mut Viewports, painter: &mut egui_wgpu::winit::Painter, viewport_from_window: &mut HashMap, - viewport_output: &ViewportIdMap, + viewport_output: &OrderedViewportIdMap, ) { let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); @@ -1050,8 +1051,8 @@ pub(crate) fn remove_viewports_not_in( /// Add new viewports, and update existing ones: fn handle_viewport_output( egui_ctx: &egui::Context, - viewport_output: &ViewportIdMap, - viewports: &mut ViewportIdMap, + viewport_output: &OrderedViewportIdMap, + viewports: &mut Viewports, painter: &mut egui_wgpu::winit::Painter, viewport_from_window: &mut HashMap, ) { @@ -1111,6 +1112,8 @@ fn initialize_or_update_viewport<'a>( viewport_ui_cb: Option>, painter: &mut egui_wgpu::winit::Painter, ) -> &'a mut Viewport { + use std::collections::btree_map::Entry; + profiling::function_scope!(); if builder.icon.is_none() { @@ -1121,7 +1124,7 @@ fn initialize_or_update_viewport<'a>( } match viewports.entry(ids.this) { - std::collections::hash_map::Entry::Vacant(entry) => { + Entry::Vacant(entry) => { // New viewport: log::debug!("Creating new viewport {:?} ({:?})", ids.this, builder.title); entry.insert(Viewport { @@ -1130,14 +1133,14 @@ fn initialize_or_update_viewport<'a>( builder, deferred_commands: vec![], info: Default::default(), - actions_requested: HashSet::new(), + actions_requested: Vec::new(), viewport_ui_cb, window: None, egui_winit: None, }) } - std::collections::hash_map::Entry::Occupied(mut entry) => { + Entry::Occupied(mut entry) => { // Patch an existing viewport: let viewport = entry.get_mut(); diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 0d01fcc6c..73c3705a4 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -286,6 +286,8 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { // See https://github.com/emilk/egui/issues/4724 let keys_down = runner.egui_ctx().input(|i| i.keys_down.clone()); + + #[expect(clippy::iter_over_hash_type)] for key in keys_down { let egui_event = egui::Event::Key { key, diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index f4503235f..f2932ab5b 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -57,7 +57,6 @@ x11 = ["winit/x11", "bytemuck"] [dependencies] egui = { workspace = true, default-features = false, features = ["log"] } -ahash.workspace = true log.workspace = true profiling.workspace = true raw-window-handle.workspace = true diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index b07d47b0e..65612b497 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -22,7 +22,6 @@ mod window_settings; pub use window_settings::WindowSettings; -use ahash::HashSet; use raw_window_handle::HasDisplayHandle; use winit::{ @@ -1334,7 +1333,7 @@ pub fn process_viewport_commands( info: &mut ViewportInfo, commands: impl IntoIterator, window: &Window, - actions_requested: &mut HashSet, + actions_requested: &mut Vec, ) { for command in commands { process_viewport_command(egui_ctx, window, command, info, actions_requested); @@ -1346,7 +1345,7 @@ fn process_viewport_command( window: &Window, command: ViewportCommand, info: &mut ViewportInfo, - actions_requested: &mut HashSet, + actions_requested: &mut Vec, ) { profiling::function_scope!(); @@ -1539,16 +1538,16 @@ fn process_viewport_command( } } ViewportCommand::Screenshot(user_data) => { - actions_requested.insert(ActionRequested::Screenshot(user_data)); + actions_requested.push(ActionRequested::Screenshot(user_data)); } ViewportCommand::RequestCut => { - actions_requested.insert(ActionRequested::Cut); + actions_requested.push(ActionRequested::Cut); } ViewportCommand::RequestCopy => { - actions_requested.insert(ActionRequested::Copy); + actions_requested.push(ActionRequested::Copy); } ViewportCommand::RequestPaste => { - actions_requested.insert(ActionRequested::Paste); + actions_requested.push(ActionRequested::Paste); } } } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 9594f03e9..859835da3 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2149,6 +2149,7 @@ impl Context { self.write(|ctx| { if ctx.memory.options.zoom_factor != zoom_factor { ctx.new_zoom_factor = Some(zoom_factor); + #[expect(clippy::iter_over_hash_type)] for viewport_id in ctx.all_viewport_ids() { ctx.request_repaint(viewport_id, cause.clone()); } @@ -2275,6 +2276,8 @@ impl Context { /// Called at the end of the pass. #[cfg(debug_assertions)] fn debug_painting(&self) { + #![expect(clippy::iter_over_hash_type)] // ok to be sloppy in debug painting + let paint_widget = |widget: &WidgetRect, text: &str, color: Color32| { let rect = widget.interact_rect; if rect.is_positive() { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 05b93616d..6e6a3deb8 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -3,7 +3,7 @@ use epaint::ColorImage; use crate::{ - Key, Theme, ViewportId, ViewportIdMap, + Key, OrderedViewportIdMap, Theme, ViewportId, ViewportIdMap, emath::{Pos2, Rect, Vec2}, }; @@ -1132,7 +1132,11 @@ impl RawInput { } = self; ui.label(format!("Active viewport: {viewport_id:?}")); - for (id, viewport) in viewports { + let ordered_viewports = viewports + .iter() + .map(|(id, value)| (*id, value)) + .collect::>(); + for (id, viewport) in ordered_viewports { ui.group(|ui| { ui.label(format!("Viewport {id:?}")); ui.push_id(id, |ui| { diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 233f75a42..76c3de5f6 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -1,6 +1,6 @@ //! All the data egui returns to the backend at the end of each frame. -use crate::{RepaintCause, ViewportIdMap, ViewportOutput, WidgetType}; +use crate::{OrderedViewportIdMap, RepaintCause, ViewportOutput, WidgetType}; /// What egui emits each frame from [`crate::Context::run`]. /// @@ -32,12 +32,14 @@ pub struct FullOutput { /// /// It is up to the integration to spawn a native window for each viewport, /// and to close any window that no longer has a viewport in this map. - pub viewport_output: ViewportIdMap, + pub viewport_output: OrderedViewportIdMap, } impl FullOutput { /// Add on new output. pub fn append(&mut self, newer: Self) { + use std::collections::btree_map::Entry; + let Self { platform_output, textures_delta, @@ -53,10 +55,10 @@ impl FullOutput { for (id, new_viewport) in viewport_output { match self.viewport_output.entry(id) { - std::collections::hash_map::Entry::Vacant(entry) => { + Entry::Vacant(entry) => { entry.insert(new_viewport); } - std::collections::hash_map::Entry::Occupied(mut entry) => { + Entry::Occupied(mut entry) => { entry.get_mut().append(new_viewport); } } diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 927ffc36b..9b0889116 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -240,8 +240,7 @@ impl GraphicLayers { if let Some(list) = order_map.get_mut(&layer_id.id) { if let Some(to_global) = to_global.get(layer_id) { for clipped_shape in &mut list.0 { - clipped_shape.clip_rect = *to_global * clipped_shape.clip_rect; - clipped_shape.shape.transform(*to_global); + clipped_shape.transform(*to_global); } } all_shapes.append(&mut list.0); @@ -250,13 +249,15 @@ impl GraphicLayers { } // Also draw areas that are missing in `area_order`: + // NOTE: We don't think we end up here in normal situations. + // This is just a safety net in case we have some bug somewhere. + #[expect(clippy::iter_over_hash_type)] for (id, list) in order_map { let layer_id = LayerId::new(order, *id); if let Some(to_global) = to_global.get(&layer_id) { for clipped_shape in &mut list.0 { - clipped_shape.clip_rect = *to_global * clipped_shape.clip_rect; - clipped_shape.shape.transform(*to_global); + clipped_shape.transform(*to_global); } } diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index d4912f9d8..45894f3f7 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -711,6 +711,8 @@ impl Focus { let mut best_score = f32::INFINITY; let mut best_id = None; + // iteration order should only matter in case of a tie, and that should be very rare + #[expect(clippy::iter_over_hash_type)] for (candidate_id, candidate_rect) in &self.focus_widgets_cache { if *candidate_id == current_focused.id { continue; @@ -959,6 +961,7 @@ impl Memory { /// Forget window positions, sizes etc. /// Can be used to auto-layout windows. pub fn reset_areas(&mut self) { + #[expect(clippy::iter_over_hash_type)] for area in self.areas.values_mut() { *area = Default::default(); } @@ -1324,12 +1327,14 @@ impl Areas { wants_to_be_on_top.clear(); // For all layers with sublayers, put the sublayers directly after the parent layer: - let sublayers = std::mem::take(sublayers); - for (parent, children) in sublayers { - let mut moved_layers = vec![parent]; + // (it doesn't matter in which order we replace parents with their children) + #[expect(clippy::iter_over_hash_type)] + for (parent, children) in std::mem::take(sublayers) { + let mut moved_layers = vec![parent]; // parent first… + order.retain(|l| { if children.contains(l) { - moved_layers.push(*l); + moved_layers.push(*l); // …followed by children false } else { true @@ -1338,7 +1343,7 @@ impl Areas { let Some(parent_pos) = order.iter().position(|l| l == &parent) else { continue; }; - order.splice(parent_pos..=parent_pos, moved_layers); + order.splice(parent_pos..=parent_pos, moved_layers); // replace the parent with itself and its children } self.order_map = self diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index bd4d14efd..366ad5190 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -574,6 +574,8 @@ struct PersistedMap(Vec<(u64, SerializedElement)>); #[cfg(feature = "persistence")] impl PersistedMap { fn from_map(map: &IdTypeMap) -> Self { + #![expect(clippy::iter_over_hash_type)] // the serialized order doesn't matter + profiling::function_scope!(); use std::collections::BTreeMap; diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 8818a1715..bcbb04a78 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -113,6 +113,20 @@ pub enum ViewportClass { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ViewportId(pub Id); +// We implement `PartialOrd` and `Ord` so we can use `ViewportId` in a `BTreeMap`, +// which allows predicatable iteration order, frame-to-frame. +impl PartialOrd for ViewportId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ViewportId { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.value().cmp(&other.0.value()) + } +} + impl Default for ViewportId { #[inline] fn default() -> Self { @@ -151,6 +165,9 @@ pub type ViewportIdSet = nohash_hasher::IntSet; /// A fast hash map from [`ViewportId`] to `T`. pub type ViewportIdMap = nohash_hasher::IntMap; +/// An order map from [`ViewportId`] to `T`. +pub type OrderedViewportIdMap = std::collections::BTreeMap; + // ---------------------------------------------------------------------------- /// Image data for an application icon. diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index 3725d62a7..fa99c95dc 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -129,6 +129,7 @@ impl WidgetRects { infos, } = self; + #[expect(clippy::iter_over_hash_type)] for rects in by_layer.values_mut() { rects.clear(); } diff --git a/crates/egui_demo_lib/src/demo/extra_viewport.rs b/crates/egui_demo_lib/src/demo/extra_viewport.rs index 1ab7787f9..d7c875f5f 100644 --- a/crates/egui_demo_lib/src/demo/extra_viewport.rs +++ b/crates/egui_demo_lib/src/demo/extra_viewport.rs @@ -54,7 +54,11 @@ fn viewport_content(ui: &mut egui::Ui, ctx: &egui::Context, open: &mut bool) { egui::ScrollArea::vertical().show(ui, |ui| { let viewports = ui.input(|i| i.raw.viewports.clone()); - for (id, viewport) in viewports { + let ordered_viewports = viewports + .iter() + .map(|(id, viewport)| (*id, viewport.clone())) + .collect::>(); + for (id, viewport) in ordered_viewports { ui.group(|ui| { ui.label(format!("viewport {id:?}")); ui.push_id(id, |ui| { diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 2e32bd3da..7a15d4d3c 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -53,7 +53,6 @@ x11 = ["winit?/x11"] egui = { workspace = true, default-features = false, features = ["bytemuck"] } egui-winit = { workspace = true, optional = true, default-features = false } -ahash.workspace = true bytemuck.workspace = true glow.workspace = true log.workspace = true diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 98fe25a45..03372f7aa 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -698,6 +698,7 @@ impl Painter { unsafe fn destroy_gl(&self) { unsafe { self.gl.delete_program(self.program); + #[expect(clippy::iter_over_hash_type)] for tex in self.textures.values() { self.gl.delete_texture(*tex); } diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index 2fe15dcd0..0c1a47670 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -1,7 +1,6 @@ -use ahash::HashSet; +pub use egui_winit::{self, EventResponse}; + use egui::{ViewportId, ViewportOutput}; -pub use egui_winit; -pub use egui_winit::EventResponse; use egui_winit::winit; use crate::shader_version::ShaderVersion; @@ -81,7 +80,7 @@ impl EguiGlow { log::warn!("Multiple viewports not yet supported by EguiGlow"); } for (_, ViewportOutput { commands, .. }) in viewport_output { - let mut actions_requested: HashSet = Default::default(); + let mut actions_requested = Default::default(); egui_winit::process_viewport_commands( &self.egui_ctx, &mut self.viewport_info, diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index f02889d97..5e6dc27e1 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -127,6 +127,18 @@ pub struct ClippedShape { pub shape: Shape, } +impl ClippedShape { + /// Transform (move/scale) the shape in-place. + /// + /// If using a [`PaintCallback`], note that only the rect is scaled as opposed + /// to other shapes where the stroke is also scaled. + pub fn transform(&mut self, transform: emath::TSTransform) { + let Self { clip_rect, shape } = self; + *clip_rect = transform * *clip_rect; + shape.transform(transform); + } +} + /// A [`Mesh`] or [`PaintCallback`] within a clip rectangle. /// /// Everything is using logical points. diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 3cdabeadc..10de38d15 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -416,7 +416,7 @@ impl Shape { self.transform(TSTransform::from_translation(delta)); } - /// Move the shape by this many points, in-place. + /// Transform (move/scale) the shape in-place. /// /// If using a [`PaintCallback`], note that only the rect is scaled as opposed /// to other shapes where the stroke is also scaled. diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index 9b7876323..ad39ffb34 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -367,6 +367,7 @@ fn drag_and_drop_test(ui: &mut egui::Ui) { assert!(col <= COLS, "The col should be less then: {COLS}"); // Should be a better way to do this! + #[expect(clippy::iter_over_hash_type)] for container_data in self.containers_data.values_mut() { for ids in container_data { ids.retain(|i| *i != id); From e8e99a0bb6eb8467d0478043fd73005fc39ae73d Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 7 Aug 2025 14:18:04 +0200 Subject: [PATCH 164/388] Fix manual `Popup` not closing (#7383) Fixes manually created popups (via `Popup::new`) not closing, since widget_clicked_elsewhere was always false. This example would never close: ```rs let mut open = true; eframe::run_simple_native("My egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { let response = egui::Popup::new( Id::new("popup"), ctx.clone(), PopupAnchor::Position(Pos2::new(10.0, 10.0)), LayerId::new(Order::Foreground, Id::new("popup")), ) .open(open) .show(|ui| { ui.label("This is a popup!"); ui.label("You can put anything in here."); }); if let Some(response) = response { if response.response.should_close() { open = false; } } }); }) ``` I also noticed that the Color submenu in the popups example had a double arrow (must have been broken in the atoms PR): Screenshot 2025-08-07 at 13 42 28 Also fixed this in the PR. --- crates/egui/src/containers/popup.rs | 27 +++++++++++++------------ crates/egui_demo_lib/src/demo/popups.rs | 20 +++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 520fb408e..535ffc354 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -179,9 +179,6 @@ pub struct Popup<'a> { /// Gap between the anchor and the popup gap: f32, - /// Used later depending on close behavior - widget_clicked_elsewhere: bool, - /// Default width passed to the Area width: Option, sense: Sense, @@ -205,7 +202,6 @@ impl<'a> Popup<'a> { rect_align: RectAlign::BOTTOM_START, alternative_aligns: None, gap: 0.0, - widget_clicked_elsewhere: false, width: None, sense: Sense::click(), layout: Layout::default(), @@ -219,14 +215,12 @@ 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( + Self::new( Self::default_response_id(response), response.ctx.clone(), response, response.layer_id, - ); - popup.widget_clicked_elsewhere = response.clicked_elsewhere(); - popup + ) } /// Show a popup relative to some widget, @@ -504,9 +498,14 @@ 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 hover_pos = self.ctx.pointer_hover_pos(); - let id = self.id; + // When the popup was just opened with a click we don't want to immediately close it based + // on the `PopupCloseBehavior`, so we need to remember if the popup was already open on + // last frame. A convenient way to check this is to see if we have a response for the `Area` + // from last frame: + let was_open_last_frame = self.ctx.read_response(id).is_some(); + + let hover_pos = self.ctx.pointer_hover_pos(); if let OpenKind::Memory { set } = self.open_kind { match set { Some(SetOpenCommand::Bool(open)) => { @@ -548,7 +547,6 @@ impl<'a> Popup<'a> { rect_align: _, alternative_aligns: _, gap, - widget_clicked_elsewhere, width, sense, layout, @@ -595,10 +593,13 @@ impl<'a> Popup<'a> { frame.show(ui, content).inner }); + // If the popup was just opened with a click, we don't want to immediately close it again. + let close_click = was_open_last_frame && ctx.input(|i| i.pointer.any_click()); + let closed_by_click = match close_behavior { - PopupCloseBehavior::CloseOnClick => widget_clicked_elsewhere, + PopupCloseBehavior::CloseOnClick => close_click, PopupCloseBehavior::CloseOnClickOutside => { - widget_clicked_elsewhere && response.response.clicked_elsewhere() + close_click && response.response.clicked_elsewhere() } PopupCloseBehavior::IgnoreClicks => false, }; diff --git a/crates/egui_demo_lib/src/demo/popups.rs b/crates/egui_demo_lib/src/demo/popups.rs index 6f7152c2b..4574d99e2 100644 --- a/crates/egui_demo_lib/src/demo/popups.rs +++ b/crates/egui_demo_lib/src/demo/popups.rs @@ -2,8 +2,8 @@ use crate::rust_view_ui; use egui::color_picker::{Alpha, color_picker_color32}; use egui::containers::menu::{MenuConfig, SubMenuButton}; use egui::{ - Align, Align2, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior, RectAlign, RichText, - Tooltip, Ui, UiBuilder, include_image, + Align, Align2, Atom, Button, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior, RectAlign, + RichText, Tooltip, Ui, UiBuilder, include_image, }; /// Showcase [`Popup`]. @@ -79,13 +79,15 @@ impl PopupsDemo { } else { egui::Color32::WHITE }; - let mut color_button = - SubMenuButton::new(RichText::new("Background").color(text_color)); - color_button.button = color_button.button.fill(self.color); - color_button.button = color_button - .button - .right_text(RichText::new(SubMenuButton::RIGHT_ARROW).color(text_color)); - color_button.ui(ui, |ui| { + + let button = Button::new(( + RichText::new("Background").color(text_color), + Atom::grow(), + RichText::new(SubMenuButton::RIGHT_ARROW).color(text_color), + )) + .fill(self.color); + + SubMenuButton::from_button(button).ui(ui, |ui| { ui.spacing_mut().slider_width = 200.0; color_picker_color32(ui, &mut self.color, Alpha::Opaque); }); From 3024c39eaf439e6ad0b35ae395ba26003948cdd9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 8 Aug 2025 09:57:53 +0200 Subject: [PATCH 165/388] Enable and fix some more clippy lints (#7426) One can never have too many lints --- Cargo.toml | 16 +++++++++++++- crates/ecolor/src/hsva.rs | 2 +- crates/eframe/src/epi.rs | 19 ++++++++-------- crates/eframe/src/native/glow_integration.rs | 12 ++++++---- crates/eframe/src/native/run.rs | 6 ++--- crates/eframe/src/native/wgpu_integration.rs | 21 ++++++++---------- crates/egui-wgpu/src/renderer.rs | 2 +- crates/egui-wgpu/src/winit.rs | 2 +- crates/egui-winit/src/lib.rs | 4 ++-- crates/egui/src/containers/popup.rs | 2 +- crates/egui/src/containers/scroll_area.rs | 4 ++-- crates/egui/src/containers/window.rs | 2 +- crates/egui/src/context.rs | 2 +- crates/egui/src/data/output.rs | 2 +- crates/egui/src/interaction.rs | 2 +- crates/egui/src/layout.rs | 4 ++-- crates/egui/src/memory/mod.rs | 22 ++++++++----------- crates/egui/src/menu.rs | 5 ++--- crates/egui/src/style.rs | 2 +- crates/egui/src/widgets/button.rs | 2 +- crates/egui/src/widgets/label.rs | 2 +- crates/egui/src/widgets/slider.rs | 4 ++-- .../egui_demo_app/src/apps/fractal_clock.rs | 2 +- crates/egui_demo_app/src/apps/image_viewer.rs | 2 +- crates/egui_demo_app/src/main.rs | 2 +- .../src/demo/misc_demo_window.rs | 4 ++-- crates/egui_demo_lib/src/demo/paint_bezier.rs | 2 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- .../src/easy_mark/easy_mark_viewer.rs | 2 +- crates/egui_extras/src/datepicker/popup.rs | 2 +- crates/egui_glow/src/painter.rs | 2 +- crates/egui_kittest/src/snapshot.rs | 19 +++++++++++----- crates/egui_kittest/tests/menu.rs | 2 +- crates/egui_kittest/tests/tests.rs | 2 +- crates/emath/src/align.rs | 2 +- crates/emath/src/rect.rs | 4 ++-- crates/emath/src/smart_aim.rs | 2 +- crates/emath/src/ts_transform.rs | 2 +- examples/puffin_profiler/src/main.rs | 2 +- examples/screenshot/src/main.rs | 2 +- xtask/src/utils.rs | 2 +- 41 files changed, 107 insertions(+), 91 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b6b6b2628..67ee191b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,8 +159,8 @@ disallowed_types = "warn" # See clippy.toml doc_link_with_quotes = "warn" doc_markdown = "warn" empty_enum = "warn" -empty_line_after_outer_attr = "warn" empty_enum_variants_with_brackets = "warn" +empty_line_after_outer_attr = "warn" enum_glob_use = "warn" equatable_if_let = "warn" exit = "warn" @@ -180,6 +180,7 @@ if_let_mutex = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" +inconsistent_struct_constructor = "warn" index_refutable_slice = "warn" inefficient_to_string = "warn" infinite_loop = "warn" @@ -295,8 +296,21 @@ verbose_file_reads = "warn" wildcard_dependencies = "warn" zero_sized_map_values = "warn" +# Enable these when we update MSRV: +# doc_comment_double_space_linebreaks = "warn" +# elidable_lifetime_names = "warn" +# ignore_without_reason = "warn" +# manual_midpoint = "warn" +# non_std_lazy_statics = "warn" +# precedence_bits = "warn" +# return_and_then = "warn" +# single_option_map = "warn" +# unnecessary_debug_formatting = "warn" +# unnecessary_semicolon = "warn" + # TODO(emilk): maybe enable more of these lints? +comparison_chain = "allow" should_panic_without_expect = "allow" too_many_lines = "allow" unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one diff --git a/crates/ecolor/src/hsva.rs b/crates/ecolor/src/hsva.rs index 97aed1b59..06adb121f 100644 --- a/crates/ecolor/src/hsva.rs +++ b/crates/ecolor/src/hsva.rs @@ -234,7 +234,7 @@ pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] { } #[test] -#[ignore] // a bit expensive +#[ignore = "too expensive"] fn test_hsv_roundtrip() { for r in 0..=255 { for g in 0..=255 { diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 3e6023270..4b5e69d11 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -893,16 +893,15 @@ pub trait Storage { #[cfg(feature = "ron")] pub fn get_value(storage: &dyn Storage, key: &str) -> Option { profiling::function_scope!(key); - storage - .get_string(key) - .and_then(|value| match ron::from_str(&value) { - Ok(value) => Some(value), - Err(err) => { - // This happens on when we break the format, e.g. when updating egui. - log::debug!("Failed to decode RON: {err}"); - None - } - }) + let value = storage.get_string(key)?; + match ron::from_str(&value) { + Ok(value) => Some(value), + Err(err) => { + // This happens on when we break the format, e.g. when updating egui. + log::debug!("Failed to decode RON: {err}"); + None + } + } } /// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key. diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 661308843..4a1df9e1a 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -336,10 +336,10 @@ impl<'app> GlowWinitApp<'app> { } Ok(self.running.insert(GlowWinitRunning { - glutin, - painter, integration, app, + glutin, + painter, })) } } @@ -362,8 +362,12 @@ impl WinitApp for GlowWinitApp<'_> { fn window_id_from_viewport_id(&self, id: ViewportId) -> Option { self.running - .as_ref() - .and_then(|r| r.glutin.borrow().window_from_viewport.get(&id).copied()) + .as_ref()? + .glutin + .borrow() + .window_from_viewport + .get(&id) + .copied() } fn save(&mut self) { diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 9c31aefe6..d21731bd1 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -145,7 +145,7 @@ impl WinitAppWrapper { log::error!("Exiting because of error: {err}"); exit = true; self.return_result = Err(err); - }; + } if save { log::debug!("Received an EventResult::Save - saving app state"); @@ -176,7 +176,7 @@ impl WinitAppWrapper { .retain(|window_id, repaint_time| { if now < *repaint_time { return true; // not yet ready - }; + } event_loop.set_control_flow(ControlFlow::Poll); @@ -192,7 +192,7 @@ impl WinitAppWrapper { let next_repaint_time = self.windows_next_repaint_times.values().min().copied(); if let Some(next_repaint_time) = next_repaint_time { event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time)); - }; + } } } diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 0bf4fb5cc..1d06e6722 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -333,10 +333,8 @@ impl WinitApp for WgpuWinitApp<'_> { .as_ref() .and_then(|r| { let shared = r.shared.borrow(); - shared - .viewport_from_window - .get(&window_id) - .and_then(|id| shared.viewports.get(id).map(|v| v.window.clone())) + let id = shared.viewport_from_window.get(&window_id)?; + shared.viewports.get(id).map(|v| v.window.clone()) }) .flatten() } @@ -821,17 +819,16 @@ impl WgpuWinitRunning<'_> { } _ => {} - }; + } let event_response = viewport_id .and_then(|viewport_id| { - shared.viewports.get_mut(&viewport_id).and_then(|viewport| { - Some(integration.on_window_event( - viewport.window.as_deref()?, - viewport.egui_winit.as_mut()?, - event, - )) - }) + let viewport = shared.viewports.get_mut(&viewport_id)?; + Some(integration.on_window_event( + viewport.window.as_deref()?, + viewport.egui_winit.as_mut()?, + event, + )) }) .unwrap_or_default(); diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 012613aeb..a626c8efa 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -873,7 +873,7 @@ impl Renderer { callbacks.push(c.0.as_ref()); } else { log::warn!("Unknown paint callback: expected `egui_wgpu::Callback`"); - }; + } acc } } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index cd02b59d8..800d67c31 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -328,7 +328,7 @@ impl Painter { }) .create_view(&wgpu::TextureViewDescriptor::default()), ); - }; + } } pub fn on_window_resized( diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 65612b497..688d452c2 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -372,7 +372,7 @@ impl State { winit::event::Ime::Disabled | winit::event::Ime::Preedit(_, None) => { self.ime_event_disable(); } - }; + } EventResponse { repaint: true, @@ -583,7 +583,7 @@ impl State { pos, force: None, }); - }; + } } } } diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 535ffc354..e8c0ac596 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -458,7 +458,7 @@ impl<'a> Popup<'a> { /// Get the expected size of the popup. pub fn get_expected_size(&self) -> Option { - AreaState::load(&self.ctx, self.id).and_then(|area| area.size) + AreaState::load(&self.ctx, self.id)?.size } /// Calculate the best alignment for the popup, based on the last size and screen rect. diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 45599ac8c..dbefe018d 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1059,7 +1059,7 @@ impl Prepared { delta += delta_update; animation = animation_update; - }; + } if delta != 0.0 { let target_offset = state.offset[d] + delta; @@ -1090,7 +1090,7 @@ impl Prepared { for d in 0..2 { if saved_scroll_target[d].is_some() { state.scroll_target[d] = saved_scroll_target[d].clone(); - }; + } } }); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index e93b046e5..39190d7a6 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -616,7 +616,7 @@ impl Window<'_> { *where_to_put_header_background, RectShape::filled(title_bar.inner_rect, round, header_color), ); - }; + } if false { ctx.debug_painter().debug_rect( diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 859835da3..a48de514e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1259,7 +1259,7 @@ impl Context { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::RIGHT; } _ => return false, - }; + } true }); }); diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 76c3de5f6..809fae34b 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -729,7 +729,7 @@ impl WidgetInfo { description = format!("{state} {description}"); } else { description += if *selected { "selected" } else { "" }; - }; + } } if let Some(label) = label { diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 9cd76b3a0..d81291a6f 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -293,7 +293,7 @@ pub(crate) fn interact( drag_started, dragged, drag_stopped, - contains_pointer, hovered, + contains_pointer, } } diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 601d1e2e7..c44c928bb 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -753,7 +753,7 @@ impl Layout { pos2(frame_rect.max.x, f32::NAN), ); } - }; + } } } else { // Make sure we also expand where we consider adding things (the cursor): @@ -779,7 +779,7 @@ impl Layout { Direction::BottomUp => { cursor.max.y = widget_rect.min.y - item_spacing.y; } - }; + } } /// Move to the next row in a wrapping layout. diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 45894f3f7..349ebcea3 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -802,15 +802,12 @@ impl Memory { /// Top-most layer at the given position. pub fn layer_id_at(&self, pos: Pos2) -> Option { - self.areas() - .layer_id_at(pos, &self.to_global) - .and_then(|layer_id| { - if self.is_above_modal_layer(layer_id) { - Some(layer_id) - } else { - self.top_modal_layer() - } - }) + let layer_id = self.areas().layer_id_at(pos, &self.to_global)?; + if self.is_above_modal_layer(layer_id) { + Some(layer_id) + } else { + self.top_modal_layer() + } } /// The currently set transform of a layer. @@ -855,7 +852,7 @@ impl Memory { /// Which widget has keyboard focus? pub fn focused(&self) -> Option { - self.focus().and_then(|f| f.focused()) + self.focus()?.focused() } /// Set an event filter for a widget. @@ -1066,9 +1063,8 @@ impl Memory { /// 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) - .and_then(|state| if state.id == id { state.pos } else { None }) + let state = self.popups.get(&self.viewport_id)?; + if state.id == id { state.pos } else { None } } /// Close any currently open popup. diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index f245473db..f9744bd5c 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -766,9 +766,8 @@ impl MenuState { } fn submenu(&self, id: Id) -> Option<&Arc>> { - self.sub_menu - .as_ref() - .and_then(|(k, sub)| if id == *k { Some(sub) } else { None }) + let (k, sub) = self.sub_menu.as_ref()?; + if id == *k { Some(sub) } else { None } } /// Open submenu at position, if not already open. diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 364b3fffc..68491d2d9 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2136,7 +2136,7 @@ impl Visuals { ui.color_edit_button_srgba(color); } else { *color = None; - }; + } }); ui.end_row(); diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index d836c0701..fef32da38 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -325,7 +325,7 @@ impl<'a> Button<'a> { .fill(fill) .stroke(stroke) .corner_radius(corner_radius.unwrap_or(visuals.corner_radius)); - }; + } prepared.paint(ui) } else { diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 1abac7886..007a1291a 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -250,7 +250,7 @@ impl Label { } else { layout_job.halign = self.halign.unwrap_or(ui.layout().horizontal_placement()); layout_job.justify = ui.layout().horizontal_justify(); - }; + } let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); let (rect, mut response) = ui.allocate_exact_size(galley.size(), sense); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 777c2eed1..7937e5897 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -806,7 +806,7 @@ impl Slider<'_> { SliderOrientation::Vertical => { trailing_rail_rect.min.y = center.y - corner_radius.se as f32; } - }; + } ui.painter().rect_filled( trailing_rail_rect, @@ -936,7 +936,7 @@ impl Slider<'_> { if let Some(fmt) = &self.custom_formatter { dv = dv.custom_formatter(fmt); - }; + } if let Some(parser) = &self.custom_parser { dv = dv.custom_parser(parser); } diff --git a/crates/egui_demo_app/src/apps/fractal_clock.rs b/crates/egui_demo_app/src/apps/fractal_clock.rs index 50d9ae5ef..52a7f25b2 100644 --- a/crates/egui_demo_app/src/apps/fractal_clock.rs +++ b/crates/egui_demo_app/src/apps/fractal_clock.rs @@ -73,7 +73,7 @@ impl FractalClock { )); } else { ui.label("The fractal_clock clock is not showing the correct time"); - }; + } ui.label(format!("Painted line count: {}", self.line_count)); ui.checkbox(&mut self.paused, "Paused"); diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index 75ff27190..16e380e81 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -61,7 +61,7 @@ impl eframe::App for ImageViewer { ctx.forget_image(&self.current_uri); self.uri_edit_text = self.uri_edit_text.trim().to_owned(); self.current_uri = self.uri_edit_text.clone(); - }; + } #[cfg(not(target_arch = "wasm32"))] if ui.button("file…").clicked() { diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 099d16f59..48825715d 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -91,5 +91,5 @@ fn start_puffin_server() { Err(err) => { log::error!("Failed to start puffin server: {err}"); } - }; + } } 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 5af27d5e4..b5a2402b7 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -805,7 +805,7 @@ impl TextRotation { if let egui::epaint::Shape::Text(ts) = &mut t { let new = ts.clone().with_angle_and_anchor(self.angle, self.align); *ts = new; - }; + } t }); @@ -814,7 +814,7 @@ impl TextRotation { let align_pt = rect.min + start_pos + self.align.pos_in_rect(&ts.galley.rect).to_vec2(); painter.circle(align_pt, 2.0, Color32::RED, (0.0, Color32::RED)); - }; + } painter.rect( rect, diff --git a/crates/egui_demo_lib/src/demo/paint_bezier.rs b/crates/egui_demo_lib/src/demo/paint_bezier.rs index 7017560a5..57def359f 100644 --- a/crates/egui_demo_lib/src/demo/paint_bezier.rs +++ b/crates/egui_demo_lib/src/demo/paint_bezier.rs @@ -152,7 +152,7 @@ impl PaintBezier { _ => { unreachable!(); } - }; + } painter.add(PathShape::line(points_in_screen, self.aux_stroke)); painter.extend(control_point_shapes); 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 d17385a68..47d0beeaf 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 @@ -165,7 +165,7 @@ fn shortcuts(ui: &Ui, code: &mut dyn TextBuffer, ccursor_range: &mut CCursorRang if ui.input_mut(|i| i.consume_shortcut(&shortcut)) { any_change = true; toggle_surrounding(code, ccursor_range, surrounding); - }; + } } any_change 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 13ff7da03..41d99fb8a 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 @@ -101,7 +101,7 @@ pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) { Shape::rect_filled(rect, 1.0, code_bg_color), ); } - }; + } } fn rich_text_from_style(text: &str, style: &easy_mark::Style) -> RichText { diff --git a/crates/egui_extras/src/datepicker/popup.rs b/crates/egui_extras/src/datepicker/popup.rs index c63de3a91..d353307b3 100644 --- a/crates/egui_extras/src/datepicker/popup.rs +++ b/crates/egui_extras/src/datepicker/popup.rs @@ -336,7 +336,7 @@ impl DatePickerPopup<'_> { if day.month() != popup_state.month { text_color = text_color.linear_multiply(0.5); - }; + } let button_response = ui.add( Button::new( diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 03372f7aa..8409765d6 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -528,7 +528,7 @@ impl Painter { self.upload_texture_srgb(delta.pos, image.size, delta.options, data); } - }; + } } fn upload_texture_srgb( diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index c27a14887..6f565166f 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -232,7 +232,8 @@ impl Display for SnapshotError { let diff_path = std::path::absolute(diff_path).unwrap_or(diff_path.clone()); write!( f, - "'{name}' Image did not match snapshot. Diff: {diff}, {diff_path:?}. {HOW_TO_UPDATE_SCREENSHOTS}" + "'{name}' Image did not match snapshot. Diff: {diff}, {}. {HOW_TO_UPDATE_SCREENSHOTS}", + diff_path.display() ) } Self::OpenSnapshot { path, err } => { @@ -240,19 +241,25 @@ impl Display for SnapshotError { match err { ImageError::IoError(io) => match io.kind() { ErrorKind::NotFound => { - write!(f, "Missing snapshot: {path:?}. {HOW_TO_UPDATE_SCREENSHOTS}") + write!( + f, + "Missing snapshot: {}. {HOW_TO_UPDATE_SCREENSHOTS}", + path.display() + ) } err => { write!( f, - "Error reading snapshot: {err:?}\nAt: {path:?}. {HOW_TO_UPDATE_SCREENSHOTS}" + "Error reading snapshot: {err:?}\nAt: {}. {HOW_TO_UPDATE_SCREENSHOTS}", + path.display() ) } }, 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" + "Error decoding snapshot: {err:?}\nAt: {}. Make sure git-lfs is setup correctly. Read the instructions here: https://github.com/emilk/egui/blob/main/CONTRIBUTING.md#making-a-pr", + path.display() ) } } @@ -269,7 +276,7 @@ impl Display for SnapshotError { } Self::WriteSnapshot { path, err } => { let path = std::path::absolute(path).unwrap_or(path.clone()); - write!(f, "Error writing snapshot: {err:?}\nAt: {path:?}") + write!(f, "Error writing snapshot: {err:?}\nAt: {}", path.display()) } Self::RenderError { err } => { write!(f, "Error rendering image: {err:?}") @@ -363,7 +370,7 @@ fn try_image_snapshot_options_impl( // No need for an explicit `.new` file: std::fs::remove_file(&new_path).ok(); - println!("Updated snapshot: {snapshot_path:?}"); + println!("Updated snapshot: {}", snapshot_path.display()); Ok(()) }; diff --git a/crates/egui_kittest/tests/menu.rs b/crates/egui_kittest/tests/menu.rs index b7d001308..a76001e46 100644 --- a/crates/egui_kittest/tests/menu.rs +++ b/crates/egui_kittest/tests/menu.rs @@ -50,7 +50,7 @@ impl TestMenu { .clicked() { ui.close(); - }; + } }); }); }); diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index 6d66c5f5a..8ff6584ac 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -93,7 +93,7 @@ fn test_scroll_harness() -> Harness<'static, bool> { } if ui.button("Hidden Button").clicked() { *state = true; - }; + } }); }, false, diff --git a/crates/emath/src/align.rs b/crates/emath/src/align.rs index a672c456e..9604855e8 100644 --- a/crates/emath/src/align.rs +++ b/crates/emath/src/align.rs @@ -134,7 +134,7 @@ impl Align { if size == f32::INFINITY { Rangef::new(f32::NEG_INFINITY, f32::INFINITY) } else { - let left = (min + max) / 2.0 - size / 2.0; + let left = f32::midpoint(min, max) - size / 2.0; Rangef::new(left, left + size) } } diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index b9e45c52d..7731a5ce0 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -331,8 +331,8 @@ impl Rect { #[inline(always)] pub fn center(&self) -> Pos2 { Pos2 { - x: (self.min.x + self.max.x) / 2.0, - y: (self.min.y + self.max.y) / 2.0, + x: f32::midpoint(self.min.x, self.max.x), + y: f32::midpoint(self.min.y, self.max.y), } } diff --git a/crates/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index e75ae42ce..a31816474 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -43,7 +43,7 @@ pub fn best_in_range_f64(min: f64, max: f64) -> f64 { if min_exponent.floor() != max_exponent.floor() { // pick the geometric center of the two: - let exponent = (min_exponent + max_exponent) / 2.0; + let exponent = f64::midpoint(min_exponent, max_exponent); return 10.0_f64.powi(exponent.round() as i32); } diff --git a/crates/emath/src/ts_transform.rs b/crates/emath/src/ts_transform.rs index 46c7e2a8e..515f5ecd7 100644 --- a/crates/emath/src/ts_transform.rs +++ b/crates/emath/src/ts_transform.rs @@ -36,8 +36,8 @@ impl TSTransform { /// `(0, 0)`, then translates them. pub fn new(translation: Vec2, scaling: f32) -> Self { Self { - translation, scaling, + translation, } } diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 1386e4884..942b57a4a 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -177,5 +177,5 @@ fn start_puffin_server() { Err(err) => { log::error!("Failed to start puffin server: {err}"); } - }; + } } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 1dd0bbf50..0cff59a93 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -57,7 +57,7 @@ impl eframe::App for MyApp { ctx.set_theme(egui::Theme::Dark); } else { ctx.set_theme(egui::Theme::Light); - }; + } ctx.send_viewport_cmd( egui::ViewportCommand::Screenshot(Default::default()), ); diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs index afefa6054..b45295aea 100644 --- a/xtask/src/utils.rs +++ b/xtask/src/utils.rs @@ -32,7 +32,7 @@ pub fn ask_to_run(mut cmd: Command, ask: bool, reason: &str) -> Result<(), DynEr "" | "y" | "yes" => {} "n" | "no" => return Err("Aborting as per your request".into()), a => return Err(format!("Invalid answer `{a}`").into()), - }; + } } else { println!("Running `{cmd:?}` to {reason}."); } From 6fae65a3fabaf442cbbc96a76cbf70fe2fee16ee Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 8 Aug 2025 12:04:51 +0200 Subject: [PATCH 166/388] Add `emath::fast_midpoint` (#7435) --- Cargo.toml | 2 +- crates/emath/src/align.rs | 2 +- crates/emath/src/lib.rs | 15 +++++++++++++++ crates/emath/src/rect.rs | 6 +++--- crates/emath/src/smart_aim.rs | 4 +++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67ee191b5..d29449711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -300,7 +300,7 @@ zero_sized_map_values = "warn" # doc_comment_double_space_linebreaks = "warn" # elidable_lifetime_names = "warn" # ignore_without_reason = "warn" -# manual_midpoint = "warn" +# manual_midpoint = "warn" # NOTE `midpoint` is often a lot slower for floats, so we have our own `emath::fast_midpoint` function. # non_std_lazy_statics = "warn" # precedence_bits = "warn" # return_and_then = "warn" diff --git a/crates/emath/src/align.rs b/crates/emath/src/align.rs index 9604855e8..395323d4f 100644 --- a/crates/emath/src/align.rs +++ b/crates/emath/src/align.rs @@ -134,7 +134,7 @@ impl Align { if size == f32::INFINITY { Rangef::new(f32::NEG_INFINITY, f32::INFINITY) } else { - let left = f32::midpoint(min, max) - size / 2.0; + let left = crate::fast_midpoint(min, max) - size / 2.0; Rangef::new(left, left + size) } } diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 2d9dfb2f1..476b36d07 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -112,6 +112,21 @@ where (T::ONE - t) * *range.start() + t * *range.end() } +/// This is a faster version of [`f32::midpoint`] which doesn't handle overflow. +/// +/// ``` +/// # use emath::fast_midpoint; +/// assert_eq!(fast_midpoint(1.0, 5.0), 3.0); +/// ``` +#[inline(always)] +pub fn fast_midpoint(a: R, b: R) -> R +where + R: Copy + Add + Div + One, +{ + let two = R::ONE + R::ONE; + (a + b) / two +} + /// Where in the range is this value? Returns 0-1 if within the range. /// /// Returns <0 if before and >1 if after. diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 7731a5ce0..b46fc43ca 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{Div, Mul, NumExt as _, Pos2, Rangef, Rot2, Vec2, lerp, pos2, vec2}; +use crate::{Div, Mul, NumExt as _, Pos2, Rangef, Rot2, Vec2, fast_midpoint, lerp, pos2, vec2}; use std::ops::{BitOr, BitOrAssign}; /// A rectangular region of space. @@ -331,8 +331,8 @@ impl Rect { #[inline(always)] pub fn center(&self) -> Pos2 { Pos2 { - x: f32::midpoint(self.min.x, self.max.x), - y: f32::midpoint(self.min.y, self.max.y), + x: fast_midpoint(self.min.x, self.max.x), + y: fast_midpoint(self.min.y, self.max.y), } } diff --git a/crates/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index a31816474..8d083917a 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -1,5 +1,7 @@ //! Find "simple" numbers is some range. Used by sliders. +use crate::fast_midpoint; + const NUM_DECIMALS: usize = 15; /// Find the "simplest" number in a closed range [min, max], i.e. the one with the fewest decimal digits. @@ -43,7 +45,7 @@ pub fn best_in_range_f64(min: f64, max: f64) -> f64 { if min_exponent.floor() != max_exponent.floor() { // pick the geometric center of the two: - let exponent = f64::midpoint(min_exponent, max_exponent); + let exponent = fast_midpoint(min_exponent, max_exponent); return 10.0_f64.powi(exponent.round() as i32); } From fb5fe645be09bcde08aa6bc38a115ca366e7197f Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:12:44 +0200 Subject: [PATCH 167/388] Make the `hex_color` macro `const` (#7444) * Closes * [x] I have followed the instructions in the PR template --- crates/ecolor/src/color32.rs | 23 +++++++++++++++++++++++ crates/ecolor/src/hex_color_macro.rs | 7 ++++--- crates/ecolor/src/lib.rs | 4 ++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index e73720703..b9de323e0 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -157,6 +157,27 @@ impl Color32 { } } + /// Same as [`Self::from_rgba_unmultiplied`], but can be used in a const context. + /// + /// It is slightly slower when operating on non-const data. + #[inline] + pub const fn from_rgba_unmultiplied_const(r: u8, g: u8, b: u8, a: u8) -> Self { + match a { + // common-case optimization: + 0 => Self::TRANSPARENT, + + // common-case optimization: + 255 => Self::from_rgb(r, g, b), + + a => { + let r = fast_round(r as f32 * linear_f32_from_linear_u8(a)); + let g = fast_round(g as f32 * linear_f32_from_linear_u8(a)); + let b = fast_round(b as f32 * linear_f32_from_linear_u8(a)); + Self::from_rgba_premultiplied(r, g, b, a) + } + } + } + /// Opaque gray. #[doc(alias = "from_grey")] #[inline] @@ -502,9 +523,11 @@ mod test { fn to_from_rgba() { for [r, g, b, a] in test_rgba() { let original = Color32::from_rgba_unmultiplied(r, g, b, a); + let constfn = Color32::from_rgba_unmultiplied_const(r, g, b, a); let rgba = Rgba::from(original); let back = Color32::from(rgba); assert_eq!(back, original); + assert_eq!(constfn, original); } assert_eq!( diff --git a/crates/ecolor/src/hex_color_macro.rs b/crates/ecolor/src/hex_color_macro.rs index e95bbc465..cda00a513 100644 --- a/crates/ecolor/src/hex_color_macro.rs +++ b/crates/ecolor/src/hex_color_macro.rs @@ -31,10 +31,11 @@ /// let _ = ecolor::hex_color!("#20212x"); /// ``` /// -/// The macro cannot be used in a `const` context. +/// The macro can be used in a `const` context. /// -/// ```compile_fail +/// ``` /// const COLOR: ecolor::Color32 = ecolor::hex_color!("#202122"); +/// assert_eq!(COLOR, ecolor::Color32::from_rgb(0x20, 0x21, 0x22)); /// ``` #[macro_export] macro_rules! hex_color { @@ -42,7 +43,7 @@ macro_rules! hex_color { let array = $crate::color_hex::color_from_hex!($s); match array.as_slice() { [r, g, b] => $crate::Color32::from_rgb(*r, *g, *b), - [r, g, b, a] => $crate::Color32::from_rgba_unmultiplied(*r, *g, *b, *a), + [r, g, b, a] => $crate::Color32::from_rgba_unmultiplied_const(*r, *g, *b, *a), _ => panic!("Invalid hex color length: expected 3 (RGB) or 4 (RGBA) bytes"), } }}; diff --git a/crates/ecolor/src/lib.rs b/crates/ecolor/src/lib.rs index 9c4ac9b66..f4cdf4d77 100644 --- a/crates/ecolor/src/lib.rs +++ b/crates/ecolor/src/lib.rs @@ -105,7 +105,7 @@ pub fn linear_f32_from_gamma_u8(s: u8) -> f32 { /// linear [0, 255] -> linear [0, 1]. /// Useful for alpha-channel. #[inline(always)] -pub fn linear_f32_from_linear_u8(a: u8) -> f32 { +pub const fn linear_f32_from_linear_u8(a: u8) -> f32 { a as f32 / 255.0 } @@ -130,7 +130,7 @@ pub fn linear_u8_from_linear_f32(a: f32) -> u8 { fast_round(a * 255.0) } -fn fast_round(r: f32) -> u8 { +const fn fast_round(r: f32) -> u8 { (r + 0.5) as _ // rust does a saturating cast since 1.45 } From 53d8c48b4f54faaedd3077bb95202897eec502e5 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:13:50 +0200 Subject: [PATCH 168/388] Fix panic on Stroke style editor widget (#7443) The infinite limit caused arithmetic issues when rendering the preview with very large values, which led to a panic. The new limit should still be higher than anyone would reasonably want to set a stroke width to, but not high enough to trigger a panic. * Closes * [x] I have followed the instructions in the PR template --- crates/egui/src/style.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 68491d2d9..aa24f472c 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2708,7 +2708,7 @@ impl Widget for &mut Stroke { let Stroke { width, color } = self; ui.horizontal(|ui| { - ui.add(DragValue::new(width).speed(0.1).range(0.0..=f32::INFINITY)) + ui.add(DragValue::new(width).speed(0.1).range(0.0..=1e9)) .on_hover_text("Width"); ui.color_edit_button_srgba(color); From 68d456ac0b52b30b92c02250354ce76800973f56 Mon Sep 17 00:00:00 2001 From: RndUsr123 <150948884+RndUsr123@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:27:55 +0000 Subject: [PATCH 169/388] Fixes sense issues in TextEdit when vertical alignment is used (#7436) * Closes * [ ] I have followed the instructions in the PR template I'm running a rustup-less rust install on Windows, so I don't have `clippy` nor `fmt` and can't run the .sh script. It's very little code and I manually tested this, so hopefully that's ok... Let me know if the comment in `state.rs` needs to be updated or the `text_offset` name isn't clear enough. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widgets/text_edit/builder.rs | 13 ++++++------- crates/egui/src/widgets/text_edit/state.rs | 7 ++++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 4df736e34..f46776b7a 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -581,9 +581,8 @@ impl TextEdit<'_> { // TODO(emilk): drag selected text to either move or clone (ctrl on windows, alt on mac) - let singleline_offset = vec2(state.singleline_offset, 0.0); let cursor_at_pointer = - galley.cursor_from_pos(pointer_pos - rect.min + singleline_offset); + galley.cursor_from_pos(pointer_pos - rect.min + state.text_offset); if ui.visuals().text_cursor.preview && response.hovered() @@ -653,16 +652,16 @@ impl TextEdit<'_> { .align_size_within_rect(galley.size(), rect) .intersect(rect) // limit pos to the response rect area .min; - let align_offset = rect.left() - galley_pos.x; + let align_offset = rect.left_top() - galley_pos; // Visual clipping for singleline text editor with text larger than width - if clip_text && align_offset == 0.0 { + if clip_text && align_offset.x == 0.0 { let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) { (Some(cursor_range), true) => galley.pos_from_cursor(cursor_range.primary).min.x, _ => 0.0, }; - let mut offset_x = state.singleline_offset; + let mut offset_x = state.text_offset.x; let visible_range = offset_x..=offset_x + desired_inner_size.x; if !visible_range.contains(&cursor_pos) { @@ -677,10 +676,10 @@ impl TextEdit<'_> { .at_most(galley.size().x - desired_inner_size.x) .at_least(0.0); - state.singleline_offset = offset_x; + state.text_offset = vec2(offset_x, align_offset.y); galley_pos -= vec2(offset_x, 0.0); } else { - state.singleline_offset = align_offset; + state.text_offset = align_offset; } let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) = diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index 11304e700..5827aac4b 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use crate::mutex::Mutex; use crate::{ - Context, Id, + Context, Id, Vec2, text_selection::{CCursorRange, TextCursorState}, }; @@ -49,9 +49,10 @@ pub struct TextEditState { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) ime_cursor_range: CCursorRange, - // Visual offset when editing singleline text bigger than the width. + // Text offset within the widget area. + // Used for sensing and singleline text clipping. #[cfg_attr(feature = "serde", serde(skip))] - pub(crate) singleline_offset: f32, + pub(crate) text_offset: Vec2, /// When did the user last press a key or click on the `TextEdit`. /// Used to pause the cursor animation when typing. From adf463b0a989e350aebaa3628e7bcc6e56f4f3bf Mon Sep 17 00:00:00 2001 From: Sola Date: Tue, 12 Aug 2025 18:28:51 +0800 Subject: [PATCH 170/388] Replace `winapi` with `windows-sys` crate (#7416) This the second take of , adjusted for newer version of `windows-sys`, and with merge conflicts resolved. I added the original author to `Co-authored-by:`. * Closes * Closes * [x] I have followed the instructions in the PR template Tested on Windows 10: image image Action run result: https://github.com/sola-contrib/egui/actions/runs/16748810761 --------- Co-authored-by: Casper Meijn --- Cargo.lock | 1 - crates/eframe/Cargo.toml | 5 +++-- crates/eframe/src/native/app_icon.rs | 33 ++++++++++++++-------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e197450b8..da1de39af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,7 +1242,6 @@ dependencies = [ "web-sys", "web-time", "wgpu", - "winapi", "windows-sys 0.59.0", "winit", ] diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index f1146058d..b95a8eb67 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -192,11 +192,12 @@ objc2-app-kit = { version = "0.2.0", default-features = false, features = [ # windows: [target.'cfg(any(target_os = "windows"))'.dependencies] -winapi = { version = "0.3.9", features = ["winuser"] } windows-sys = { workspace = true, features = [ "Win32_Foundation", - "Win32_UI_Shell", "Win32_System_Com", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", ] } # ------------------------------------------- diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index b94d8f83b..7e04f1df7 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -80,7 +80,11 @@ fn set_title_and_icon(_title: &str, _icon_data: Option<&IconData>) -> AppIconSta #[expect(unsafe_code)] fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { use crate::icon_data::IconDataExt as _; - use winapi::um::winuser; + use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow; + use windows_sys::Win32::UI::WindowsAndMessaging::{ + CreateIconFromResourceEx, GetSystemMetrics, HICON, ICON_BIG, ICON_SMALL, LR_DEFAULTCOLOR, + SM_CXICON, SM_CXSMICON, SendMessageW, WM_SETICON, + }; // We would get fairly far already with winit's `set_window_icon` (which is exposed to eframe) actually! // However, it only sets ICON_SMALL, i.e. doesn't allow us to set a higher resolution icon for the task bar. @@ -92,16 +96,13 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { // * using undocumented SetConsoleIcon method (successfully queried via GetProcAddress) // SAFETY: WinApi function without side-effects. - let window_handle = unsafe { winuser::GetActiveWindow() }; + let window_handle = unsafe { GetActiveWindow() }; if window_handle.is_null() { // The Window isn't available yet. Try again later! return AppIconStatus::NotSetTryAgain; } - fn create_hicon_with_scale( - unscaled_image: &image::RgbaImage, - target_size: i32, - ) -> winapi::shared::windef::HICON { + fn create_hicon_with_scale(unscaled_image: &image::RgbaImage, target_size: i32) -> HICON { let image_scaled = image::imageops::resize( unscaled_image, target_size as _, @@ -127,14 +128,14 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { // SAFETY: Creating an HICON which should be readonly on our data. unsafe { - winuser::CreateIconFromResourceEx( + CreateIconFromResourceEx( image_scaled_bytes.as_mut_ptr(), image_scaled_bytes.len() as u32, 1, // Means this is an icon, not a cursor. 0x00030000, // Version number of the HICON target_size, // Note that this method can scale, but it does so *very* poorly. So let's avoid that! target_size, - winuser::LR_DEFAULTCOLOR, + LR_DEFAULTCOLOR, ) } } @@ -155,7 +156,7 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { // Note that ICON_SMALL may be used even if we don't render a title bar as it may be used in alt+tab! { // SAFETY: WinAPI getter function with no known side effects. - let icon_size_big = unsafe { winuser::GetSystemMetrics(winuser::SM_CXICON) }; + let icon_size_big = unsafe { GetSystemMetrics(SM_CXICON) }; let icon_big = create_hicon_with_scale(&unscaled_image, icon_size_big); if icon_big.is_null() { log::warn!("Failed to create HICON (for big icon) from embedded png data."); @@ -163,10 +164,10 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { } else { // SAFETY: Unsafe WinApi function, takes objects previously created with WinAPI, all checked for null prior. unsafe { - winuser::SendMessageW( + SendMessageW( window_handle, - winuser::WM_SETICON, - winuser::ICON_BIG as usize, + WM_SETICON, + ICON_BIG as usize, icon_big as isize, ); } @@ -174,7 +175,7 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { } { // SAFETY: WinAPI getter function with no known side effects. - let icon_size_small = unsafe { winuser::GetSystemMetrics(winuser::SM_CXSMICON) }; + let icon_size_small = unsafe { GetSystemMetrics(SM_CXSMICON) }; let icon_small = create_hicon_with_scale(&unscaled_image, icon_size_small); if icon_small.is_null() { log::warn!("Failed to create HICON (for small icon) from embedded png data."); @@ -182,10 +183,10 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { } else { // SAFETY: Unsafe WinApi function, takes objects previously created with WinAPI, all checked for null prior. unsafe { - winuser::SendMessageW( + SendMessageW( window_handle, - winuser::WM_SETICON, - winuser::ICON_SMALL as usize, + WM_SETICON, + ICON_SMALL as usize, icon_small as isize, ); } From 42bdf5b88147c548152c8a3db0faba1da53debd3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 12 Aug 2025 12:34:53 +0200 Subject: [PATCH 171/388] Fix link checker CI (#7308) --- lychee.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lychee.toml b/lychee.toml index 05ae0623a..54fc40a85 100644 --- a/lychee.toml +++ b/lychee.toml @@ -38,3 +38,8 @@ accept = [ "200..=299", # Success codes. "429", # Too many requests. This is practically never a sign of a broken link. ] + +# Exclude URLs and mail addresses from checking (supports regex). +exclude = [ + "https://creativecommons.org/.*", # They don't like bots +] From e794de5ffa387b7275462b539f0b4da99f29c0e5 Mon Sep 17 00:00:00 2001 From: Luke M <44519853+lkdm@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:35:45 +1000 Subject: [PATCH 172/388] Document platform compatibility on `viewport::WindowLevel` and dependents (#7432) Documents platform compatibility on the WindowLevel struct and some of the methods that are dependent on it. Provides a link to `winit::window::WindowLevel` documentation section that provides a list of which platforms are supported. * Closes [Issue 7419](https://github.com/emilk/egui/issues/7419) * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/viewport.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index bcbb04a78..1e5f46129 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -630,6 +630,8 @@ impl ViewportBuilder { } /// Control if window is always-on-top, always-on-bottom, or neither. + /// + /// For platform compatibility see [`crate::viewport::WindowLevel`] documentation #[inline] pub fn with_window_level(mut self, level: WindowLevel) -> Self { self.window_level = Some(level); @@ -637,6 +639,8 @@ impl ViewportBuilder { } /// This window is always on top + /// + /// For platform compatibility see [`crate::viewport::WindowLevel`] documentation #[inline] pub fn with_always_on_top(self) -> Self { self.with_window_level(WindowLevel::AlwaysOnTop) @@ -898,6 +902,7 @@ impl ViewportBuilder { } } +/// For winit platform compatibility, see [`winit::WindowLevel` documentation](https://docs.rs/winit/latest/winit/window/enum.WindowLevel.html#platform-specific) #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum WindowLevel { From c2de29a8de76d2c003c4edd2a485624dc0c15076 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 13 Aug 2025 14:56:26 +0200 Subject: [PATCH 173/388] Fix `WidgetText::Text` ignoring fallback font and overrides (#7361) * closes https://github.com/emilk/egui/issues/7356 --- crates/egui/src/atomics/atom.rs | 7 ++++-- crates/egui/src/atomics/atom_kind.rs | 6 +++--- crates/egui/src/atomics/atom_layout.rs | 30 ++++++++++++++++++++++---- crates/egui/src/style.rs | 14 +++++++++++- crates/egui/src/widget_text.rs | 4 +++- crates/egui/src/widgets/button.rs | 8 ++++--- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/crates/egui/src/atomics/atom.rs b/crates/egui/src/atomics/atom.rs index ee5ff30d4..6425ac724 100644 --- a/crates/egui/src/atomics/atom.rs +++ b/crates/egui/src/atomics/atom.rs @@ -1,4 +1,4 @@ -use crate::{AtomKind, Id, SizedAtom, Ui}; +use crate::{AtomKind, FontSelection, Id, SizedAtom, Ui}; use emath::{NumExt as _, Vec2}; use epaint::text::TextWrapMode; @@ -69,6 +69,7 @@ impl<'a> Atom<'a> { ui: &Ui, mut available_size: Vec2, mut wrap_mode: Option, + fallback_font: FontSelection, ) -> SizedAtom<'a> { if !self.shrink && self.max_size.x.is_infinite() { wrap_mode = Some(TextWrapMode::Extend); @@ -81,7 +82,9 @@ impl<'a> Atom<'a> { wrap_mode = Some(TextWrapMode::Truncate); } - let (intrinsic, kind) = self.kind.into_sized(ui, available_size, wrap_mode); + let (intrinsic, kind) = self + .kind + .into_sized(ui, available_size, wrap_mode, fallback_font); let size = self .size diff --git a/crates/egui/src/atomics/atom_kind.rs b/crates/egui/src/atomics/atom_kind.rs index 34cac4ceb..f58b39f8a 100644 --- a/crates/egui/src/atomics/atom_kind.rs +++ b/crates/egui/src/atomics/atom_kind.rs @@ -1,4 +1,4 @@ -use crate::{Id, Image, ImageSource, SizedAtomKind, TextStyle, Ui, WidgetText}; +use crate::{FontSelection, Id, Image, ImageSource, SizedAtomKind, Ui, WidgetText}; use emath::Vec2; use epaint::text::TextWrapMode; @@ -78,12 +78,12 @@ impl<'a> AtomKind<'a> { ui: &Ui, available_size: Vec2, wrap_mode: Option, + fallback_font: FontSelection, ) -> (Vec2, SizedAtomKind<'a>) { match self { AtomKind::Text(text) => { let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); - let galley = - text.into_galley(ui, Some(wrap_mode), available_size.x, TextStyle::Button); + let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font); (galley.intrinsic_size(), SizedAtomKind::Text(galley)) } AtomKind::Image(image) => { diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs index 53819fbb0..1df890250 100644 --- a/crates/egui/src/atomics/atom_layout.rs +++ b/crates/egui/src/atomics/atom_layout.rs @@ -1,7 +1,7 @@ use crate::atomics::ATOMS_SMALL_VEC_SIZE; use crate::{ - AtomKind, Atoms, Frame, Id, Image, IntoAtoms, Response, Sense, SizedAtom, SizedAtomKind, Ui, - Widget, + AtomKind, Atoms, FontSelection, Frame, Id, Image, IntoAtoms, Response, Sense, SizedAtom, + SizedAtomKind, Ui, Widget, }; use emath::{Align2, GuiRounding as _, NumExt as _, Rect, Vec2}; use epaint::text::TextWrapMode; @@ -36,6 +36,7 @@ pub struct AtomLayout<'a> { pub(crate) frame: Frame, pub(crate) sense: Sense, fallback_text_color: Option, + fallback_font: Option, min_size: Vec2, wrap_mode: Option, align2: Option, @@ -56,6 +57,7 @@ impl<'a> AtomLayout<'a> { frame: Frame::default(), sense: Sense::hover(), fallback_text_color: None, + fallback_font: None, min_size: Vec2::ZERO, wrap_mode: None, align2: None, @@ -94,6 +96,13 @@ impl<'a> AtomLayout<'a> { self } + /// Set the fallback (default) font. + #[inline] + pub fn fallback_font(mut self, font: impl Into) -> Self { + self.fallback_font = Some(font.into()); + self + } + /// Set the minimum size of the Widget. /// /// This will find and expand atoms with `grow: true`. @@ -154,8 +163,11 @@ impl<'a> AtomLayout<'a> { min_size, wrap_mode, align2, + fallback_font, } = self; + let fallback_font = fallback_font.unwrap_or_default(); + let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); // If the TextWrapMode is not Extend, ensure there is some item marked as `shrink`. @@ -220,7 +232,12 @@ impl<'a> AtomLayout<'a> { continue; } } - let sized = item.into_sized(ui, available_inner_size, Some(wrap_mode)); + let sized = item.into_sized( + ui, + available_inner_size, + Some(wrap_mode), + fallback_font.clone(), + ); let size = sized.size; desired_width += size.x; @@ -239,7 +256,12 @@ impl<'a> AtomLayout<'a> { available_inner_size.y, ); - let sized = item.into_sized(ui, available_size_for_shrink_item, Some(wrap_mode)); + let sized = item.into_sized( + ui, + available_size_for_shrink_item, + Some(wrap_mode), + fallback_font, + ); let size = sized.size; desired_width += size.x; diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index aa24f472c..ff8043beb 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -121,6 +121,7 @@ impl TextStyle { // ---------------------------------------------------------------------------- /// A way to select [`FontId`], either by picking one directly or by using a [`TextStyle`]. +#[derive(Debug, Clone)] pub enum FontSelection { /// Default text style - will use [`TextStyle::Body`], unless /// [`Style::override_font_id`] or [`Style::override_text_style`] is set. @@ -141,7 +142,18 @@ impl Default for FontSelection { } impl FontSelection { + /// Resolve to a [`FontId`]. + /// + /// On [`Self::Default`] and no override in the style, this will + /// resolve to [`TextStyle::Body`]. pub fn resolve(self, style: &Style) -> FontId { + self.resolve_with_fallback(style, TextStyle::Body.into()) + } + + /// Resolve with a final fallback. + /// + /// Fallback is resolved on [`Self::Default`] and no override in the style. + pub fn resolve_with_fallback(self, style: &Style, fallback: Self) -> FontId { match self { Self::Default => { if let Some(override_font_id) = &style.override_font_id { @@ -149,7 +161,7 @@ impl FontSelection { } else if let Some(text_style) = &style.override_text_style { text_style.resolve(style) } else { - TextStyle::Body.resolve(style) + fallback.resolve(style) } } Self::FontId(font_id) => font_id, diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index bf7cbde34..f28707842 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -749,7 +749,9 @@ impl WidgetText { let mut layout_job = LayoutJob::simple_format( text, TextFormat { - font_id: FontSelection::Default.resolve(style), + // We want the style overrides to take precedence over the fallback font + font_id: FontSelection::default() + .resolve_with_fallback(style, fallback_font), color: crate::Color32::PLACEHOLDER, valign: default_valign, ..Default::default() diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index fef32da38..af31b40af 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -1,7 +1,7 @@ use crate::{ Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame, - Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextWrapMode, Ui, Vec2, Widget, - WidgetInfo, WidgetText, WidgetType, + Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, + Widget, WidgetInfo, WidgetText, WidgetType, }; /// Clickable button with text. @@ -40,7 +40,9 @@ pub struct Button<'a> { impl<'a> Button<'a> { pub fn new(atoms: impl IntoAtoms<'a>) -> Self { Self { - layout: AtomLayout::new(atoms.into_atoms()).sense(Sense::click()), + layout: AtomLayout::new(atoms.into_atoms()) + .sense(Sense::click()) + .fallback_font(TextStyle::Button), fill: None, stroke: None, small: false, From 1937cc4d612cad9b47e36d2377adab5702a65cce Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:40:04 +0200 Subject: [PATCH 174/388] Fix `override_text_color` priority (#7439) The override_text_color is now used when rendering text from a String or &str. This is consistent with the RichText variant and makes the option behave as advertised, taking precedence over WidgetVisuals and overriding the color for all text unless explicitly changed for a single widget (via RichText or LayoutJob). * Closes * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widget_text.rs | 6 +++- crates/egui_kittest/tests/regression_tests.rs | 30 +++++++++++++++++++ .../override_text_color_interactive.png | 3 ++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 crates/egui_kittest/tests/snapshots/override_text_color_interactive.png diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index f28707842..eb0111a76 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -746,13 +746,17 @@ impl WidgetText { ) -> Arc { match self { Self::Text(text) => { + let color = style + .visuals + .override_text_color + .unwrap_or(crate::Color32::PLACEHOLDER); let mut layout_job = LayoutJob::simple_format( text, TextFormat { // We want the style overrides to take precedence over the fallback font font_id: FontSelection::default() .resolve_with_fallback(style, fallback_font), - color: crate::Color32::PLACEHOLDER, + color, valign: default_valign, ..Default::default() }, diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index 0cae152bf..a3b7f6d92 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -157,3 +157,33 @@ pub fn slider_should_move_with_fixed_decimals() { let actual_slider = harness.get_by_role(accesskit::Role::SpinButton); assert_eq!(actual_slider.value(), Some("1.00".to_owned())); } + +#[test] +pub fn override_text_color_affects_interactive_widgets() { + use egui::{Color32, RichText}; + + let mut harness = Harness::new_ui(|ui| { + _ = ui.button("normal"); + _ = ui.checkbox(&mut true, "normal"); + _ = ui.radio(true, "normal"); + ui.visuals_mut().widgets.inactive.fg_stroke.color = Color32::RED; + _ = ui.button("red"); + _ = ui.checkbox(&mut true, "red"); + _ = ui.radio(true, "red"); + // override_text_color takes precedence over `WidgetVisuals`, as it docstring claims + ui.visuals_mut().override_text_color = Some(Color32::GREEN); + _ = ui.button("green"); + _ = ui.checkbox(&mut true, "green"); + _ = ui.radio(true, "green"); + // Setting the color explicitly with `RichText` overrides style + _ = ui.button(RichText::new("blue").color(Color32::BLUE)); + _ = ui.checkbox(&mut true, RichText::new("blue").color(Color32::BLUE)); + _ = ui.radio(true, RichText::new("blue").color(Color32::BLUE)); + }); + + #[cfg(all(feature = "wgpu", feature = "snapshot"))] + let mut results = SnapshotResults::new(); + + #[cfg(all(feature = "wgpu", feature = "snapshot"))] + results.add(harness.try_snapshot("override_text_color_interactive")); +} diff --git a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png new file mode 100644 index 000000000..0db4034a8 --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60540cb1b5b71f100b2ea367a939cb9d93a91e56ff1f14ebfc988bbe79d69ac7 +size 19719 From a7ae1012e52fd3a0d2f5d159edd4075d3c51c18c Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:41:51 +0200 Subject: [PATCH 175/388] Fix debug-panic in ScrollArea if contents fit without scrolling (#7440) If the ScrollArea's contents are smaller than the inner rect, but the scrollbar is set to always visible, clicking on it led to a remap from an empty range to calculate the new offset, which triggered a debug assertion in the remap function, because the result is indeterminate. Since in this case there is no need to scroll, we just skip the remap and set the offset to 0 directly. * Closes * [x] I have followed the instructions in the PR template --- crates/egui/src/containers/scroll_area.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index dbefe018d..3d8bf75f1 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1315,11 +1315,13 @@ impl Prepared { }); let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left; - state.offset[d] = remap( - new_handle_top, - scroll_bar_rect.min[d]..=(scroll_bar_rect.max[d] - handle_rect.size()[d]), - 0.0..=max_offset[d], - ); + let handle_travel = + scroll_bar_rect.min[d]..=(scroll_bar_rect.max[d] - handle_rect.size()[d]); + state.offset[d] = if handle_travel.start() == handle_travel.end() { + 0.0 + } else { + remap(new_handle_top, handle_travel, 0.0..=max_offset[d]) + }; // some manual action taken, scroll not stuck state.scroll_stuck_to_end[d] = false; From 839ee3eaf72831c6973670703b65a13e1342e138 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 14 Aug 2025 12:03:38 +0200 Subject: [PATCH 176/388] Make sure we always track the root viewport (#7450) Some sanity checks I added while working on another bug --- crates/egui/src/context.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index a48de514e..923c1069f 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -770,6 +770,7 @@ impl Default for Context { fn default() -> Self { let ctx_impl = ContextImpl { embed_viewports: true, + viewports: std::iter::once((ViewportId::ROOT, ViewportState::default())).collect(), ..Default::default() }; let ctx = Self(Arc::new(RwLock::new(ctx_impl))); @@ -1617,7 +1618,14 @@ impl Context { self.read(|ctx| { ctx.viewports .get(&id) - .map_or(0, |v| v.repaint.cumulative_frame_nr) + .map(|v| v.repaint.cumulative_frame_nr) + .unwrap_or_else(|| { + if cfg!(debug_assertions) { + panic!("cumulative_frame_nr_for failed to find the viewport {id:?}"); + } else { + 0 + } + }) }) } @@ -2516,6 +2524,10 @@ impl ContextImpl { self.last_viewport = ended_viewport_id; self.viewports.retain(|&id, viewport| { + if id == ViewportId::ROOT { + return true; // never remove the root + } + let parent = *self.viewport_parents.entry(id).or_default(); if !all_viewport_ids.contains(&parent) { @@ -2586,6 +2598,10 @@ impl ContextImpl { if is_last { // Remove dead viewports: self.viewports.retain(|id, _| all_viewport_ids.contains(id)); + debug_assert!( + self.viewports.contains_key(&ViewportId::ROOT), + "Bug in egui: we removed the root viewport" + ); self.viewport_parents .retain(|id, _| all_viewport_ids.contains(id)); } else { From b24a56d3f7dbde53812f465c18868fe39cd7f84b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 15 Aug 2025 12:21:51 +0200 Subject: [PATCH 177/388] Only update snapshot if we didn't pass (#7455) * Closes https://github.com/emilk/egui/issues/7449 --- crates/egui_kittest/src/snapshot.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 6f565166f..fea8ab8b6 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -421,13 +421,13 @@ fn try_image_snapshot_options_impl( err, })?; + if num_wrong_pixels as i64 <= *failed_pixel_count_threshold as i64 { + return Ok(()); + } + 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, diff: num_wrong_pixels, From 6a355c3808dbb2a2acdb65bcd33d65252762156c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 15 Aug 2025 13:29:55 +0200 Subject: [PATCH 178/388] Add 0.32.1 to changelogs --- CHANGELOG.md | 12 ++++++++++++ crates/ecolor/CHANGELOG.md | 4 ++++ crates/eframe/CHANGELOG.md | 5 +++++ crates/egui-wgpu/CHANGELOG.md | 4 ++++ crates/egui-winit/CHANGELOG.md | 4 ++++ crates/egui_extras/CHANGELOG.md | 4 ++++ crates/egui_glow/CHANGELOG.md | 4 ++++ crates/egui_kittest/CHANGELOG.md | 4 ++++ crates/epaint/CHANGELOG.md | 5 +++++ crates/epaint_default_fonts/CHANGELOG.md | 4 ++++ 10 files changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 385550843..c2438ec9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,18 @@ 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.1 - 2025-08-15 - Misc bug fixes +### ⭐ Added +* Add `ComboBox::popup_style` [#7360](https://github.com/emilk/egui/pull/7360) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🐛 Fixed +* Fix glyph rendering: clamp coverage to [0, 1] [#7415](https://github.com/emilk/egui/pull/7415) by [@emilk](https://github.com/emilk) +* Fix manual `Popup` not closing [#7383](https://github.com/emilk/egui/pull/7383) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix `WidgetText::Text` ignoring fallback font and overrides [#7361](https://github.com/emilk/egui/pull/7361) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix `override_text_color` priority [#7439](https://github.com/emilk/egui/pull/7439) by [@YgorSouza](https://github.com/YgorSouza) +* Fix debug-panic in ScrollArea if contents fit without scrolling [#7440](https://github.com/emilk/egui/pull/7440) by [@YgorSouza](https://github.com/YgorSouza) + + ## 0.32.0 - 2025-07-10 - Atoms, popups, and better SVG support This is a big egui release, with several exciting new features! diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 535f8b18e..c73787de7 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,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.1 - 2025-08-15 +Nothing new + + ## 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) diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index aa0b3436c..b6dcb47d6 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,11 @@ 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.1 - 2025-08-15 +* Enable wgpu default features in eframe / egui_wgpu default features [#7344](https://github.com/emilk/egui/pull/7344) by [@lucasmerlin](https://github.com/lucasmerlin) +* Request a redraw when the url change through the `popstate` event listener [#7403](https://github.com/emilk/egui/pull/7403) by [@irevoire](https://github.com/irevoire) + + ## 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) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index eda975de2..476485c55 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,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.1 - 2025-08-15 +* Enable wgpu default features in eframe / egui_wgpu default features [#7344](https://github.com/emilk/egui/pull/7344) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 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) diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index da51386e9..70eeb8c2a 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/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.1 - 2025-08-15 +* Update to winit 0.30.12 [#7420](https://github.com/emilk/egui/pull/7420) by [@emilk](https://github.com/emilk) + + ## 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) diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 2c2ae8ba4..d0f4f8ec6 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/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.1 - 2025-08-15 +Nothing new + + ## 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) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 4969b5569..466898a80 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.1 - 2025-08-15 +* Fix `UPDATE_SNAPSHOTS`: only update if we didn't pass the test [#7455](https://github.com/emilk/egui/pull/7455) by [@emilk](https://github.com/emilk) + + ## 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) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index bff5336a4..4b28cea06 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,11 @@ 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.1 - 2025-08-15 +* Fix multi-line `TextShape` rotation [#7404](https://github.com/emilk/egui/pull/7404) by [@afishhh](https://github.com/afishhh) +* Fix glyph rendering: clamp coverage to [0, 1] [#7415](https://github.com/emilk/egui/pull/7415) by [@emilk](https://github.com/emilk) + + ## 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) diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index f61b14cda..4642118f6 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.1 - 2025-08-15 +Nothing new + + ## 0.32.0 - 2025-07-10 Nothing new From 7c5798289dd9217ad105b800db1dba5248ed43a2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 15 Aug 2025 13:33:47 +0200 Subject: [PATCH 179/388] Bump version numbers to 0.32.1 --- Cargo.lock | 32 ++++++++++++++++---------------- Cargo.toml | 26 +++++++++++++------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da1de39af..77af28068 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1197,7 +1197,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.32.0" +version = "0.32.1" dependencies = [ "bytemuck", "cint", @@ -1209,7 +1209,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.32.0" +version = "0.32.1" dependencies = [ "ahash", "bytemuck", @@ -1248,7 +1248,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.32.0" +version = "0.32.1" dependencies = [ "accesskit", "ahash", @@ -1268,7 +1268,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.32.0" +version = "0.32.1" dependencies = [ "ahash", "bytemuck", @@ -1286,7 +1286,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.32.0" +version = "0.32.1" dependencies = [ "accesskit_winit", "arboard", @@ -1306,7 +1306,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.32.0" +version = "0.32.1" dependencies = [ "bytemuck", "chrono", @@ -1334,7 +1334,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.32.0" +version = "0.32.1" dependencies = [ "chrono", "criterion", @@ -1351,7 +1351,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.32.0" +version = "0.32.1" dependencies = [ "ahash", "chrono", @@ -1370,7 +1370,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.32.0" +version = "0.32.1" dependencies = [ "bytemuck", "document-features", @@ -1389,7 +1389,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.32.0" +version = "0.32.1" dependencies = [ "dify", "document-features", @@ -1405,7 +1405,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.32.0" +version = "0.32.1" dependencies = [ "egui", "egui_extras", @@ -1435,7 +1435,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.32.0" +version = "0.32.1" dependencies = [ "bytemuck", "document-features", @@ -1532,7 +1532,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.32.0" +version = "0.32.1" dependencies = [ "ab_glyph", "ahash", @@ -1555,7 +1555,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.32.0" +version = "0.32.1" [[package]] name = "equivalent" @@ -3239,7 +3239,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.32.0" +version = "0.32.1" dependencies = [ "eframe", "env_logger", @@ -5506,7 +5506,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.32.0" +version = "0.32.1" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index d29449711..34a98e332 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.32.0" +version = "0.32.1" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -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 } +emath = { version = "0.32.1", path = "crates/emath", default-features = false } +ecolor = { version = "0.32.1", path = "crates/ecolor", default-features = false } +epaint = { version = "0.32.1", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.32.1", path = "crates/epaint_default_fonts" } +egui = { version = "0.32.1", path = "crates/egui", default-features = false } +egui-winit = { version = "0.32.1", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.32.1", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.32.1", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.32.1", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.32.1", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.32.1", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.32.1", path = "crates/eframe", default-features = false } accesskit = "0.19.0" accesskit_winit = "0.27" From 1da1d57c111e14ed89516c9767b5a42c7a7d15a4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 21 Aug 2025 15:31:56 +0200 Subject: [PATCH 180/388] Panic mutexes that can't lock for 30 seconds, in debug builds (#7468) I'm trying to debug a suspected deadlock in the CI for https://github.com/emilk/egui/pull/7467 Since we use our own mutex wrappers, we can just panic if the lock is too slow. Ugly and effective :) --- crates/epaint/src/mutex.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 465722c17..ef1b3a6c4 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -23,7 +23,13 @@ mod mutex_impl { #[inline(always)] pub fn lock(&self) -> MutexGuard<'_, T> { - self.0.lock() + if cfg!(debug_assertions) { + self.0 + .try_lock_for(std::time::Duration::from_secs(30)) + .expect("Looks like a deadlock!") + } else { + self.0.lock() + } } } } From 531ead5ad1a84010fccdb06e8a2d1e1011774986 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 21 Aug 2025 15:38:41 +0200 Subject: [PATCH 181/388] Update MSRV to 1.86 (#7469) --- .github/workflows/cargo_machete.yml | 3 +-- .github/workflows/deploy_web_demo.yml | 2 +- .github/workflows/preview_build.yml | 2 +- .github/workflows/rust.yml | 14 +++++++------- Cargo.toml | 2 +- clippy.toml | 2 +- crates/egui/src/lib.rs | 2 +- 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, 42 insertions(+), 43 deletions(-) diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml index b6f472504..53eb51123 100644 --- a/.github/workflows/cargo_machete.yml +++ b/.github/workflows/cargo_machete.yml @@ -8,11 +8,10 @@ jobs: steps: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.85 + toolchain: 1.86 - name: Machete install run: cargo install cargo-machete --locked - name: Checkout uses: actions/checkout@v4 - name: Machete Check run: cargo machete - diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index 278549e1e..a122914a0 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.85.0 + toolchain: 1.86.0 override: true - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 437108ab6..b50d866df 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.85.0 + toolchain: 1.86.0 targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fece7f88d..3698dcc22 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.85.0 + toolchain: 1.86.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.85.0 + toolchain: 1.86.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.85.0" + rust-version: "1.86.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -170,7 +170,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.85.0 + toolchain: 1.86.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -191,7 +191,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.85.0 + toolchain: 1.86.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.85.0 + toolchain: 1.86.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.85.0 + toolchain: 1.86.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index 34a98e332..6891471e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ [workspace.package] edition = "2024" license = "MIT OR Apache-2.0" -rust-version = "1.85" +rust-version = "1.86" version = "0.32.1" diff --git a/clippy.toml b/clippy.toml index 0f40ef38d..e988cb677 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.85" +msrv = "1.86" allow-unwrap-in-tests = true diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index c2908df19..3739cd8a8 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.85.0 or later to use `egui`. +//! You need to have rust 1.86.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/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index d7de7866c..a44ab868f 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 1d78565ec..7cef1f343 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index 86ce17bfb..3a2124b18 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index b3cd82d2a..4c44d5988 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index de66772da..76c8b5a65 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index baae7bfc1..a079dfa16 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index 74f07237e..ccb6ef73d 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/external_eventloop/Cargo.toml b/examples/external_eventloop/Cargo.toml index e14852930..26eac7210 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/external_eventloop_async/Cargo.toml b/examples/external_eventloop_async/Cargo.toml index 5305bdbb3..418008cca 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index bf437a396..17bfb754d 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index 6e077c74f..d5ebd01f0 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" 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 4a5779b8c..a3d02778a 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index ee3cb7c84..e3976cdff 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index d5ce91625..3256332c5 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index 01b63625c..604573d28 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 2ae7b0731..3639cfe20 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index a672ea0db..493e1a218 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 536f6ae72..b068d4a39 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [package.metadata.cargo-machete] diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index d633159cc..ee2ef0bd9 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] license = "MIT OR Apache-2.0" edition = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index 800600c7c..c9dbc7730 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 24d362e8c..6d502dcd8 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/rust-toolchain b/rust-toolchain index 71db6497f..2fda45d24 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.85.0" +channel = "1.86.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/check.sh b/scripts/check.sh index 0207df1cc..68120b78b 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.85.0 install --quiet typos-cli +# cargo +1.86.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 e91b18abc..fbe946e8c 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.85" +msrv = "1.86" allow-unwrap-in-tests = true diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index 6058a1865..1bf772c56 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index 5171700ea..6cb294179 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml index 6dbdfe1c0..2a6c73be7 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/tests/test_ui_stack/Cargo.toml b/tests/test_ui_stack/Cargo.toml index a826324a0..65d808a0d 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] diff --git a/tests/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml index 9a64b485d..eca0cda80 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 = "2024" -rust-version = "1.85" +rust-version = "1.86" publish = false [lints] From 5453cceded370d136261001f13f07d18be4b137b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 21 Aug 2025 15:42:10 +0200 Subject: [PATCH 182/388] Use official cargo machete action (#7470) --- .github/workflows/cargo_machete.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml index 53eb51123..b67eadc0e 100644 --- a/.github/workflows/cargo_machete.yml +++ b/.github/workflows/cargo_machete.yml @@ -10,7 +10,8 @@ jobs: with: toolchain: 1.86 - name: Machete install - run: cargo install cargo-machete --locked + ## The official cargo-machete action + uses: bnjbvr/cargo-machete@main - name: Checkout uses: actions/checkout@v4 - name: Machete Check From bf981b8d3e36b15a0c7784f798b23d86da702086 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 21 Aug 2025 17:45:34 +0200 Subject: [PATCH 183/388] Add `SurrenderFocusOn` option (#7471) * [X] I have followed the instructions in the PR template On touch devices you don't want the keyboard to disappear when scrolling, so this PR adds a `SurrenderFocusOn` enum to configure on what interaction to surrender focus. --- crates/egui/src/context.rs | 14 ++++++++--- crates/egui/src/input_state/mod.rs | 40 ++++++++++++++++++++++++++++++ crates/egui/src/lib.rs | 2 +- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 923c1069f..8dbbc2c1d 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -38,10 +38,10 @@ use crate::{ viewport::ViewportClass, }; +use self::{hit_test::WidgetHits, interaction::InteractionSnapshot}; #[cfg(feature = "accesskit")] use crate::IdMap; - -use self::{hit_test::WidgetHits, interaction::InteractionSnapshot}; +use crate::input_state::SurrenderFocusOn; /// Information given to the backend about when it is time to repaint the ui. /// @@ -1430,8 +1430,14 @@ impl Context { res.flags.set(Flags::HOVERED, false); } - let pointer_pressed_elsewhere = any_press && !res.hovered(); - if pointer_pressed_elsewhere && memory.has_focus(id) { + let should_surrender_focus = match memory.options.input_options.surrender_focus_on { + SurrenderFocusOn::Presses => any_press, + SurrenderFocusOn::Clicks => input.pointer.any_click(), + SurrenderFocusOn::Never => false, + }; + + let pointer_clicked_elsewhere = should_surrender_focus && !res.hovered(); + if pointer_clicked_elsewhere && memory.has_focus(id) { memory.surrender_focus(id); } }); diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index a3ebf532e..bfa20d6c8 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -17,6 +17,37 @@ pub use crate::Key; pub use touch_state::MultiTouchInfo; use touch_state::TouchState; +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum SurrenderFocusOn { + /// Surrender focus if the user _presses_ somewhere outside the focused widget. + Presses, + + /// Surrender focus if the user _clicks_ somewhere outside the focused widget. + #[default] + Clicks, + + /// Never surrender focus. + Never, +} + +impl SurrenderFocusOn { + pub fn ui(&mut self, ui: &mut crate::Ui) { + ui.horizontal(|ui| { + ui.selectable_value(self, Self::Presses, "Presses") + .on_hover_text( + "Surrender focus if the user presses somewhere outside the focused widget.", + ); + ui.selectable_value(self, Self::Clicks, "Clicks") + .on_hover_text( + "Surrender focus if the user clicks somewhere outside the focused widget.", + ); + ui.selectable_value(self, Self::Never, "Never") + .on_hover_text("Never surrender focus."); + }); + } +} + /// Options for input state handling. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -58,6 +89,9 @@ pub struct InputOptions { /// and when combined with [`Self::zoom_modifier`] it will result in zooming /// on only the vertical axis. pub vertical_scroll_modifier: Modifiers, + + /// When should we surrender focus from the focused widget? + pub surrender_focus_on: SurrenderFocusOn, } impl Default for InputOptions { @@ -79,6 +113,7 @@ impl Default for InputOptions { zoom_modifier: Modifiers::COMMAND, horizontal_scroll_modifier: Modifiers::SHIFT, vertical_scroll_modifier: Modifiers::ALT, + surrender_focus_on: SurrenderFocusOn::default(), } } } @@ -95,6 +130,7 @@ impl InputOptions { zoom_modifier, horizontal_scroll_modifier, vertical_scroll_modifier, + surrender_focus_on, } = self; crate::Grid::new("InputOptions") .num_columns(2) @@ -155,6 +191,10 @@ impl InputOptions { vertical_scroll_modifier.ui(ui); ui.end_row(); + ui.label("surrender_focus_on"); + surrender_focus_on.ui(ui); + ui.end_row(); + }); } } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 3739cd8a8..1fc5f6ca7 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -495,7 +495,7 @@ pub use self::{ epaint::text::TextWrapMode, grid::Grid, id::{Id, IdMap}, - input_state::{InputOptions, InputState, MultiTouchInfo, PointerState}, + input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn}, layers::{LayerId, Order}, layout::*, load::SizeHint, From 42c2fc58c90af92f19cc6bbda4b75ce7a290c0aa Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 21 Aug 2025 17:46:36 +0200 Subject: [PATCH 184/388] Allow masking widgets in kittest snapshots (#7467) * Closes * [ ] I have followed the instructions in the PR template --- crates/egui_kittest/src/lib.rs | 15 ++++++++++++- .../tests/snapshots/test_masking.png | 3 +++ crates/egui_kittest/tests/tests.rs | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 crates/egui_kittest/tests/snapshots/test_masking.png diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 6adefe53b..ae4d42f4b 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -28,8 +28,9 @@ pub use builder::*; pub use node::*; pub use renderer::*; +use egui::epaint::{ClippedShape, RectShape}; use egui::style::ScrollAnimation; -use egui::{Key, Modifiers, Pos2, Rect, RepaintCause, Vec2, ViewportId}; +use egui::{Color32, Key, Modifiers, Pos2, Rect, RepaintCause, Shape, Vec2, ViewportId}; use kittest::Queryable; #[derive(Debug, Clone)] @@ -556,6 +557,18 @@ impl<'a, State> Harness<'a, State> { self.key_combination_modifiers(modifiers, &[key]); } + /// Mask something. Useful for snapshot tests. + /// + /// Call this _after_ [`Self::run`] and before [`Self::snapshot`]. + /// This will add a [`RectShape`] to the output shapes, for the current frame. + /// Will be overwritten on the next call to [`Self::run`]. + pub fn mask(&mut self, rect: Rect) { + self.output.shapes.push(ClippedShape { + clip_rect: Rect::EVERYTHING, + shape: Shape::Rect(RectShape::filled(rect, 0.0, Color32::MAGENTA)), + }); + } + /// Render the last output to an image. /// /// # Errors diff --git a/crates/egui_kittest/tests/snapshots/test_masking.png b/crates/egui_kittest/tests/snapshots/test_masking.png new file mode 100644 index 000000000..932e9d5e4 --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/test_masking.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdb0c0b955e3d3773a00afa61d4c44999de447aa71dd737181563101f09f5ac9 +size 5444 diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index 8ff6584ac..e18e21761 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -138,3 +138,24 @@ fn test_scroll_down() { "The button was not clicked after scrolling down. (Probably not scrolled enough / at all)" ); } + +#[test] +fn test_masking() { + let mut harness = Harness::new_ui(|ui| { + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis(); + + ui.label("I should not be masked."); + ui.label(format!("Timestamp: {timestamp}")); + ui.label("I should also not be masked."); + }); + + harness.fit_contents(); + + let to_be_masked = harness.get_by_label_contains("Timestamp: "); + harness.mask(to_be_masked.rect()); + + harness.snapshot("test_masking"); +} From 454af7aa6c1e42abb6b162ea02843561695dc91c Mon Sep 17 00:00:00 2001 From: ozwaldorf Date: Sun, 24 Aug 2025 08:43:31 -0400 Subject: [PATCH 185/388] fix: `SubMenu` should not display when ui is disabled (#7428) Adds a check for `ui.is_enabled()` when displaying the SubMenu items. There were a few places the condition could be placed, but I figured there was simplest. The inner button will already not be able to be clicked due to ui being disabled. cc @lucasmerlin * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/containers/menu.rs | 4 +++- crates/egui_demo_lib/src/demo/popups.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index 1e2cbfa2e..c83b5297d 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -446,7 +446,9 @@ impl SubMenu { let is_hovered = hover_pos.is_some_and(|pos| button_rect.contains(pos)); // The clicked handler is there for accessibility (keyboard navigation) - if (!is_any_open && is_hovered) || button_response.clicked() { + let should_open = + ui.is_enabled() && (button_response.clicked() || (is_hovered && !is_any_open)); + if should_open { set_open = Some(true); is_open = true; // Ensure that all other sub menus are closed when we open the menu diff --git a/crates/egui_demo_lib/src/demo/popups.rs b/crates/egui_demo_lib/src/demo/popups.rs index 4574d99e2..9998dd59f 100644 --- a/crates/egui_demo_lib/src/demo/popups.rs +++ b/crates/egui_demo_lib/src/demo/popups.rs @@ -53,6 +53,9 @@ impl PopupsDemo { ui.close(); } }); + ui.add_enabled_ui(false, |ui| { + ui.menu_button("SubMenus can be disabled", |_| {}); + }); ui.menu_image_text_button( include_image!("../../data/icon.png"), "I have an icon!", From a5a51ced0f8fc7612dfce9b4b8432883b671868b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 24 Aug 2025 15:03:57 +0200 Subject: [PATCH 186/388] Improve egui-winit profile scope --- crates/egui-winit/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 688d452c2..6d0604cfd 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1347,7 +1347,7 @@ fn process_viewport_command( info: &mut ViewportInfo, actions_requested: &mut Vec, ) { - profiling::function_scope!(); + profiling::function_scope!(format!("{command:?}")); use winit::window::ResizeDirection; From 608de4a264d3bd0b6c26d432b5a35c122e5b6f9c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 24 Aug 2025 16:27:21 +0200 Subject: [PATCH 187/388] Update bytemuck (#7475) --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77af28068..08dd3a451 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,18 +638,18 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 6891471e5..65489aa66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ ahash = { version = "0.8.11", default-features = false, features = [ ] } backtrace = "0.3" bitflags = "2.6" -bytemuck = "1.7.2" +bytemuck = "1.23.2" criterion = { version = "0.5.1", default-features = false } dify = { version = "0.7", default-features = false } document-features = "0.2.10" From 0fad7d85037d01afd301cb387796ba081d340e10 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 24 Aug 2025 16:27:28 +0200 Subject: [PATCH 188/388] Enable more clippy lints (#7474) --- Cargo.toml | 8 ++++---- crates/eframe/src/web/events.rs | 2 +- crates/eframe/src/web/text_agent.rs | 4 ++-- crates/eframe/src/web/web_painter_wgpu.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65489aa66..9cb793d0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -233,12 +233,14 @@ needless_for_each = "warn" needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" negative_feature_names = "warn" +non_std_lazy_statics = "warn" non_zero_suggestions = "warn" nonstandard_macro_braces = "warn" option_as_ref_cloned = "warn" option_option = "warn" path_buf_push_overwrite = "warn" pathbuf_init_then_push = "warn" +precedence_bits = "warn" print_stderr = "warn" print_stdout = "warn" ptr_as_ptr = "warn" @@ -252,6 +254,7 @@ ref_as_ptr = "warn" ref_option_ref = "warn" ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" +return_and_then = "warn" same_functions_in_if_condition = "warn" semicolon_if_nothing_returned = "warn" set_contains_or_insert = "warn" @@ -282,6 +285,7 @@ unnecessary_literal_bound = "warn" unnecessary_safety_comment = "warn" unnecessary_safety_doc = "warn" unnecessary_self_imports = "warn" +unnecessary_semicolon = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" @@ -301,12 +305,8 @@ zero_sized_map_values = "warn" # elidable_lifetime_names = "warn" # ignore_without_reason = "warn" # manual_midpoint = "warn" # NOTE `midpoint` is often a lot slower for floats, so we have our own `emath::fast_midpoint` function. -# non_std_lazy_statics = "warn" -# precedence_bits = "warn" -# return_and_then = "warn" # single_option_map = "warn" # unnecessary_debug_formatting = "warn" -# unnecessary_semicolon = "warn" # TODO(emilk): maybe enable more of these lints? diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 73c3705a4..1a79937b0 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -997,7 +997,7 @@ impl ResizeObserverContext { // we rely on the resize observer to trigger the first `request_animation_frame`: if let Err(err) = runner_ref.request_animation_frame() { log::error!("{}", super::string_from_js_value(&err)); - }; + } } else { log::warn!("ResizeObserverContext callback: failed to lock runner"); } diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index 42d6efdc3..ac917329f 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -179,7 +179,7 @@ impl TextAgent { if let Err(err) = self.input.focus() { log::error!("failed to set focus: {}", super::string_from_js_value(&err)); - }; + } } pub fn blur(&self) { @@ -191,7 +191,7 @@ impl TextAgent { if let Err(err) = self.input.blur() { log::error!("failed to set focus: {}", super::string_from_js_value(&err)); - }; + } } } diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 735c94d73..7b83faa0b 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -274,7 +274,7 @@ impl WebPainter for WebPainterWgpu { &mut encoder, )); } - }; + } Some((output_frame, capture_buffer)) }; From 2957fd56a5a7b85bfe6360d1800e14eb97bf45bd Mon Sep 17 00:00:00 2001 From: Sergey Ukolov Date: Sun, 24 Aug 2025 17:40:44 +0300 Subject: [PATCH 189/388] Fix: use unique id for resize columns in `Table` (#7414) Currently the IDs for resize columns in Table are based on the ID of parent. When placing multiple tables within the same parent the ID clash for resize columns occurs despite the [TableBuilder::id_salt](https://docs.rs/egui_extras/0.32.0/egui_extras/struct.TableBuilder.html#method.id_salt) is being used. --- crates/egui_extras/src/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 9d77c60c8..385e4197d 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -851,7 +851,7 @@ impl Table<'_> { if column.is_auto() && (is_sizing_pass || !column_is_resizable) { *column_width = width_range.clamp(max_used_widths[i]); } else if column_is_resizable { - let column_resize_id = ui.id().with("resize_column").with(i); + let column_resize_id = state_id.with("resize_column").with(i); let mut p0 = egui::pos2(x, table_top); let mut p1 = egui::pos2(x, bottom); From 0a81372cfd3a4deda640acdecbbaf24bf78bb6a2 Mon Sep 17 00:00:00 2001 From: n4n5 Date: Sun, 24 Aug 2025 16:41:10 +0200 Subject: [PATCH 190/388] Add theme selection for code editor (#7375) Change the theme selection of the code editor Before - was showing the window theme selector After - is showing the code editor theme selector image * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui_extras/src/syntax_highlighting.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 894f2cc24..c08c69750 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -333,9 +333,19 @@ impl CodeTheme { } } + pub fn is_dark(&self) -> bool { + self.dark_mode + } + /// Show UI for changing the color theme. pub fn ui(&mut self, ui: &mut egui::Ui) { - egui::widgets::global_theme_preference_buttons(ui); + ui.horizontal(|ui| { + ui.selectable_value(&mut self.dark_mode, true, "🌙 Dark theme") + .on_hover_text("Use the dark mode theme"); + + ui.selectable_value(&mut self.dark_mode, false, "☀ Light theme") + .on_hover_text("Use the light mode theme"); + }); for theme in SyntectTheme::all() { if theme.is_dark() == self.dark_mode { From ab8dcee7e716279b5fcc923128b6f2e68e13ee09 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 3 Sep 2025 12:55:02 +0200 Subject: [PATCH 191/388] Fix egui_demo_app missing wgpu backends (#7492) * This was broken in https://github.com/emilk/egui/pull/7344 --- crates/egui_demo_app/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 5868ed481..f4c6e2864 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -69,7 +69,8 @@ bytemuck = { workspace = true, optional = true } puffin = { workspace = true, optional = true } puffin_http = { workspace = true, optional = true } # Enable both WebGL & WebGPU when targeting the web (these features have no effect when not targeting wasm32) -wgpu = { workspace = true, features = ["webgpu", "webgl"], optional = true } +# Also enable the default features so we have a supported backend for every platform. +wgpu = { workspace = true, features = ["default", "webgpu", "webgl"], optional = true } # feature "http": From 01ee9c72d5c8cf48dc9da350835f6264aeace8a5 Mon Sep 17 00:00:00 2001 From: Dot32 <61964090+Dot32Dev@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:18:53 +0800 Subject: [PATCH 192/388] Fix typo in the description of the `id` method in `ui.rs` (#7457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [x] I have followed the instructions in the PR template The description of the id method in the Ui struct incorrectly wrote "where" instead of "were". Screenshot 2025-08-15 at 7 03 27 pm This PR fixes that typo. --- crates/egui/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 1371f1a7c..67d210b20 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -377,7 +377,7 @@ impl Ui { /// /// However, it is not necessarily globally unique. /// For instance, sibling `Ui`s share the same [`Self::id`] - /// unless they where explicitly given different id salts using + /// unless they were explicitly given different id salts using /// [`UiBuilder::id_salt`]. #[inline] pub fn id(&self) -> Id { From b9414bd4cc50712fff46debc8e53a76f24e422a4 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Thu, 4 Sep 2025 09:42:46 +0200 Subject: [PATCH 193/388] Selectively update dependencies to reduce total number (#7488) * [x] I have followed the instructions in the PR template Update some of the core dependencies and run cargo update for selected dependencies to remove total number and older versions. --- Cargo.lock | 517 ++++++++++++++--------------- Cargo.toml | 6 +- crates/eframe/Cargo.toml | 2 +- crates/epaint/benches/benchmark.rs | 4 +- 4 files changed, 252 insertions(+), 277 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08dd3a451..7497726f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,7 +108,7 @@ dependencies = [ "accesskit_macos", "accesskit_unix", "accesskit_windows", - "raw-window-handle 0.6.2", + "raw-window-handle", "winit", ] @@ -138,7 +138,7 @@ dependencies = [ "once_cell", "serde", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -229,19 +229,21 @@ checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" [[package]] name = "arboard" -version = "3.4.1" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ "clipboard-win", - "core-graphics", "image", "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.0", "parking_lot", - "windows-sys 0.48.0", + "percent-encoding", + "windows-sys 0.60.2", "x11rb", ] @@ -284,7 +286,7 @@ dependencies = [ "futures-channel", "futures-util", "rand", - "raw-window-handle 0.6.2", + "raw-window-handle", "serde", "serde_repr", "url", @@ -850,12 +852,12 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -957,23 +959,20 @@ dependencies = [ [[package]] name = "criterion" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -981,12 +980,12 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", - "itertools", + "itertools 0.13.0", ] [[package]] @@ -1117,23 +1116,23 @@ dependencies = [ [[package]] name = "directories" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] @@ -1233,7 +1232,7 @@ dependencies = [ "percent-encoding", "pollster", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "ron", "serde", "static_assertions", @@ -1242,7 +1241,7 @@ dependencies = [ "web-sys", "web-time", "wgpu", - "windows-sys 0.59.0", + "windows-sys 0.60.2", "winit", ] @@ -1295,7 +1294,7 @@ dependencies = [ "egui", "log", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "serde", "smithay-clipboard", "wayland-cursor", @@ -1570,7 +1569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1809,12 +1808,12 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.4.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "libc", - "windows-targets 0.48.5", + "rustix 1.0.8", + "windows-targets 0.52.6", ] [[package]] @@ -1907,7 +1906,7 @@ dependencies = [ "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "once_cell", - "raw-window-handle 0.6.2", + "raw-window-handle", "wayland-sys", "windows-sys 0.52.0", "x11-dl", @@ -1921,7 +1920,7 @@ checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" dependencies = [ "cfg_aliases", "glutin", - "raw-window-handle 0.6.2", + "raw-window-handle", "winit", ] @@ -2104,16 +2103,17 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.0", ] [[package]] @@ -2127,21 +2127,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -2150,31 +2151,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -2182,67 +2163,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" version = "1.0.3" @@ -2256,9 +2224,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -2338,6 +2306,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2506,9 +2483,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" @@ -2682,7 +2659,7 @@ dependencies = [ "log", "ndk-sys 0.6.0+11769913", "num_enum", - "raw-window-handle 0.6.2", + "raw-window-handle", "thiserror 1.0.66", ] @@ -2825,6 +2802,7 @@ dependencies = [ "bitflags 2.9.0", "block2 0.6.0", "objc2 0.6.0", + "objc2-core-graphics", "objc2-foundation 0.3.0", ] @@ -2874,6 +2852,18 @@ dependencies = [ "objc2 0.6.0", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -2928,6 +2918,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -3251,6 +3252,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3263,7 +3273,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -3320,7 +3330,7 @@ dependencies = [ "bincode", "byteorder", "cfg-if", - "itertools", + "itertools 0.10.5", "lz4_flex", "once_cell", "parking_lot", @@ -3397,13 +3407,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", - "zerocopy 0.8.23", ] [[package]] @@ -3431,12 +3440,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3483,13 +3486,13 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.66", + "thiserror 2.0.11", ] [[package]] @@ -3557,7 +3560,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation 0.3.0", "pollster", - "raw-window-handle 0.6.2", + "raw-window-handle", "urlencoding", "wasm-bindgen", "wasm-bindgen-futures", @@ -3590,9 +3593,9 @@ dependencies = [ [[package]] name = "ron" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" +checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64", "bitflags 2.9.0", @@ -3642,7 +3645,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4222,9 +4225,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -4277,13 +4280,13 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "toml_datetime", - "winnow 0.6.20", + "winnow", ] [[package]] @@ -4487,12 +4490,6 @@ dependencies = [ "xmlwriter", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4728,17 +4725,18 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b6f804e41d0852e16d2eaee61c7e4f7d3e8ffdb7b8ed85886aeb0791fe9fcd" +checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e" dependencies = [ + "block2 0.5.1", "core-foundation", "home", "jni", "log", "ndk-context", - "objc", - "raw-window-handle 0.5.2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "url", "web-sys", ] @@ -4775,7 +4773,7 @@ dependencies = [ "parking_lot", "portable-atomic", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "smallvec", "static_assertions", "wasm-bindgen", @@ -4806,7 +4804,7 @@ dependencies = [ "parking_lot", "portable-atomic", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "rustc-hash", "smallvec", "thiserror 2.0.11", @@ -4890,7 +4888,7 @@ dependencies = [ "portable-atomic", "profiling", "range-alloc", - "raw-window-handle 0.6.2", + "raw-window-handle", "renderdoc-sys", "smallvec", "thiserror 2.0.11", @@ -4937,7 +4935,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4978,15 +4976,6 @@ dependencies = [ "windows-core 0.61.0", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.58.0" @@ -5069,9 +5058,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -5129,15 +5118,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -5156,6 +5136,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5171,21 +5160,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -5195,145 +5169,168 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winit" version = "0.30.12" @@ -5364,7 +5361,7 @@ dependencies = [ "orbclient", "percent-encoding", "pin-project", - "raw-window-handle 0.6.2", + "raw-window-handle", "redox_syscall 0.4.1", "rustix 0.38.38", "sctk-adwaita", @@ -5386,15 +5383,6 @@ dependencies = [ "xkbcommon-dl", ] -[[package]] -name = "winnow" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.3" @@ -5413,17 +5401,11 @@ dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "x11-dl" @@ -5438,24 +5420,24 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", "libloading", "once_cell", - "rustix 0.38.38", + "rustix 1.0.8", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" @@ -5519,9 +5501,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -5531,9 +5513,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -5570,7 +5552,7 @@ dependencies = [ "tracing", "uds_windows", "windows-sys 0.59.0", - "winnow 0.7.3", + "winnow", "xdg-home", "zbus_macros", "zbus_names", @@ -5618,13 +5600,13 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow 0.6.20", + "winnow", "zvariant", ] @@ -5648,16 +5630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" -dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive", ] [[package]] @@ -5671,17 +5644,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zerocopy-derive" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zerofrom" version = "0.1.5" @@ -5710,10 +5672,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -5722,9 +5695,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", @@ -5757,7 +5730,7 @@ dependencies = [ "serde", "static_assertions", "url", - "winnow 0.7.3", + "winnow", "zvariant_derive", "zvariant_utils", ] @@ -5786,5 +5759,5 @@ dependencies = [ "serde", "static_assertions", "syn", - "winnow 0.7.3", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 9cb793d0f..45754c2f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ ahash = { version = "0.8.11", default-features = false, features = [ backtrace = "0.3" bitflags = "2.6" bytemuck = "1.23.2" -criterion = { version = "0.5.1", default-features = false } +criterion = { version = "0.7", default-features = false } dify = { version = "0.7", default-features = false } document-features = "0.2.10" glow = "0.16" @@ -95,7 +95,7 @@ profiling = { version = "1.0.16", default-features = false } puffin = "0.19" puffin_http = "0.16" raw-window-handle = "0.6.0" -ron = "0.10.1" +ron = "0.11" serde = { version = "1", features = ["derive"] } similar-asserts = "1.4.2" smallvec = "1" @@ -107,7 +107,7 @@ wasm-bindgen-futures = "0.4" web-sys = "0.3.73" web-time = "1.1.0" # Timekeeping for native and web wgpu = { version = "25.0.0", default-features = false } -windows-sys = "0.59" +windows-sys = "0.60" winit = { version = "0.30.12", default-features = false } [workspace.lints.rust] diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index b95a8eb67..ab9871f02 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -271,4 +271,4 @@ wgpu = { workspace = true, optional = true } # Native dev dependencies for testing [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -directories = "5" +directories = "6" diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index d4b10a216..7b97b20a2 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -1,10 +1,12 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use criterion::{Criterion, criterion_group, criterion_main}; use epaint::{ AlphaFromCoverage, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions, Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, }; +use std::hint::black_box; + #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; // Much faster allocator From 4947b191e46dfe918ab25eebd1a728e0b675240f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 4 Sep 2025 09:54:59 +0200 Subject: [PATCH 194/388] Make more dependencies workspace dependencies (#7495) --- Cargo.toml | 7 +++++++ crates/eframe/Cargo.toml | 16 ++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45754c2f3..33c91a2c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,17 +79,23 @@ bitflags = "2.6" bytemuck = "1.23.2" criterion = { version = "0.7", default-features = false } dify = { version = "0.7", default-features = false } +directories = "6" document-features = "0.2.10" glow = "0.16" 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 } +js-sys = "0.3" kittest = { version = "0.2.0" } log = { version = "0.4", features = ["std"] } mimalloc = "0.1.46" nohash-hasher = "0.2" +objc2 = "0.5.1" +objc2-app-kit = { version = "0.2.0", default-features = false } +objc2-foundation = { version = "0.2.0", default-features = false } parking_lot = "0.12" +percent-encoding = "2.1" pollster = "0.4" profiling = { version = "1.0.16", default-features = false } puffin = "0.19" @@ -99,6 +105,7 @@ ron = "0.11" serde = { version = "1", features = ["derive"] } similar-asserts = "1.4.2" smallvec = "1" +static_assertions = "1.1.0" thiserror = "1.0.37" type-map = "0.5.0" unicode-segmentation = "1.12.0" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index ab9871f02..b37a19899 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -42,7 +42,7 @@ default = [ "egui-wgpu?/fragile-send-sync-non-atomic-wasm", # Let's enable some backends so that users can use `eframe` out-of-the-box # without having to explicitly opt-in to backends - "egui-wgpu?/default" + "egui-wgpu?/default", ] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). @@ -135,7 +135,7 @@ log.workspace = true parking_lot.workspace = true profiling.workspace = true raw-window-handle.workspace = true -static_assertions = "1.1.0" +static_assertions.workspace = true web-time.workspace = true # Optional dependencies @@ -174,14 +174,14 @@ wgpu = { workspace = true, optional = true } # mac: [target.'cfg(any(target_os = "macos"))'.dependencies] -objc2 = "0.5.1" -objc2-foundation = { version = "0.2.0", default-features = false, features = [ +objc2.workspace = true +objc2-foundation = { workspace = true, default-features = false, features = [ "std", "block2", "NSData", "NSString", ] } -objc2-app-kit = { version = "0.2.0", default-features = false, features = [ +objc2-app-kit = { workspace = true, default-features = false, features = [ "std", "NSApplication", "NSImage", @@ -205,8 +205,8 @@ windows-sys = { workspace = true, features = [ [target.'cfg(target_arch = "wasm32")'.dependencies] bytemuck.workspace = true image = { workspace = true, features = ["png"] } # For copying images -js-sys = "0.3" -percent-encoding = "2.1" +js-sys.workspace = true +percent-encoding.workspace = true wasm-bindgen.workspace = true wasm-bindgen-futures.workspace = true web-sys = { workspace = true, features = [ @@ -271,4 +271,4 @@ wgpu = { workspace = true, optional = true } # Native dev dependencies for testing [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -directories = "6" +directories.workspace = true From d3cd6d44cf1bc980d186b61ccf3ce27db7ea1450 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 4 Sep 2025 10:07:35 +0200 Subject: [PATCH 195/388] Add `Ui::place`, to place widgets without changing the cursor (#7359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Closes Currently `ui.put` moves the cursor after the placed widget. In my opinion that is a bit unexpected and counterproductive in most cases where `ui.put` makes sense. The following example breakes with the current behavior and looks right with my change: ```rs ui.horizontal(|ui| { 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)); } 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)); } }); ui.add_space(10.0); let response = ui.button("Notifications"); ui.put( Rect::from_center_size(response.rect.right_top(), Vec2::splat(12.0)), |ui: &mut Ui| { Frame::new() .fill(Color32::RED) .corner_radius(10.0) .show(ui, |ui| { ui.label(RichText::new("11").size(8.0).color(Color32::WHITE)); }).response }, ); ui.button("Some other button"); ``` Screenshot 2025-07-14 at 10 58 30 Screenshot 2025-07-14 at 10 58 51 I had a look at reruns source code and there are no uses of `ui.put` that would break with this change (very little usages in general). ## Alternatives Instead of a breaking change we could of course instead introduce a new metheod (e.g. `Ui::place`?). --- crates/egui/src/atomics/atom_kind.rs | 4 ++-- crates/egui/src/ui.rs | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/atomics/atom_kind.rs b/crates/egui/src/atomics/atom_kind.rs index f58b39f8a..3c54c496b 100644 --- a/crates/egui/src/atomics/atom_kind.rs +++ b/crates/egui/src/atomics/atom_kind.rs @@ -41,7 +41,7 @@ pub enum AtomKind<'a> { /// For custom rendering. /// /// You can get the [`crate::Rect`] with the [`Id`] from [`crate::AtomLayoutResponse`] and use a - /// [`crate::Painter`] or [`Ui::put`] to add/draw some custom content. + /// [`crate::Painter`] or [`Ui::place`] to add/draw some custom content. /// /// Example: /// ``` @@ -53,7 +53,7 @@ pub enum AtomKind<'a> { /// /// let rect = response.rect(id); /// if let Some(rect) = rect { - /// ui.put(rect, Button::new("⏵")); + /// ui.place(rect, Button::new("⏵")); /// } /// # }); /// ``` diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 67d210b20..f595469b4 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1710,7 +1710,7 @@ impl Ui { /// The returned [`Response`] can be used to check for interactions, /// as well as adding tooltips using [`Response::on_hover_text`]. /// - /// See also [`Self::add_sized`] and [`Self::put`]. + /// See also [`Self::add_sized`], [`Self::place`] and [`Self::put`]. /// /// ``` /// # egui::__run_test_ui(|ui| { @@ -1729,7 +1729,7 @@ impl Ui { /// /// To fill all remaining area, use `ui.add_sized(ui.available_size(), widget);` /// - /// See also [`Self::add`] and [`Self::put`]. + /// See also [`Self::add`], [`Self::place`] and [`Self::put`]. /// /// ``` /// # egui::__run_test_ui(|ui| { @@ -1748,9 +1748,23 @@ impl Ui { .inner } - /// Add a [`Widget`] to this [`Ui`] at a specific location (manual layout). + /// Add a [`Widget`] to this [`Ui`] at a specific location (manual layout) without + /// affecting this [`Ui`]s cursor. /// - /// See also [`Self::add`] and [`Self::add_sized`]. + /// See also [`Self::add`] and [`Self::add_sized`] and [`Self::put`]. + pub fn place(&mut self, max_rect: Rect, widget: impl Widget) -> Response { + self.new_child( + UiBuilder::new() + .max_rect(max_rect) + .layout(Layout::centered_and_justified(Direction::TopDown)), + ) + .add(widget) + } + + /// Add a [`Widget`] to this [`Ui`] at a specific location (manual layout) and advance the + /// cursor after the widget. + /// + /// See also [`Self::add`], [`Self::add_sized`], and [`Self::place`]. pub fn put(&mut self, max_rect: Rect, widget: impl Widget) -> Response { self.scope_builder( UiBuilder::new() From fa4bee3bf7a8b6c6f7253520d0dc47d5c2cd6519 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 4 Sep 2025 10:31:26 +0200 Subject: [PATCH 196/388] Fix deadlock in `ImageLoader`, `FileLoader`, `EhttpLoader` (#7494) * Recently CI runs started to hang randomly: https://github.com/emilk/egui/actions/runs/17427449210/job/49477714447?pr=7359 This fixes the deadlock and adds the basic deadlock detection we also added to Mutexes in #7468. Also, interestingly, the more sophisticated deadlock detection (behind the deadlock_detection feature) didn't catch this for some reason. I wonder why it exists in the first place, when parking_lot also has built in deadlock detection? It also seems to make tests slower, widget_tests usually needs ~30s, with the deadlock detection removed its only ~12s. --- .../egui_extras/src/loaders/ehttp_loader.rs | 31 ++++++++++++------- crates/egui_extras/src/loaders/file_loader.rs | 23 +++++++++----- .../egui_extras/src/loaders/image_loader.rs | 27 +++++++++++----- crates/epaint/src/mutex.rs | 27 ++++++++++++++-- 4 files changed, 80 insertions(+), 28 deletions(-) diff --git a/crates/egui_extras/src/loaders/ehttp_loader.rs b/crates/egui_extras/src/loaders/ehttp_loader.rs index 7310cc6e1..1d3e20c90 100644 --- a/crates/egui_extras/src/loaders/ehttp_loader.rs +++ b/crates/egui_extras/src/loaders/ehttp_loader.rs @@ -94,18 +94,27 @@ impl BytesLoader for EhttpLoader { Err(format!("Failed to load {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); + let repaint = { + 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:?}"); + true + } else { + log::trace!( + "Canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading." + ); + false + } + }; + // We may not lock Context while the cache lock is held (see ImageLoader::load + // for details). + if repaint { ctx.request_repaint(); - log::trace!("Finished loading {uri:?}"); - } else { - log::trace!( - "Canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading." - ); } } }); diff --git a/crates/egui_extras/src/loaders/file_loader.rs b/crates/egui_extras/src/loaders/file_loader.rs index 001e988c2..5e66b76da 100644 --- a/crates/egui_extras/src/loaders/file_loader.rs +++ b/crates/egui_extras/src/loaders/file_loader.rs @@ -95,14 +95,23 @@ impl BytesLoader for FileLoader { } Err(err) => Err(err.to_string()), }; - 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); + let repaint = { + 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:?}"); + true + } else { + log::trace!("Canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); + false + } + }; + // We may not lock Context while the cache lock is held (see ImageLoader::load + // for details). + if repaint { ctx.request_repaint(); - log::trace!("Finished loading {uri:?}"); - } else { - log::trace!("Canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); } } }) diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 82df87b38..3acb6b582 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -100,15 +100,28 @@ impl ImageLoader for ImageCrateLoader { let result = crate::image::load_image_bytes(&bytes) .map(Arc::new) .map_err(|err| err.to_string()); - let mut cache = cache.lock(); + let repaint = { + 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); + if let std::collections::hash_map::Entry::Occupied(mut entry) = cache.entry(uri.clone()) { + let entry = entry.get_mut(); + *entry = Poll::Ready(result); + log::trace!("ImageLoader - finished loading {uri:?}"); + true + } else { + log::trace!("ImageLoader - canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); + false + } + }; + // We may not lock Context while the cache lock is held, since this can + // deadlock. + // Example deadlock scenario: + // - loader thread: lock cache + // - main thread: lock ctx (e.g. in `Context::has_pending_images`) + // - loader thread: try to lock ctx (in `request_repaint`) + // - main thread: try to lock cache (from `Self::has_pending`) + if repaint { ctx.request_repaint(); - log::trace!("ImageLoader - finished loading {uri:?}"); - } else { - log::trace!("ImageLoader - canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading."); } } }) diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index ef1b3a6c4..1a043b37b 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -2,8 +2,13 @@ // ---------------------------------------------------------------------------- +#[cfg(not(feature = "deadlock_detection"))] +const DEADLOCK_DURATION: std::time::Duration = std::time::Duration::from_secs(30); + #[cfg(not(feature = "deadlock_detection"))] mod mutex_impl { + use super::DEADLOCK_DURATION; + /// Provides interior mutability. /// /// This is a thin wrapper around [`parking_lot::Mutex`], except if @@ -25,7 +30,7 @@ mod mutex_impl { pub fn lock(&self) -> MutexGuard<'_, T> { if cfg!(debug_assertions) { self.0 - .try_lock_for(std::time::Duration::from_secs(30)) + .try_lock_for(DEADLOCK_DURATION) .expect("Looks like a deadlock!") } else { self.0.lock() @@ -127,6 +132,8 @@ mod mutex_impl { #[cfg(not(feature = "deadlock_detection"))] mod rw_lock_impl { + use super::DEADLOCK_DURATION; + /// The lock you get from [`RwLock::read`]. pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard; @@ -151,12 +158,26 @@ mod rw_lock_impl { impl RwLock { #[inline(always)] pub fn read(&self) -> RwLockReadGuard<'_, T> { - parking_lot::RwLockReadGuard::map(self.0.read(), |v| v) + let guard = if cfg!(debug_assertions) { + self.0 + .try_read_for(DEADLOCK_DURATION) + .expect("Looks like a deadlock!") + } else { + self.0.read() + }; + parking_lot::RwLockReadGuard::map(guard, |v| v) } #[inline(always)] pub fn write(&self) -> RwLockWriteGuard<'_, T> { - parking_lot::RwLockWriteGuard::map(self.0.write(), |v| v) + let guard = if cfg!(debug_assertions) { + self.0 + .try_write_for(DEADLOCK_DURATION) + .expect("Looks like a deadlock!") + } else { + self.0.write() + }; + parking_lot::RwLockWriteGuard::map(guard, |v| v) } } } From d66fa63e202ae653364e48e6795389607ef476c1 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 4 Sep 2025 12:37:24 +0200 Subject: [PATCH 197/388] Add reasonable timeouts to all workflows (#7499) Turns out the default timeout for github actions is 6 hours (!). This PR sets some reasonable default for all workflows, the ones invoking cargo in some way are limited to 60 minutes and the remaining ones to 10-15mins. --- .github/workflows/cargo_machete.yml | 1 + .github/workflows/deploy_web_demo.yml | 2 +- .github/workflows/enforce_branch_name.yml | 1 + .github/workflows/labels.yml | 1 + .github/workflows/png_only_on_lfs.yml | 1 + .github/workflows/preview_build.yml | 1 + .github/workflows/preview_cleanup.yml | 1 + .github/workflows/preview_deploy.yml | 1 + .github/workflows/rust.yml | 9 ++++++++- .github/workflows/spelling_and_links.yml | 2 ++ 10 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml index b67eadc0e..0414de063 100644 --- a/.github/workflows/cargo_machete.yml +++ b/.github/workflows/cargo_machete.yml @@ -5,6 +5,7 @@ on: [push, pull_request] jobs: cargo-machete: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index a122914a0..f88d89414 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -29,8 +29,8 @@ jobs: # Single deploy job since we're just deploying deploy: name: Deploy web demo - runs-on: ubuntu-latest + timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/enforce_branch_name.yml b/.github/workflows/enforce_branch_name.yml index 85035330e..8c2b28d37 100644 --- a/.github/workflows/enforce_branch_name.yml +++ b/.github/workflows/enforce_branch_name.yml @@ -7,6 +7,7 @@ on: jobs: check-source-branch: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Check PR source branch run: | diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index b83040667..2982b2a84 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -16,6 +16,7 @@ on: jobs: label: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Check for a "do-not-merge" label uses: mheap/github-action-required-labels@v3 diff --git a/.github/workflows/png_only_on_lfs.yml b/.github/workflows/png_only_on_lfs.yml index 271a06c00..0b7c122fe 100644 --- a/.github/workflows/png_only_on_lfs.yml +++ b/.github/workflows/png_only_on_lfs.yml @@ -5,6 +5,7 @@ on: [push, pull_request] jobs: check-binary-files: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout repository diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index b50d866df..05d4118d7 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -15,6 +15,7 @@ on: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master diff --git a/.github/workflows/preview_cleanup.yml b/.github/workflows/preview_cleanup.yml index 6e2b94b83..b2ef1e3b3 100644 --- a/.github/workflows/preview_cleanup.yml +++ b/.github/workflows/preview_cleanup.yml @@ -11,6 +11,7 @@ on: jobs: cleanup: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/preview_deploy.yml b/.github/workflows/preview_deploy.yml index f98f96da1..5a97658cd 100644 --- a/.github/workflows/preview_deploy.yml +++ b/.github/workflows/preview_deploy.yml @@ -20,6 +20,7 @@ concurrency: jobs: deploy: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3698dcc22..d6abc2d45 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,6 +11,7 @@ jobs: fmt-crank-check-test: name: Format + check runs-on: ubuntu-22.04 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 with: @@ -79,6 +80,7 @@ jobs: check_wasm: name: Check wasm32 + wasm-bindgen runs-on: ubuntu-22.04 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master @@ -114,6 +116,7 @@ jobs: check_wasm_atomics: name: Check wasm32+atomics runs-on: ubuntu-22.04 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev @@ -151,6 +154,7 @@ jobs: name: cargo-deny ${{ matrix.target }} runs-on: ubuntu-22.04 + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v2 @@ -165,6 +169,7 @@ jobs: android: name: android runs-on: ubuntu-22.04 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 @@ -186,6 +191,7 @@ jobs: ios: name: ios runs-on: ubuntu-22.04 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 @@ -206,6 +212,7 @@ jobs: windows: name: Check Windows runs-on: windows-latest + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master @@ -227,7 +234,7 @@ jobs: name: Run tests # We run the tests on macOS because it will run with an actual GPU runs-on: macos-latest - + timeout-minutes: 60 steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/spelling_and_links.yml b/.github/workflows/spelling_and_links.yml index 8fb16ae27..e411de5f3 100644 --- a/.github/workflows/spelling_and_links.yml +++ b/.github/workflows/spelling_and_links.yml @@ -8,6 +8,7 @@ jobs: # install and run locally: cargo install typos-cli && typos name: typos runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout Actions Repository uses: actions/checkout@v4 @@ -18,6 +19,7 @@ jobs: lychee: name: lychee runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Don't check CHANGELOG.md files From 80d61a7c534131b10a5d6c43d408b7f7afb62633 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 4 Sep 2025 12:57:09 +0200 Subject: [PATCH 198/388] Remove the `deadlock_detection` feature (#7497) * related #7494 Removes the `deadlock_detection` feature, since we now have a more primitive panic-after-30s deadlock detection which works well enough and even detects kinds of deadlocks that the `deadlock_detection` feature never supported. --- Cargo.lock | 1 - crates/egui/Cargo.toml | 5 - crates/egui_demo_app/tests/test_demo_app.rs | 3 + crates/epaint/Cargo.toml | 10 - crates/epaint/src/mutex.rs | 470 ++++---------------- 5 files changed, 94 insertions(+), 395 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7497726f0..e799591de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1535,7 +1535,6 @@ version = "0.32.1" dependencies = [ "ab_glyph", "ahash", - "backtrace", "bytemuck", "criterion", "document-features", diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 238a8d8ea..32951c05b 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -44,11 +44,6 @@ cint = ["epaint/cint"] ## Enable the [`hex_color`] macro. color-hex = ["epaint/color-hex"] -## This will automatically detect deadlocks due to double-locking on the same thread. -## If your app freezes, you may want to enable this! -## Only affects [`epaint::mutex::RwLock`] (which egui uses a lot). -deadlock_detection = ["epaint/deadlock_detection"] - ## If set, egui will use `include_bytes!` to bundle some fonts. ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["epaint/default_fonts"] diff --git a/crates/egui_demo_app/tests/test_demo_app.rs b/crates/egui_demo_app/tests/test_demo_app.rs index 63b3b2a89..e083c8455 100644 --- a/crates/egui_demo_app/tests/test_demo_app.rs +++ b/crates/egui_demo_app/tests/test_demo_app.rs @@ -62,6 +62,9 @@ fn test_demo_app() { .type_text("file://../eframe/data/icon.png"); harness.get_by_role_and_label(Role::Button, "✔").click(); + + // Wait for the image to load + harness.try_run_realtime().ok(); } _ => {} } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 5a4016980..3cbb4f474 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -40,11 +40,6 @@ cint = ["ecolor/cint"] ## Enable the [`hex_color`] macro. color-hex = ["ecolor/color-hex"] -## This will automatically detect deadlocks due to double-locking on the same thread. -## If your app freezes, you may want to enable this! -## Only affects [`mutex::RwLock`] (which epaint and egui uses a lot). -deadlock_detection = ["dep:backtrace"] - ## If set, epaint will use `include_bytes!` to bundle some fonts. ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["epaint_default_fonts"] @@ -94,11 +89,6 @@ serde = { workspace = true, optional = true, features = ["derive", "rc"] } epaint_default_fonts = { workspace = true, optional = true } -# native: -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -backtrace = { workspace = true, optional = true } - - [dev-dependencies] criterion.workspace = true mimalloc.workspace = true diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 1a043b37b..4b6a66f94 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -1,394 +1,107 @@ -//! Helper module that adds extra checks when the `deadlock_detection` feature is turned on. +//! Wrappers around `parking_lot` locks, with a simple deadlock detection mechanism. // ---------------------------------------------------------------------------- -#[cfg(not(feature = "deadlock_detection"))] -const DEADLOCK_DURATION: std::time::Duration = std::time::Duration::from_secs(30); +const DEADLOCK_DURATION: std::time::Duration = std::time::Duration::from_secs(10); -#[cfg(not(feature = "deadlock_detection"))] -mod mutex_impl { - use super::DEADLOCK_DURATION; +/// Provides interior mutability. +/// +/// It's tailored for internal use in egui should only be used for short locks (as a guideline, +/// locks should never be held longer than a single frame). In debug builds, when a lock can't +/// be acquired within 10 seconds, we assume a deadlock and will panic. +/// +/// This is a thin wrapper around [`parking_lot::Mutex`]. +#[derive(Default)] +pub struct Mutex(parking_lot::Mutex); - /// Provides interior mutability. +/// The lock you get from [`Mutex`]. +pub use parking_lot::MutexGuard; + +impl Mutex { + #[inline(always)] + pub fn new(val: T) -> Self { + Self(parking_lot::Mutex::new(val)) + } + + /// Try to acquire the lock. /// - /// This is a thin wrapper around [`parking_lot::Mutex`], except if - /// the feature `deadlock_detection` is turned enabled, in which case - /// extra checks are added to detect deadlocks. - #[derive(Default)] - pub struct Mutex(parking_lot::Mutex); - - /// The lock you get from [`Mutex`]. - pub use parking_lot::MutexGuard; - - impl Mutex { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(parking_lot::Mutex::new(val)) - } - - #[inline(always)] - pub fn lock(&self) -> MutexGuard<'_, T> { - if cfg!(debug_assertions) { - self.0 - .try_lock_for(DEADLOCK_DURATION) - .expect("Looks like a deadlock!") - } else { - self.0.lock() - } - } - } -} - -#[cfg(feature = "deadlock_detection")] -mod mutex_impl { - /// Provides interior mutability. - /// - /// This is a thin wrapper around [`parking_lot::Mutex`], except if - /// the feature `deadlock_detection` is turned enabled, in which case - /// extra checks are added to detect deadlocks. - #[derive(Default)] - pub struct Mutex(parking_lot::Mutex); - - /// The lock you get from [`Mutex`]. - pub struct MutexGuard<'a, T>(parking_lot::MutexGuard<'a, T>, *const ()); - - #[derive(Default)] - struct HeldLocks(Vec<*const ()>); - - impl HeldLocks { - #[inline(always)] - fn insert(&mut self, lock: *const ()) { - // Very few locks will ever be held at the same time, so a linear search is fast - assert!( - !self.0.contains(&lock), - "Recursively locking a Mutex in the same thread is not supported" - ); - self.0.push(lock); - } - - #[inline(always)] - fn remove(&mut self, lock: *const ()) { - self.0.retain(|&ptr| ptr != lock); - } - } - - thread_local! { - static HELD_LOCKS_TLS: std::cell::RefCell = Default::default(); - } - - impl Mutex { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(parking_lot::Mutex::new(val)) - } - - pub fn lock(&self) -> MutexGuard<'_, T> { - // Detect if we are recursively taking out a lock on this mutex. - - // use a pointer to the inner data as an id for this lock - let ptr = std::ptr::from_ref::>(&self.0).cast::<()>(); - - // Store it in thread local storage while we have a lock guard taken out - HELD_LOCKS_TLS.with(|held_locks| { - held_locks.borrow_mut().insert(ptr); - }); - - MutexGuard(self.0.lock(), ptr) - } - - #[inline(always)] - pub fn into_inner(self) -> T { - self.0.into_inner() - } - } - - impl Drop for MutexGuard<'_, T> { - fn drop(&mut self) { - let ptr = self.1; - HELD_LOCKS_TLS.with(|held_locks| { - held_locks.borrow_mut().remove(ptr); - }); - } - } - - impl std::ops::Deref for MutexGuard<'_, T> { - type Target = T; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl std::ops::DerefMut for MutexGuard<'_, T> { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } -} - -// ---------------------------------------------------------------------------- - -#[cfg(not(feature = "deadlock_detection"))] -mod rw_lock_impl { - use super::DEADLOCK_DURATION; - - /// The lock you get from [`RwLock::read`]. - pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard; - - /// The lock you get from [`RwLock::write`]. - pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard; - - /// Provides interior mutability. - /// - /// This is a thin wrapper around [`parking_lot::RwLock`], except if - /// the feature `deadlock_detection` is turned enabled, in which case - /// extra checks are added to detect deadlocks. - #[derive(Default)] - pub struct RwLock(parking_lot::RwLock); - - impl RwLock { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(parking_lot::RwLock::new(val)) - } - } - - impl RwLock { - #[inline(always)] - pub fn read(&self) -> RwLockReadGuard<'_, T> { - let guard = if cfg!(debug_assertions) { - self.0 - .try_read_for(DEADLOCK_DURATION) - .expect("Looks like a deadlock!") - } else { - self.0.read() - }; - parking_lot::RwLockReadGuard::map(guard, |v| v) - } - - #[inline(always)] - pub fn write(&self) -> RwLockWriteGuard<'_, T> { - let guard = if cfg!(debug_assertions) { - self.0 - .try_write_for(DEADLOCK_DURATION) - .expect("Looks like a deadlock!") - } else { - self.0.write() - }; - parking_lot::RwLockWriteGuard::map(guard, |v| v) - } - } -} - -#[cfg(feature = "deadlock_detection")] -mod rw_lock_impl { - use std::{ - ops::{Deref, DerefMut}, - sync::Arc, - thread::ThreadId, - }; - - use ahash::HashMap; - use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; - - /// The lock you get from [`RwLock::read`]. - pub struct RwLockReadGuard<'a, T> { - // The option is used only because we need to `take()` the guard out of self - // when doing remappings (`map()`), i.e. it's used as a safe `ManuallyDrop`. - guard: Option>, - holders: Arc>>, - } - - impl<'a, T> RwLockReadGuard<'a, T> { - #[inline] - pub fn map(mut s: Self, f: F) -> RwLockReadGuard<'a, U> - where - F: FnOnce(&T) -> &U, - { - RwLockReadGuard { - guard: s - .guard - .take() - .map(|g| parking_lot::MappedRwLockReadGuard::map(g, f)), - holders: Arc::clone(&s.holders), - } - } - } - - impl Deref for RwLockReadGuard<'_, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.guard.as_ref().unwrap() - } - } - - impl Drop for RwLockReadGuard<'_, T> { - fn drop(&mut self) { - let tid = std::thread::current().id(); - self.holders.lock().remove(&tid); - } - } - - /// The lock you get from [`RwLock::write`]. - pub struct RwLockWriteGuard<'a, T> { - // The option is used only because we need to `take()` the guard out of self - // when doing remappings (`map()`), i.e. it's used as a safe `ManuallyDrop`. - guard: Option>, - holders: Arc>>, - } - - impl<'a, T> RwLockWriteGuard<'a, T> { - #[inline] - pub fn map(mut s: Self, f: F) -> RwLockWriteGuard<'a, U> - where - F: FnOnce(&mut T) -> &mut U, - { - RwLockWriteGuard { - guard: s - .guard - .take() - .map(|g| parking_lot::MappedRwLockWriteGuard::map(g, f)), - holders: Arc::clone(&s.holders), - } - } - } - - impl Deref for RwLockWriteGuard<'_, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.guard.as_ref().unwrap() - } - } - - impl DerefMut for RwLockWriteGuard<'_, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.guard.as_mut().unwrap() - } - } - - impl Drop for RwLockWriteGuard<'_, T> { - fn drop(&mut self) { - let tid = std::thread::current().id(); - self.holders.lock().remove(&tid); - } - } - - /// Provides interior mutability. - /// - /// This is a thin wrapper around [`parking_lot::RwLock`], except if - /// the feature `deadlock_detection` is turned enabled, in which case - /// extra checks are added to detect deadlocks. - #[derive(Default)] - pub struct RwLock { - lock: parking_lot::RwLock, - // Technically we'd need a list of backtraces per thread-id since parking_lot's - // read-locks are reentrant. - // In practice it's not that useful to have the whole list though, so we only - // keep track of the first backtrace for now. - holders: Arc>>, - } - - impl RwLock { - pub fn new(val: T) -> Self { - Self { - lock: parking_lot::RwLock::new(val), - holders: Default::default(), - } - } - - pub fn read(&self) -> RwLockReadGuard<'_, T> { - let tid = std::thread::current().id(); - - // If it is write-locked, and we locked it (reentrancy deadlock) - let would_deadlock = - self.lock.is_locked_exclusive() && self.holders.lock().contains_key(&tid); - assert!( - !would_deadlock, - "{} DEAD-LOCK DETECTED ({:?})!\n\ - Trying to grab read-lock at:\n{}\n\ - which is already exclusively held by current thread at:\n{}\n\n", - std::any::type_name::(), - tid, - format_backtrace(&mut make_backtrace()), - format_backtrace(self.holders.lock().get_mut(&tid).unwrap()) - ); - - self.holders - .lock() - .entry(tid) - .or_insert_with(make_backtrace); - - RwLockReadGuard { - guard: parking_lot::RwLockReadGuard::map(self.lock.read(), |v| v).into(), - holders: Arc::clone(&self.holders), - } - } - - pub fn write(&self) -> RwLockWriteGuard<'_, T> { - let tid = std::thread::current().id(); - - // If it is locked in any way, and we locked it (reentrancy deadlock) - let would_deadlock = self.lock.is_locked() && self.holders.lock().contains_key(&tid); - assert!( - !would_deadlock, - "{} DEAD-LOCK DETECTED ({:?})!\n\ - Trying to grab write-lock at:\n{}\n\ - which is already held by current thread at:\n{}\n\n", - std::any::type_name::(), - tid, - format_backtrace(&mut make_backtrace()), - format_backtrace(self.holders.lock().get_mut(&tid).unwrap()) - ); - - self.holders - .lock() - .entry(tid) - .or_insert_with(make_backtrace); - - RwLockWriteGuard { - guard: parking_lot::RwLockWriteGuard::map(self.lock.write(), |v| v).into(), - holders: Arc::clone(&self.holders), - } - } - - #[inline(always)] - pub fn into_inner(self) -> T { - self.lock.into_inner() - } - } - - fn make_backtrace() -> backtrace::Backtrace { - backtrace::Backtrace::new_unresolved() - } - - fn format_backtrace(backtrace: &mut backtrace::Backtrace) -> String { - backtrace.resolve(); - - let stacktrace = format!("{backtrace:?}"); - - // Remove irrelevant parts of the stacktrace: - let end_offset = stacktrace - .find("std::sys_common::backtrace::__rust_begin_short_backtrace") - .unwrap_or(stacktrace.len()); - let stacktrace = &stacktrace[..end_offset]; - - let first_interesting_function = "epaint::mutex::rw_lock_impl::make_backtrace\n"; - if let Some(start_offset) = stacktrace.find(first_interesting_function) { - stacktrace[start_offset + first_interesting_function.len()..].to_owned() + /// ## Panics + /// Will panic in debug builds if the lock can't be acquired within 10 seconds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn lock(&self) -> MutexGuard<'_, T> { + if cfg!(debug_assertions) { + self.0 + .try_lock_for(DEADLOCK_DURATION) + .expect("Looks like a deadlock!") } else { - stacktrace.to_owned() + self.0.lock() } } } // ---------------------------------------------------------------------------- -pub use mutex_impl::{Mutex, MutexGuard}; -pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +/// The lock you get from [`RwLock::read`]. +pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard; + +/// The lock you get from [`RwLock::write`]. +pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard; + +/// Provides interior mutability. +/// +/// It's tailored for internal use in egui should only be used for short locks (as a guideline, +/// locks should never be held longer than a single frame). In debug builds, when a lock can't +/// be acquired within 10 seconds, we assume a deadlock and will panic. +/// +/// This is a thin wrapper around [`parking_lot::RwLock`]. +#[derive(Default)] +pub struct RwLock(parking_lot::RwLock); + +impl RwLock { + #[inline(always)] + pub fn new(val: T) -> Self { + Self(parking_lot::RwLock::new(val)) + } +} + +impl RwLock { + /// Try to acquire read-access to the lock. + /// + /// ## Panics + /// Will panic in debug builds if the lock can't be acquired within 10 seconds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn read(&self) -> RwLockReadGuard<'_, T> { + let guard = if cfg!(debug_assertions) { + self.0 + .try_read_for(DEADLOCK_DURATION) + .expect("Looks like a deadlock!") + } else { + self.0.read() + }; + parking_lot::RwLockReadGuard::map(guard, |v| v) + } + + /// Try to acquire write-access to the lock. + /// + /// ## Panics + /// Will panic in debug builds if the lock can't be acquired within 10 seconds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn write(&self) -> RwLockWriteGuard<'_, T> { + let guard = if cfg!(debug_assertions) { + self.0 + .try_write_for(DEADLOCK_DURATION) + .expect("Looks like a deadlock!") + } else { + self.0.write() + }; + parking_lot::RwLockWriteGuard::map(guard, |v| v) + } +} + +// ---------------------------------------------------------------------------- impl Clone for Mutex where @@ -434,7 +147,6 @@ mod tests { } #[cfg(not(target_arch = "wasm32"))] -#[cfg(feature = "deadlock_detection")] #[cfg(test)] mod tests_rwlock { #![allow(clippy::disallowed_methods)] // Ok for tests From 3a2094e80e879830548ef3bf53d5bc008597c2b1 Mon Sep 17 00:00:00 2001 From: darkwater Date: Thu, 4 Sep 2025 13:02:18 +0200 Subject: [PATCH 199/388] Add `Memory::move_focus` (#7476) * [X] I have followed the instructions in the PR template This allows us to programatically move focus around. Another way would be to inject arrow key inputs, but that requires the backend to implement a way to do that, and could have unintended side-effects. Co-authored-by: Emil Ernerfeldt --- crates/egui/src/lib.rs | 2 +- crates/egui/src/memory/mod.rs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 1fc5f6ca7..75f804576 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -499,7 +499,7 @@ pub use self::{ layers::{LayerId, Order}, layout::*, load::SizeHint, - memory::{Memory, Options, Theme, ThemePreference}, + memory::{FocusDirection, Memory, Options, Theme, ThemePreference}, painter::Painter, response::{InnerResponse, Response}, sense::Sense, diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 349ebcea3..1e8cc4c92 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -140,8 +140,9 @@ impl Default for Memory { } } +/// A direction in which to move the keyboard focus. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -enum FocusDirection { +pub enum FocusDirection { /// Select the widget closest above the current focused widget. Up, @@ -888,6 +889,11 @@ impl Memory { } } + /// Move keyboard focus in a specific direction. + pub fn move_focus(&mut self, direction: FocusDirection) { + self.focus_mut().focus_direction = direction; + } + /// Returns true if /// - this layer is the top-most modal layer or above it /// - there is no modal layer From 763e2df9f94477f562bb4182776c5319380ea722 Mon Sep 17 00:00:00 2001 From: Aleksandr Strizhevskiy Date: Thu, 4 Sep 2025 07:13:28 -0400 Subject: [PATCH 200/388] Fix: prevent calendar popup from closing on dropdown change (#7409) Currently, DatePickerButton will close without saving whenever a user clicks a dropdown from year/month/date. The issue is caused because the system mistakenly interprets the user as clicking off of the calendar. This is unexpected and creates an unpleasant experience for the user. This change now allows the user to use the dropdowns as expected; it will close on save or cancel. The calendar still closes when user clicks off of it, as before. The changes here are made in: crates/egui_extras/src/datepicker/button.rs I will admit that I am not an experienced Rust developer. The changes were made with the help of ChatGPT 4.0. I have tested the changes locally, as I am using the date picker in my project. * Closes * [x] I have followed the instructions in the PR template --------- Co-authored-by: Lucas Meurer --- crates/egui_extras/src/datepicker/button.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/egui_extras/src/datepicker/button.rs b/crates/egui_extras/src/datepicker/button.rs index 0ec0680f8..a6e278286 100644 --- a/crates/egui_extras/src/datepicker/button.rs +++ b/crates/egui_extras/src/datepicker/button.rs @@ -192,7 +192,11 @@ impl Widget for DatePickerButton<'_> { button_response.mark_changed(); } + // We don't want to close our popup if any other popup is open, since other popups would + // most likely be the combo boxes in the date picker. + let any_popup_open = ui.ctx().is_popup_open(); if !button_response.clicked() + && !any_popup_open && (ui.input(|i| i.key_pressed(Key::Escape)) || area_response.clicked_elsewhere()) { button_state.picker_visible = false; From 1c460b6dc0863b85fbe931c6524cc7e11946defe Mon Sep 17 00:00:00 2001 From: Andrew Farkas <6060305+HactarCE@users.noreply.github.com> Date: Thu, 4 Sep 2025 07:17:59 -0400 Subject: [PATCH 201/388] Skip zero-length layout job sections (#7430) Fixes #7378 Includes a regression test that previously failed and now succeeds. * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt Co-authored-by: Lucas Meurer --- crates/epaint/src/text/fonts.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index f0d1c9c3a..f9c1f4f8d 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -930,7 +930,7 @@ impl GalleyCache { } else { // Section range overlaps with paragraph range debug_assert!( - section_range.start < section_range.end, + section_range.start <= section_range.end, "Bad byte_range: {section_range:?}" ); let new_range = section_range.start.saturating_sub(start) @@ -1162,6 +1162,13 @@ mod tests { job }, + { + // Regression test for + let mut job = LayoutJob::default(); + job.append("\n", 0.0, TextFormat::default()); + job.append("", 0.0, TextFormat::default()); + job + }, ] } From 669cdc1fff6b76dfbe276ca184afed1a2dbf9e2e Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:53:02 +0200 Subject: [PATCH 202/388] Remove line breaks when pasting into single line TextEdit (#7441) The line breaks are now replaced by spaces, matching the usual behavior of text fields in HTML. * Closes * [x] I have followed the instructions in the PR template Co-authored-by: Lucas Meurer --- crates/egui/src/widgets/text_edit/builder.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index f46776b7a..63dec9238 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -944,8 +944,12 @@ fn events( Event::Paste(text_to_insert) => { if !text_to_insert.is_empty() { let mut ccursor = text.delete_selected(&cursor_range); - - text.insert_text_at(&mut ccursor, text_to_insert, char_limit); + if multiline { + text.insert_text_at(&mut ccursor, text_to_insert, char_limit); + } else { + let single_line = text_to_insert.replace(['\r', '\n'], " "); + text.insert_text_at(&mut ccursor, &single_line, char_limit); + } Some(CCursorRange::one(ccursor)) } else { From 5fd452310bca8a9a3e816478da687ea11978751b Mon Sep 17 00:00:00 2001 From: Stelios Kourlis Date: Thu, 4 Sep 2025 16:03:10 +0300 Subject: [PATCH 203/388] Deprecated `ImageButton` and removed `WidgetType::ImageButton` (#7483) * Closes * [x] I have followed the instructions in the PR template * I have ran `./scripts/check.sh` and it has no fails * I have run `cargo fmt` and `cargo clippy` Added the deprecated tag to ImageButton struct Removed the `WidgetType::ImageButton` variant. ImageButton will use `WidgetType::Button` for its WidgetInfo *This is my first PR ever, please let me know if I did something wrong so I can change it* --------- Co-authored-by: Nicolas Co-authored-by: Emil Ernerfeldt Co-authored-by: Lucas Meurer --- crates/egui/src/data/output.rs | 1 - crates/egui/src/lib.rs | 2 -- crates/egui/src/response.rs | 3 +-- crates/egui/src/widgets/image_button.rs | 5 ++++- crates/egui/src/widgets/mod.rs | 1 + 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 809fae34b..0a6589655 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -713,7 +713,6 @@ impl WidgetInfo { WidgetType::Slider => "slider", WidgetType::DragValue => "drag value", WidgetType::ColorButton => "color button", - WidgetType::ImageButton => "image button", WidgetType::Image => "image", WidgetType::CollapsingHeader => "collapsing header", WidgetType::ProgressIndicator => "progress indicator", diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 75f804576..f8e9308a5 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -668,8 +668,6 @@ pub enum WidgetType { ColorButton, - ImageButton, - Image, CollapsingHeader, diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 85dc8a607..06ac7bc0c 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -847,14 +847,13 @@ impl Response { WidgetType::Label => Role::Label, WidgetType::Link => Role::Link, WidgetType::TextEdit => Role::TextInput, - WidgetType::Button | WidgetType::ImageButton | WidgetType::CollapsingHeader => { + WidgetType::Button | WidgetType::CollapsingHeader | WidgetType::SelectableLabel => { Role::Button } WidgetType::Image => Role::Image, WidgetType::Checkbox => Role::CheckBox, WidgetType::RadioButton => Role::RadioButton, WidgetType::RadioGroup => Role::RadioGroup, - WidgetType::SelectableLabel => Role::Button, WidgetType::ComboBox => Role::ComboBox, WidgetType::Slider => Role::Slider, WidgetType::DragValue => Role::SpinButton, diff --git a/crates/egui/src/widgets/image_button.rs b/crates/egui/src/widgets/image_button.rs index a765a745a..752980bb8 100644 --- a/crates/egui/src/widgets/image_button.rs +++ b/crates/egui/src/widgets/image_button.rs @@ -6,6 +6,7 @@ use crate::{ /// A clickable image within a frame. #[must_use = "You should put this widget in a ui with `ui.add(widget);`"] #[derive(Clone, Debug)] +#[deprecated(since = "0.33.0", note = "Use egui::Button::image instead")] pub struct ImageButton<'a> { pub(crate) image: Image<'a>, sense: Sense, @@ -14,6 +15,7 @@ pub struct ImageButton<'a> { alt_text: Option, } +#[expect(deprecated, reason = "Deprecated in egui 0.33.0")] impl<'a> ImageButton<'a> { pub fn new(image: impl Into>) -> Self { Self { @@ -82,6 +84,7 @@ impl<'a> ImageButton<'a> { } } +#[expect(deprecated, reason = "Deprecated in egui 0.33.0")] impl Widget for ImageButton<'_> { fn ui(self, ui: &mut Ui) -> Response { let padding = if self.frame { @@ -101,7 +104,7 @@ impl Widget for ImageButton<'_> { let padded_size = image_size + 2.0 * padding; let (rect, response) = ui.allocate_exact_size(padded_size, self.sense); response.widget_info(|| { - let mut info = WidgetInfo::new(WidgetType::ImageButton); + let mut info = WidgetInfo::new(WidgetType::Button); info.label = self.alt_text.clone(); info }); diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 9cf003c94..9cdefb699 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -24,6 +24,7 @@ pub mod text_edit; #[expect(deprecated)] pub use self::selected_label::SelectableLabel; +#[expect(deprecated, reason = "Deprecated in egui 0.33.0")] pub use self::{ button::Button, checkbox::Checkbox, From eceb0b11c9f8e68d96c33c1f4af0335b3e66376d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 5 Sep 2025 10:47:23 +0200 Subject: [PATCH 204/388] Document how to push git lfs files to 3rd party PRs (#7507) --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 110f92ddd..d3140d942 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,9 @@ git add --renormalize . # Moves already added files to regular git (according to # Push to a contributor remote (see https://github.com/cli/cli/discussions/8794#discussioncomment-8695076) git push --no-verify + +# Push git lfs files to contributor remote: +git push origin $(git branch --show-current) && git push --no-verify && git push origin --delete $(git branch --show-current) ``` ## PR review From aedd43c88f19ba88108bcab5c427d2d0d5477df2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 5 Sep 2025 16:45:36 +0200 Subject: [PATCH 205/388] kittest: Add `UPDATE_SNAPSHOTS=force` (#7508) This adds a new mode, `UPDATE_SNAPSHOTS=force`, which will lower the threshold to zero, overwriting every image that is not _exactly_ the same. Most comparisons has a threshold because different GPUs render slightly differently. However, setting that threshold accurately can be hard. Sometimes a test will pass locally, but fail on CI. In those cases you want to force an update of the failing test. You can use `UPDATE_SNAPSHOTS=force` for that. And sometimes a small change _should_ update all images, but the change is so tiny that it falls under the threshold. Still, you want to make a point of showing that these images have changes. You can use `UPDATE_SNAPSHOTS=force` for that. --- .../egui_demo_app/tests/snapshots/clock.png | 4 +- .../tests/snapshots/custom3d.png | 4 +- .../tests/snapshots/demos/Code Editor.png | 4 +- .../tests/snapshots/demos/Code Example.png | 4 +- .../tests/snapshots/demos/Cursor Test.png | 4 +- .../tests/snapshots/demos/Dancing Strings.png | 4 +- .../tests/snapshots/demos/Font Book.png | 4 +- .../tests/snapshots/demos/Frame.png | 4 +- .../tests/snapshots/demos/Grid Test.png | 2 +- .../tests/snapshots/demos/Highlighting.png | 4 +- .../tests/snapshots/demos/ID Test.png | 4 +- .../snapshots/demos/Input Event History.png | 4 +- .../tests/snapshots/demos/Input Test.png | 4 +- .../tests/snapshots/demos/Layout Test.png | 4 +- .../snapshots/demos/Manual Layout Test.png | 4 +- .../tests/snapshots/demos/Misc Demos.png | 4 +- .../tests/snapshots/demos/Multi Touch.png | 4 +- .../tests/snapshots/demos/Painting.png | 4 +- .../tests/snapshots/demos/Panels.png | 4 +- .../tests/snapshots/demos/Popups.png | 4 +- .../tests/snapshots/demos/Screenshot.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 4 +- .../tests/snapshots/demos/Sliders.png | 4 +- .../tests/snapshots/demos/Strip.png | 4 +- .../tests/snapshots/demos/Table.png | 4 +- .../tests/snapshots/demos/Text Layout.png | 4 +- .../tests/snapshots/demos/TextEdit.png | 4 +- .../tests/snapshots/demos/Tooltips.png | 4 +- .../tests/snapshots/demos/Undo Redo.png | 4 +- .../tests/snapshots/demos/Window Options.png | 4 +- .../snapshots/demos/Window Resize Test.png | 4 +- .../tests/snapshots/modals_1.png | 4 +- .../tests/snapshots/modals_2.png | 4 +- .../tests/snapshots/modals_3.png | 4 +- ...rop_should_prevent_focusing_lower_area.png | 4 +- crates/egui_kittest/README.md | 6 +- crates/egui_kittest/src/snapshot.rs | 88 +++++++++++++------ .../tests/snapshots/combobox_opened.png | 4 +- .../tests/snapshots/menu/closed_hovered.png | 4 +- .../tests/snapshots/menu/opened.png | 4 +- .../tests/snapshots/menu/submenu.png | 4 +- .../tests/snapshots/menu/subsubmenu.png | 4 +- .../override_text_color_interactive.png | 4 +- .../tests/snapshots/readme_example.png | 4 +- .../snapshots/should_wait_for_images.png | 4 +- .../tests/snapshots/layout/atoms_image.png | 4 +- .../tests/snapshots/layout/atoms_minimal.png | 4 +- .../snapshots/layout/atoms_multi_grow.png | 4 +- .../tests/snapshots/layout/button.png | 4 +- .../tests/snapshots/layout/button_image.png | 4 +- .../layout/button_image_shortcut.png | 4 +- .../tests/snapshots/layout/checkbox.png | 4 +- .../snapshots/layout/checkbox_checked.png | 4 +- .../tests/snapshots/layout/drag_value.png | 4 +- .../tests/snapshots/layout/radio.png | 4 +- .../tests/snapshots/layout/radio_checked.png | 4 +- .../snapshots/layout/selectable_value.png | 4 +- .../layout/selectable_value_selected.png | 4 +- .../tests/snapshots/layout/slider.png | 4 +- .../tests/snapshots/layout/text_edit.png | 4 +- .../egui_tests/tests/snapshots/max_width.png | 4 +- .../tests/snapshots/max_width_and_grow.png | 4 +- .../tests/snapshots/shrink_first_text.png | 4 +- .../tests/snapshots/shrink_last_text.png | 4 +- .../tests/snapshots/sides/default_long.png | 4 +- .../tests/snapshots/size_max_size.png | 4 +- .../visuals/button_image_shortcut.png | 4 +- .../button_image_shortcut_selected.png | 4 +- .../tests/snapshots/visuals/checkbox.png | 4 +- .../snapshots/visuals/checkbox_checked.png | 4 +- .../tests/snapshots/visuals/radio.png | 4 +- .../tests/snapshots/visuals/radio_checked.png | 4 +- .../snapshots/visuals/selectable_value.png | 4 +- .../visuals/selectable_value_selected.png | 4 +- .../tests/snapshots/visuals/slider.png | 4 +- 75 files changed, 213 insertions(+), 171 deletions(-) diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 31bb331ef..52e165c9b 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:0bd688ff74f9a096edab545fbcbf61b61a464183da066ae4a120ce1e2abf3e7b -size 334969 +oid sha256:991cd41c5c9c4e42d8408259a1f276c56470665ee924082405217a83f630c536 +size 334990 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index 2458cd8ba..18552e0f6 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:c80c4ae4c2bfbc5c91e9cd94213a4f87646fe910b4a7c747531a1efcf23def47 -size 92364 +oid sha256:187da3058c8311292b9fa3387cb0f059a4449ac982ab3a93743427bd7ed602f2 +size 92380 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png index e346a2779..abd66feb1 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7224afc6e728f60c28c027bf4be03d1f598dc70977274bcd32b7398d11dd36c7 -size 26416 +oid sha256:b768086b0f79d76f08b21ef315ad5333ae81aa5b592dfbf47535b40d10cc19bc +size 26422 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index dc0e224b1..94601b4d9 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cfc3ee54a0e64fb8b72d55e9fc2079aa2517b200665684076d63b87c381cdb9 -size 78704 +oid sha256:d36b7a37d8fcb727a015475f98b54552b37339e1087d0a1af3fd0fefe9a8b9cf +size 78742 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png index 778493612..007000864 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:629006243b61f25e48a454cc617b8e49e38985eebbfe136f3bcb0b361d204671 -size 61431 +oid sha256:3c03eadbd3ba486100303ab9b350d82d5ba31b7aa694641538e8b347a1bc18b0 +size 61400 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png index 90b310c4d..8fa22e08d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49b08c1fb7878d8670d96de9f9791e2db5cf7206812da1d9102c4dd1758cb803 -size 25833 +oid sha256:a63cee05ac47fdf52ae3a1398f7e230a29ffdfd62ac63f3acc74cedba9100069 +size 25840 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png index 2cbed60d2..2551a5218 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1523b8ad99267eceb65a9009ca38d99937e61c45a1115d050644f037cadfc16c -size 127794 +oid sha256:1e4ba53720713a8083997acb990fe4fc68ba4b9ffc7a4e58f17faa66de0da091 +size 128261 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index 41d3995db..e802bcec8 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:6d5f3129e34e22b15245212904e0a3537a0c7e70f1d35fd3e9c784af707038b5 -size 24018 +oid sha256:08deb70e760326a1274ebcbd51849aae958ace52ec1aab36fff93a9b8dd98f4d +size 24029 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 7dbb397fa..09ccf9c6a 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:5d05c74583024825d82f1fe8dbeb2a793e366016e87a639f51d46945831de82a +oid sha256:828ee860ed31930d67deb2b6efee8bf2aec3c3266cf05b5510d1565dc7090e2f size 99106 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index 3b1df931d..e8b6bd55b 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57e09bcf48541af11e44ff07122f09640e0329db0c2bc7a6ecb406a3ece572ac -size 17608 +oid sha256:403321533e97eac1aaf994d0ddeb5d53f06070a4b6a5c913c0ba22a3c60ad761 +size 17604 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png index dd67f7ad3..b7f376a3d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d31533f812b2b72410b5caafea9b647d3f4cc9da3db9fcf37c332cb57d58742 -size 111670 +oid sha256:e3978a1dd2479bab2f234411b49d414fa3cf43ae18025db570f3c7a84f4a16e7 +size 111696 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png index 440fbf0b0..40ea1255e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:502790125ddd0204ea8a468c80c6f3e824adcd98f5a3f626e97f3512d31e1074 -size 24516 +oid sha256:c6e29013cf12bd663cd7243baad03585c8d5b95d592f96aa8d7910abf5dcc2db +size 24541 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 aca535ad1..46e5daeae 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:e0a49139611dd5f4e97874e8f7b0e12b649da5f373ff7ee80a7ff678f7f8ecc7 -size 50321 +oid sha256:8071d1a913ed35c4edab01dcce28996ee1c365ca393e11fd3f99cef65c7736ec +size 50357 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png index 7800f5f5f..00958a6e0 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c47a19d1f56fcc4c30c7e88aada2a50e038d66c1b591b4646b86c11bffb3c66f -size 46563 +oid sha256:6cefcd7aa537e98e3a560fc458028f7e6b4f67e92343b94771f3ba21dce0298c +size 46666 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 0e2bdbf80..3e4c97ce5 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:17f7065c47712f140e4a9fd9eed61a7118fe12cd79cf0745642a02921eaa596b -size 24065 +oid sha256:9ff7b37547644d542e7fe3486954479d0e28ee30be37f7c938b820192eddcfb9 +size 24078 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index f11c5d8a7..1a65dbc7c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:116a53258be27d9c7c56538e5f83202ea731f19887fabadc0449d24fde4d80d9 -size 64494 +oid sha256:5211f7be04c3c28555cc3b53c798c165854441e9847d9bf7d67ad6b2fb0e72cc +size 64618 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index 8677f7300..a5036a36f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01705a1a49350278f524bbc5dbd47ae9da4b57ee7f6f34fb20186e1aa9b9f1d4 -size 35714 +oid sha256:99c68d8904a48205bbf02eb134715d289881be5b868949dbd22602f2a35a1d47 +size 35718 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index 060c60e01..d39c76efe 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03cb424100e99a141daeacc78036c4334d74cace3fae19bb878565ccda68457d -size 17448 +oid sha256:f8d3c5c2032c8eb36f60b2422eb8dd257b7b6417b1d93b120b3347e37a047c74 +size 17447 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index e87c842a1..87b675bc0 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:cfc5dd77728ee0b3d319c5851698305851b6713eb054a6eb5b618e9670f58ae5 -size 277018 +oid sha256:376657d119ace8d96b52bbf0e9daf379f8e0d5ee6a1be840b3efc210a02b6364 +size 277816 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png index 5dc0c2747..27b088e29 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ed78a559488474487c0a434a941e434b22354e4374d13059076d76da93bc609 -size 57051 +oid sha256:88cebf1a05400c967f8930271eb3f6a983fe82e9afa266682a2bd3862e2ab62f +size 57055 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 379a18cd3..7a57f9408 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63f5c3be15164e6f008fb09b4ff37eff2af0ab361de28d1994d595789c379df5 -size 23205 +oid sha256:15d023fcd551cbf7b643bae2591751e075fdcd8ef9386e5ab28ac2cd99c0efee +size 23230 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index f13fc54db..5d1261a7e 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:1bd15215f3ec1b365b8c51987f629d5653e4f40e84c34756aea0dc863af27c1e -size 179906 +oid sha256:059238da7f4eebf6f33da593ee5e756a710d1ea1c19ebab4494dd0f9362e822f +size 179995 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index c26e7e4f6..89e3c12f4 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:7e80bf8c79e6e431806c85385a0bd9262796efc0a1e74d431a1b896dde0b8651 -size 115338 +oid sha256:599ecaca9a324200096e445992de027fb9e9a93d56fc5cb918777aa306d20f58 +size 115446 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index bae04d3e8..40ba0313c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb7c844f6b745f66304ad036790a5121e4827fa91569b28ffa301794aecd0c66 -size 25592 +oid sha256:c8a0b214ec1cc325b88918f25da87bfdb469cbcc6bf6646a0ac0a1d5f21a26e6 +size 25614 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 8a269fd4e..f6e367f3d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d27ed8292a2612b337f663bff73cd009a82f806c61f0863bf70a53fd4c281ff -size 75074 +oid sha256:9b51b334db17a29df66e010c06d6cb3599013b569c068cf55b9603ed40c03eae +size 75313 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index fcf8b8f35..ef9969e53 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e177888e10f357f1be8ad80f7a0a33c93798c1e7c43cfe382119eeb12f21279f -size 64732 +oid sha256:1f74f765e6f187a9cf8f3ff254b1fa5826d0c2719ef904560d56aebae7526fa7 +size 64799 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index d39effd6b..3bdadb62e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e177e2631414784161a5556bdd1420ce8432f9859faede1a2e6f791a02814412 -size 20918 +oid sha256:e82ae562cf2c987624667cd9324e21726411802c2a901fe36d9f9e4e61c6b980 +size 20968 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index 852e21df3..0652b37ef 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c49c489fe1bb00512c9d08e8d8454fce786744f4ebff0bfd27dac68b7e67b815 -size 62317 +oid sha256:a328a035ebf0dd10b935902e8e17a8fce7455f2dcf21c90f44e44ce9e99c2677 +size 62318 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 b3dbb2ea1..6da000366 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:26e4828e42f54da24d032f384f8829e42bcebaee072923f277db582f84302911 -size 12847 +oid sha256:0bcca7b25740375f882acd6707b360092a78f96a1b7c807b5c22904a9c8efbd8 +size 12872 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 217419e00..6b889a87b 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:4a4520aa68d6752992fd2f87090a317e6e5e24b5cdb5ee2e82daf07f9471ca80 -size 35251 +oid sha256:288d8c2a5aa14fb1b9a7bb6dbc098c7cfbe5bb5391baf6566785d824affb9396 +size 35306 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png index bfa5643fd..4f39f7753 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c0ce7090ba12d849f9e3c77010503b394f3e1fce65c382738f55f7181fd7450 -size 42527 +oid sha256:b8c3dd4dd971ad19d6695f063779f0a25c9b4ad4a15d4e5e080ac5d832605d95 +size 42521 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index 81feea9f6..f38387d3c 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_1.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e954bf915d562abc69269cd10a4df8fbd0e5603929e6446fefa694099e2494a4 -size 47542 +oid sha256:0b1ef1ed7d902b72fac55a83865cff451c04c8ef230096a4dc420a699936334a +size 47540 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index a446fa177..991159212 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c7bd1a65b6c33eff2fe17f7af2dd731a03658abc2419f8722c0e9395b26fdef -size 47515 +oid sha256:630041fbea61422695bb52fb3d5b00ad877cb0a55cdc264d3d6c665e7d2339a7 +size 47502 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index 6cdf2d21d..ed47d5dbc 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e89cd220a925150384b9f9987b178036ffacfe29cdb36ed688205524dbb731fd -size 43803 +oid sha256:9c831bbc1f4f52e0b1d2868af3e5632717b2fd8df196b507151dcafc61ddbd45 +size 43772 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index f234c33ed..c14d596e2 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b71da58f5c0178517f9e0cc97753a0a5d1653cc5d094b5a35ffe050499bcd569 -size 43679 +oid sha256:7b5ecf62a1a4edbecb65948796695084d3ac82d6bdf6b80a5f09241e76987f64 +size 43676 diff --git a/crates/egui_kittest/README.md b/crates/egui_kittest/README.md index 31a55f618..64004a9ce 100644 --- a/crates/egui_kittest/README.md +++ b/crates/egui_kittest/README.md @@ -44,7 +44,11 @@ Once enabled, you can call `Harness::snapshot` to render the ui and save the ima To update the snapshots, run your tests with `UPDATE_SNAPSHOTS=true`, so e.g. `UPDATE_SNAPSHOTS=true cargo test`. Running with `UPDATE_SNAPSHOTS=true` will cause the tests to succeed. -This is so that you can set `UPDATE_SNAPSHOTS=true` and update _all_ tests, without `cargo test` failing on the first failing crate. +This is so that you can set `UPDATE_SNAPSHOTS=true` and update all tests, without `cargo test` failing on the first failing crate. + +`UPDATE_SNAPSHOTS=true` will only update the images of _failing_ tests. +If you want to update all snapshot images, even those that are within error margins, +run with `UPDATE_SNAPSHOTS=force`. If you want to have multiple snapshots in the same test, it makes sense to collect the results in a `Vec` ([look here](https://github.com/emilk/egui/blob/70a01138b77f9c5724a35a6ef750b9ae1ab9f2dc/crates/egui_demo_lib/src/demo/demo_app_windows.rs#L388-L427) for an example). diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index fea8ab8b6..d6c141635 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -285,14 +285,34 @@ impl Display for SnapshotError { } } -/// If this is set, we update the snapshots (if different), -/// and _succeed_ the test. -/// This is so that you can set `UPDATE_SNAPSHOTS=true` and update _all_ tests, -/// without `cargo test` failing on the first failing crate. -fn should_update_snapshots() -> bool { - match std::env::var("UPDATE_SNAPSHOTS") { - Ok(value) => !matches!(value.as_str(), "false" | "0" | "no" | "off"), - Err(_) => false, +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Mode { + Test, + UpdateFailing, + UpdateAll, +} + +impl Mode { + fn from_env() -> Self { + let Ok(value) = std::env::var("UPDATE_SNAPSHOTS") else { + return Self::Test; + }; + + match value.as_str() { + "false" | "0" | "no" | "off" => Self::Test, + "true" | "1" | "yes" | "on" => Self::UpdateFailing, + "force" => Self::UpdateAll, + unknown => { + panic!("Unsupported value for UPDATE_SNAPSHOTS: {unknown:?}"); + } + } + } + + fn is_update(&self) -> bool { + match self { + Self::Test => false, + Self::UpdateFailing | Self::UpdateAll => true, + } } } @@ -330,6 +350,8 @@ fn try_image_snapshot_options_impl( ) -> SnapshotResult { #![expect(clippy::print_stdout)] + let mode = Mode::from_env(); + let SnapshotOptions { threshold, output_path, @@ -386,7 +408,7 @@ fn try_image_snapshot_options_impl( Ok(image) => image.to_rgba8(), Err(err) => { // No previous snapshot - probablye a new test. - if should_update_snapshots() { + if mode.is_update() { return update_snapshot(); } else { return Err(SnapshotError::OpenSnapshot { @@ -398,7 +420,7 @@ fn try_image_snapshot_options_impl( }; if previous.dimensions() != new.dimensions() { - if should_update_snapshots() { + if mode.is_update() { return update_snapshot(); } else { return Err(SnapshotError::SizeMismatch { @@ -410,29 +432,45 @@ fn try_image_snapshot_options_impl( } // Compare existing image to the new one: - let result = - dify::diff::get_results(previous, new.clone(), *threshold, true, None, &None, &None); + let threshold = if mode == Mode::UpdateAll { + 0.0 + } else { + *threshold + }; - if let Some((num_wrong_pixels, result_image)) = result { - result_image + let result = + dify::diff::get_results(previous, new.clone(), threshold, true, None, &None, &None); + + if let Some((num_wrong_pixels, diff_image)) = result { + diff_image .save(diff_path.clone()) .map_err(|err| SnapshotError::WriteSnapshot { path: diff_path.clone(), err, })?; - if num_wrong_pixels as i64 <= *failed_pixel_count_threshold as i64 { - return Ok(()); - } + let is_sameish = num_wrong_pixels as i64 <= *failed_pixel_count_threshold as i64; - if should_update_snapshots() { - update_snapshot() - } else { - Err(SnapshotError::Diff { - name, - diff: num_wrong_pixels, - diff_path, - }) + match mode { + Mode::Test => { + if is_sameish { + Ok(()) + } else { + Err(SnapshotError::Diff { + name, + diff: num_wrong_pixels, + diff_path, + }) + } + } + Mode::UpdateFailing => { + if is_sameish { + Ok(()) + } else { + update_snapshot() + } + } + Mode::UpdateAll => update_snapshot(), } } else { Ok(()) diff --git a/crates/egui_kittest/tests/snapshots/combobox_opened.png b/crates/egui_kittest/tests/snapshots/combobox_opened.png index fd0262403..b406cf5d8 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_opened.png +++ b/crates/egui_kittest/tests/snapshots/combobox_opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21f70bc7146e43b6b10fe1e4cb32597d6f3507b42a6aa4c619c4e8c688ea4c85 -size 7290 +oid sha256:b3a4ea95569b812ea46bc706f91d5ac03fa18ef1707a726b729422e9e9b18680 +size 7305 diff --git a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png index f04762182..5e35fe18a 100644 --- a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png +++ b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ca504cc7ef988f122fcc099914e5b6f7c39a3a86c5869a0a982c4342c48058a -size 10516 +oid sha256:29074de792c3c266b451ed28c940d7278d533570efecf5a64ae8bbae2b851a52 +size 10533 diff --git a/crates/egui_kittest/tests/snapshots/menu/opened.png b/crates/egui_kittest/tests/snapshots/menu/opened.png index 8b0f757aa..655eaf443 100644 --- a/crates/egui_kittest/tests/snapshots/menu/opened.png +++ b/crates/egui_kittest/tests/snapshots/menu/opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:763447271686242b8a2deaa63fa1a5a0d57009ef93dd1bcb0ae906541cd7a6ea -size 21052 +oid sha256:8a12bfee8b9c6b6845ff3cadc40bdb707375f2177095fcd7f0b9ad1fcf1e9f00 +size 21118 diff --git a/crates/egui_kittest/tests/snapshots/menu/submenu.png b/crates/egui_kittest/tests/snapshots/menu/submenu.png index adb6f8fb0..c4f200799 100644 --- a/crates/egui_kittest/tests/snapshots/menu/submenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/submenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e54f7a1ea9ac74b62241c8b662579fd3c8442857b4569ce818342fb56dc30ae -size 28218 +oid sha256:0e70f7ec229d94919346192141272273ff2f56032e1ae3137cc04ffd21aabe51 +size 28361 diff --git a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png index 0b7919093..16d510eaf 100644 --- a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8443523a671d3c83456c6ee0503fdb59127a33d866c45635a84eee3596985fd -size 32831 +oid sha256:d3295fc14054c31efd7a5c6508f52c4a80b0fb8f91a452808bcfe3e4dddecc9c +size 32992 diff --git a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png index 0db4034a8..036cdd007 100644 --- a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png +++ b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60540cb1b5b71f100b2ea367a939cb9d93a91e56ff1f14ebfc988bbe79d69ac7 -size 19719 +oid sha256:e2ef3a6fb7d43c85b0f8cdb0eb7a784aa7ae5af9b61ae296e0d1a41871eb2724 +size 19713 diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index a3d4ca79a..2be9c9cdb 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:341658df1dfe665e79180d4540965a986a21de09c9cbc1a8744bdcff1a7c2086 -size 1892 +oid sha256:730bdd28319b140433802740728f096adda749014a1629114b18d6f674ee4d86 +size 1893 diff --git a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png index c6ee32d19..bb5924936 100644 --- a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png +++ b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6298e67d099002808d51e7494e4174adee66d7ef2880728126c1d761d1432372 -size 2145 +oid sha256:b8c872f818a2e88cce35d015d16406c225ab6792cc6bfd232de94dbf2665df26 +size 2142 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_image.png b/tests/egui_tests/tests/snapshots/layout/atoms_image.png index 3d9efa8a1..759c10b7a 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_image.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39a13fdac498d6f851a28ea3ca19d523235d5e0ab8e765ea980cf8fb2f64ba35 -size 387619 +oid sha256:a46aebd7c611b01885819c80a4622eea44681de7f4902cf4b5debccd4b8fc000 +size 387626 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png index 1eb0a8348..a4b0861ec 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a09e926d25e2b6f63dc6df00ab5e5b76745aae1f288231f1a602421b2bbb53b -size 384721 +oid sha256:73423d2df441a5851d7fb8100455b1126d83ada4409b191865d42609cb8badde +size 384699 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png index 87211765f..4ecf7a4e1 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14a1dc826aeced98cab1413f915dcbbe904b5b1eadfc4d811232bc8ccbe7f550 -size 299556 +oid sha256:fd3f36929cd6ba4caf12eee5e366387c8fc30addabc0fb0e34188aa8c0c8f5ef +size 299564 diff --git a/tests/egui_tests/tests/snapshots/layout/button.png b/tests/egui_tests/tests/snapshots/layout/button.png index 7232c72c8..51b6004b2 100644 --- a/tests/egui_tests/tests/snapshots/layout/button.png +++ b/tests/egui_tests/tests/snapshots/layout/button.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccd7bdd86e587bcf0577c92e10ed7c3c35195e37df109a84554ceb30a434768d -size 315482 +oid sha256:4caea23dd3b110412bb17aabfaeb99328c2f33c57426476e60ea96cedb58c1dd +size 315644 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png index fb6ff3b34..59c41c2fd 100644 --- a/tests/egui_tests/tests/snapshots/layout/button_image.png +++ b/tests/egui_tests/tests/snapshots/layout/button_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01309596ac9eb90b2dfc00074cfd39d26e3f6d1f83299f227cb4bbea9ccd3b66 -size 339917 +oid sha256:8659eefdffe496e06c4c46906f1821e8a481b2b5e58bc4a0589e26edbf97d3a9 +size 339845 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 f15fb0ce6..41bbdd066 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:d59882afca42e766dddc36450a3331ca247a130e3796f99d0335ac370a7c3610 -size 425517 +oid sha256:f4ceba068adcac969ddb20c3f249ab057e4f914a62b59fee0222b54b15ec8813 +size 424828 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox.png b/tests/egui_tests/tests/snapshots/layout/checkbox.png index b8c014727..b11e71adc 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:996e02c1c10a0c76fa295160d117aceb764ef506608b151bafbdf263106dbe57 -size 385129 +oid sha256:af4d7c3b64ac66df76a10283a4762382a8afd5cb0872bf28c000d771dced583d +size 386444 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png index 9c6fb4c07..ba2cdfb4b 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d842f88b6a94f19aa59bdae9dbbf42f4662aaead1b8f73ac0194f183112e1b8 -size 415066 +oid sha256:bccccd5a51455ccb7f6963baf8c8ce36b2097458f7e85c77333736bab27288f3 +size 416329 diff --git a/tests/egui_tests/tests/snapshots/layout/drag_value.png b/tests/egui_tests/tests/snapshots/layout/drag_value.png index a9a64c558..3433e6bd4 100644 --- a/tests/egui_tests/tests/snapshots/layout/drag_value.png +++ b/tests/egui_tests/tests/snapshots/layout/drag_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:043be3ece0697ea7114b7bd743e5c958610ae38ac359b6f8120886edff8541d8 -size 239522 +oid sha256:cf56d809f8fbb63462c4112217a869820d32be906565c2122d4fd1938c2206c2 +size 239539 diff --git a/tests/egui_tests/tests/snapshots/layout/radio.png b/tests/egui_tests/tests/snapshots/layout/radio.png index 1e1a1faf3..f71421a0e 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio.png +++ b/tests/egui_tests/tests/snapshots/layout/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f7fbeeba8ae9e34c5400727690ac7941e2711f72f2dc23e3342cb06904e4a35 -size 335775 +oid sha256:4a943ef2ebd13bcf87fc7d480b0eb1e91243236eb4affa6e17a9fd2c3ce14c4d +size 336517 diff --git a/tests/egui_tests/tests/snapshots/layout/radio_checked.png b/tests/egui_tests/tests/snapshots/layout/radio_checked.png index 323426ee9..fc2b0b36e 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/radio_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96ae7be40161b0b42959b44c8f72b62fd2cd4b3b463fc7d5bcd02ead445edca1 -size 355550 +oid sha256:591f5001e8c5a63d063929a7bb357658a1f07b2a1be93adac3790938de1fbbc4 +size 356858 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value.png b/tests/egui_tests/tests/snapshots/layout/selectable_value.png index 42794b19e..e3ea1e025 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4991fdf58542ca14162cbd7f59b6a30d6c3d752a1215cc1890359bc3a1eb23c9 -size 388912 +oid sha256:c3018bc027223a535541fa7e87d3654ab9604c5c4ca9db5751f9ff1f8e9f5e76 +size 389206 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png index 554bbbf41..baae7aafc 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ac8cbcdeed098d52009be77c8815931553d979f5aaf0baf0a9296daf6373605 -size 402699 +oid sha256:fe816c0eff10c65f4634b0e795ac49d07ebfb064cba7199561028db8fa85c05c +size 402833 diff --git a/tests/egui_tests/tests/snapshots/layout/slider.png b/tests/egui_tests/tests/snapshots/layout/slider.png index 9017347f2..a84359b51 100644 --- a/tests/egui_tests/tests/snapshots/layout/slider.png +++ b/tests/egui_tests/tests/snapshots/layout/slider.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:605091767a73a934981d10d0ed59ff561772ed61e7691303b75b35ae01163ecc -size 336722 +oid sha256:ce99e4a0da54c6349e270b4fdf6572db95eb997df6221cdc21f4aba8e123c7a3 +size 336775 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit.png b/tests/egui_tests/tests/snapshots/layout/text_edit.png index fae07202c..459b974e1 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:465e34d94bf734a2a7a1e8e4a71ce64c908c737a7c4fa2a6f812351f2aaa6808 -size 233018 +oid sha256:7825ca0dac7c330c269f4469ecf35c02cc147ed9d966ace5576fb2f52599449c +size 233025 diff --git a/tests/egui_tests/tests/snapshots/max_width.png b/tests/egui_tests/tests/snapshots/max_width.png index bab2f3876..43f3a59f0 100644 --- a/tests/egui_tests/tests/snapshots/max_width.png +++ b/tests/egui_tests/tests/snapshots/max_width.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90cfa6e9be28ef538491ad94615e162ecc107df6a320084ec30840a75660ac35 -size 8759 +oid sha256:c2a324562613323b6c94eaab6b7975576eb85f6d095ff818d2d36e116d370a8e +size 8752 diff --git a/tests/egui_tests/tests/snapshots/max_width_and_grow.png b/tests/egui_tests/tests/snapshots/max_width_and_grow.png index 077bccbd8..8f261d32d 100644 --- a/tests/egui_tests/tests/snapshots/max_width_and_grow.png +++ b/tests/egui_tests/tests/snapshots/max_width_and_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:effb4a69a7a6af12614be59a0afb0be2d2ebad402da3d7ee99fa25ae350bf4a0 -size 8761 +oid sha256:73e61063bf68035aebc244236747dd3af13d1c80df2c4cbbdacda8f796342e62 +size 8754 diff --git a/tests/egui_tests/tests/snapshots/shrink_first_text.png b/tests/egui_tests/tests/snapshots/shrink_first_text.png index c9196ea24..fb4152877 100644 --- a/tests/egui_tests/tests/snapshots/shrink_first_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_first_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf5032b2a08f993ae023934715222fe8d35a3a2e5cc09026d9e7ea3c296a9dc7 -size 11609 +oid sha256:7bcc46a2cf63d6361b28cf7b730a3bf717dbe87065c24c3ead6fddf593ea3080 +size 11606 diff --git a/tests/egui_tests/tests/snapshots/shrink_last_text.png b/tests/egui_tests/tests/snapshots/shrink_last_text.png index 038b70a2f..8cbdc8c06 100644 --- a/tests/egui_tests/tests/snapshots/shrink_last_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_last_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84d0c37a198fb56d8608a201dbe7ad19e7de7802bd5110316b36228e14b5f330 -size 12140 +oid sha256:db71fdcc89ca9f058559a2c2cd6bd1f0b7df1a0875d1d57f89a9437f14dfe649 +size 12136 diff --git a/tests/egui_tests/tests/snapshots/sides/default_long.png b/tests/egui_tests/tests/snapshots/sides/default_long.png index 2d66f3665..26e40ed63 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_long.png +++ b/tests/egui_tests/tests/snapshots/sides/default_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7ceaa95512c67dcbf1c8ba5a8f33bf4833c2e863d09903fb71b5aa2822cc086 -size 7889 +oid sha256:c4866b8bbefe79275480578f5465ec7485a642253f7ef66423e0f0e84b33a8d8 +size 7890 diff --git a/tests/egui_tests/tests/snapshots/size_max_size.png b/tests/egui_tests/tests/snapshots/size_max_size.png index 3ea8feab0..742ae57f7 100644 --- a/tests/egui_tests/tests/snapshots/size_max_size.png +++ b/tests/egui_tests/tests/snapshots/size_max_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6a7555290f6121d6e48657e3ae810976b540ee9328909aca2d6c078b3d76ab4 -size 8735 +oid sha256:161522c5bc65a03f3a12615729788b0a13b24a8b0d4d42de7f2c8a8f0ff46687 +size 8727 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 4be868a30..f633bd599 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:8ff776897760d300a4f26c10578be0d9afed7b4ae9f95f941914e641c2a10cb8 -size 13798 +oid sha256:7390b7a44bd0132d61f71598c3c8f13f0feba4f2e28b019dce9ab74ae17d9e14 +size 13783 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 ffabcae40..bcf0f01c7 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:9cd6a7f38c876cc345eae1a5e01f7668d4642b70181198fe0f09570815e47da8 -size 13489 +oid sha256:e3f62e86ef77c26dd2d54c5f94221506d8ad1517eeb26c107c6c321f9e94af6c +size 13474 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png index a0e6e18d7..00f4bd8b3 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1cd5e9ad416c3a0b6824debc343f196e6db90509fd201c60c7c1f9b022f37c1d -size 12322 +oid sha256:befff30cab57644f4e19e90d5d8a7d59093d57b8af3865c94ecffcb7ece9f2fb +size 12327 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index 113839a2f..f1b144d2e 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:ec75c3fccec8d6a72b808aba593f8c289618b6f95db08eb3cdb20a255b9d986e -size 13450 +oid sha256:a5dffe081a596a17928e1fd53df04c4eac69c0bcebc75ecec7fbc9133a5380b0 +size 13461 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png index 9c14f3032..8a364b32f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:583fa78f79b39522a44c871642114ead9ed1d177bb8a3807d2c9e2cd89bf0b44 -size 11076 +oid sha256:297dda97ae7cc4415c6679ad914a5fbee96cad527e98f2afc797b665d9b7f48f +size 11070 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index 8e89197e1..32e8a3944 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:895914fa37608ff68c5ae7fdd22d0363da26907c78d4980f6bf1ed19f7e5f388 -size 11697 +oid sha256:0bb35eac75aed7f0fcef4d467d894e4af459f0f3efe43c265384e25f31cde4c4 +size 11744 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png index c2cbd7334..49ddf6077 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eed80e11dd3ba478217cf004654934214b522ea666074e023dda9a323473615a -size 12452 +oid sha256:96057a478dd4242aa74fea9d11ff4f5181a0c414291dbabb536076af85b83542 +size 12453 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 67d80fed3..c9d3e32d2 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:0e0c4277eebadb0c350b5110d5ea7ff9292ab2b0231d6b36e9ada3aeefc7c198 -size 12510 +oid sha256:b02284574a8b5959d473cdc786ac4b323e0d7c0ebeca2933688cf51ef62a391d +size 12513 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index 7e868c0e7..1129add08 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:ec09e0e3432668c0d08bbba0aa8608c4eefba33d57f2335fdf105d144791406d -size 11036 +oid sha256:c4f3597075a77afbde2ddb7513d3309eb60f78cea6e5d4c108bbc5faed3ec937 +size 11035 From 73c5892a4db7ee06a139d84e3ec3e36826d6ad82 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 8 Sep 2025 10:13:21 +0200 Subject: [PATCH 206/388] Generate changelogs for emath (#7513) Sometimes there are changes that only affect emath, so those should be listed somewhere --- .github/workflows/labels.yml | 2 +- scripts/generate_changelog.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 2982b2a84..cac2f879c 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -30,4 +30,4 @@ jobs: with: mode: minimum count: 1 - labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui_kittest, egui-wgpu, egui-winit, egui, epaint, epaint_default_fonts, exclude from changelog, typo" + labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui_kittest, egui-wgpu, egui-winit, egui, emath, epaint, epaint_default_fonts, exclude from changelog, typo" diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py index 61986ee80..e54b18431 100755 --- a/scripts/generate_changelog.py +++ b/scripts/generate_changelog.py @@ -272,6 +272,7 @@ def main() -> None: "egui-wgpu", "egui-winit", "egui", + "emath", "epaint", "epaint_default_fonts", ] From 34cd6133783009ccfdbf9b87f49d591b846c270b Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 8 Sep 2025 10:42:10 +0200 Subject: [PATCH 207/388] Update changelogs and versions for 0.32.2 (#7505) --- CHANGELOG.md | 8 ++++++ Cargo.lock | 32 ++++++++++++------------ Cargo.toml | 26 +++++++++---------- crates/ecolor/CHANGELOG.md | 4 +++ crates/eframe/CHANGELOG.md | 4 +++ crates/egui-wgpu/CHANGELOG.md | 4 +++ crates/egui-winit/CHANGELOG.md | 4 +++ crates/egui_extras/CHANGELOG.md | 5 ++++ crates/egui_glow/CHANGELOG.md | 4 +++ crates/egui_kittest/CHANGELOG.md | 4 +++ crates/epaint/CHANGELOG.md | 5 ++++ crates/epaint_default_fonts/CHANGELOG.md | 4 +++ 12 files changed, 75 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2438ec9a..b8bf57b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,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.2 - 2025-09-04 +* Fix: `SubMenu` should not display when ui is disabled [#7428](https://github.com/emilk/egui/pull/7428) by [@ozwaldorf](https://github.com/ozwaldorf) +* Remove line breaks when pasting into single line TextEdit [#7441](https://github.com/emilk/egui/pull/7441) by [@YgorSouza](https://github.com/YgorSouza) +* Panic mutexes that can't lock for 30 seconds, in debug builds [#7468](https://github.com/emilk/egui/pull/7468) by [@emilk](https://github.com/emilk) +* Add `Ui::place`, to place widgets without changing the cursor [#7359](https://github.com/emilk/egui/pull/7359) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix: prevent calendar popup from closing on dropdown change [#7409](https://github.com/emilk/egui/pull/7409) by [@AStrizh](https://github.com/AStrizh) + + ## 0.32.1 - 2025-08-15 - Misc bug fixes ### ⭐ Added * Add `ComboBox::popup_style` [#7360](https://github.com/emilk/egui/pull/7360) by [@lucasmerlin](https://github.com/lucasmerlin) diff --git a/Cargo.lock b/Cargo.lock index e799591de..819c18bcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1196,7 +1196,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.32.1" +version = "0.32.2" dependencies = [ "bytemuck", "cint", @@ -1208,7 +1208,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.32.1" +version = "0.32.2" dependencies = [ "ahash", "bytemuck", @@ -1247,7 +1247,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.32.1" +version = "0.32.2" dependencies = [ "accesskit", "ahash", @@ -1267,7 +1267,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.32.1" +version = "0.32.2" dependencies = [ "ahash", "bytemuck", @@ -1285,7 +1285,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.32.1" +version = "0.32.2" dependencies = [ "accesskit_winit", "arboard", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.32.1" +version = "0.32.2" dependencies = [ "bytemuck", "chrono", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.32.1" +version = "0.32.2" dependencies = [ "chrono", "criterion", @@ -1350,7 +1350,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.32.1" +version = "0.32.2" dependencies = [ "ahash", "chrono", @@ -1369,7 +1369,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.32.1" +version = "0.32.2" dependencies = [ "bytemuck", "document-features", @@ -1388,7 +1388,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.32.1" +version = "0.32.2" dependencies = [ "dify", "document-features", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.32.1" +version = "0.32.2" dependencies = [ "egui", "egui_extras", @@ -1434,7 +1434,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.32.1" +version = "0.32.2" dependencies = [ "bytemuck", "document-features", @@ -1531,7 +1531,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.32.1" +version = "0.32.2" dependencies = [ "ab_glyph", "ahash", @@ -1553,7 +1553,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.32.1" +version = "0.32.2" [[package]] name = "equivalent" @@ -3239,7 +3239,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.32.1" +version = "0.32.2" dependencies = [ "eframe", "env_logger", @@ -5487,7 +5487,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.32.1" +version = "0.32.2" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index 33c91a2c5..f9e808989 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.86" -version = "0.32.1" +version = "0.32.2" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.32.1", path = "crates/emath", default-features = false } -ecolor = { version = "0.32.1", path = "crates/ecolor", default-features = false } -epaint = { version = "0.32.1", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.32.1", path = "crates/epaint_default_fonts" } -egui = { version = "0.32.1", path = "crates/egui", default-features = false } -egui-winit = { version = "0.32.1", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.32.1", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.32.1", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.32.1", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.32.1", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.32.1", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.32.1", path = "crates/eframe", default-features = false } +emath = { version = "0.32.2", path = "crates/emath", default-features = false } +ecolor = { version = "0.32.2", path = "crates/ecolor", default-features = false } +epaint = { version = "0.32.2", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.32.2", path = "crates/epaint_default_fonts" } +egui = { version = "0.32.2", path = "crates/egui", default-features = false } +egui-winit = { version = "0.32.2", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.32.2", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.32.2", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.32.2", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.32.2", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.32.2", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.32.2", path = "crates/eframe", default-features = false } accesskit = "0.19.0" accesskit_winit = "0.27" diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index c73787de7..b4c8d2ea1 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,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.2 - 2025-09-04 +Nothing new + + ## 0.32.1 - 2025-08-15 Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index b6dcb47d6..88aea16e6 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,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.2 - 2025-09-04 +Nothing new + + ## 0.32.1 - 2025-08-15 * Enable wgpu default features in eframe / egui_wgpu default features [#7344](https://github.com/emilk/egui/pull/7344) by [@lucasmerlin](https://github.com/lucasmerlin) * Request a redraw when the url change through the `popstate` event listener [#7403](https://github.com/emilk/egui/pull/7403) by [@irevoire](https://github.com/irevoire) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 476485c55..7ecf20997 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,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.2 - 2025-09-04 +Nothing new + + ## 0.32.1 - 2025-08-15 * Enable wgpu default features in eframe / egui_wgpu default features [#7344](https://github.com/emilk/egui/pull/7344) by [@lucasmerlin](https://github.com/lucasmerlin) diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 70eeb8c2a..4a4a26c72 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/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.2 - 2025-09-04 +Nothing new + + ## 0.32.1 - 2025-08-15 * Update to winit 0.30.12 [#7420](https://github.com/emilk/egui/pull/7420) by [@emilk](https://github.com/emilk) diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index d0f4f8ec6..f8693f21d 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,11 @@ 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.2 - 2025-09-04 +* Fix memory leak when `forget_image` is called while loading [#7380](https://github.com/emilk/egui/pull/7380) by [@Vanadiae](https://github.com/Vanadiae) +* Fix deadlock in `ImageLoader`, `FileLoader`, `EhttpLoader` [#7494](https://github.com/emilk/egui/pull/7494) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 0.32.1 - 2025-08-15 Nothing new diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 466898a80..12fd2f42d 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.2 - 2025-09-04 +* Allow masking widgets in kittest snapshots [#7467](https://github.com/emilk/egui/pull/7467) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 0.32.1 - 2025-08-15 * Fix `UPDATE_SNAPSHOTS`: only update if we didn't pass the test [#7455](https://github.com/emilk/egui/pull/7455) by [@emilk](https://github.com/emilk) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 4b28cea06..8d2c1521a 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,11 @@ 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.2 - 2025-09-04 +* Panic mutexes that can't lock for 30 seconds, in debug builds [#7468](https://github.com/emilk/egui/pull/7468) by [@emilk](https://github.com/emilk) +* Skip zero-length layout job sections [#7430](https://github.com/emilk/egui/pull/7430) by [@HactarCE](https://github.com/HactarCE) + + ## 0.32.1 - 2025-08-15 * Fix multi-line `TextShape` rotation [#7404](https://github.com/emilk/egui/pull/7404) by [@afishhh](https://github.com/afishhh) * Fix glyph rendering: clamp coverage to [0, 1] [#7415](https://github.com/emilk/egui/pull/7415) by [@emilk](https://github.com/emilk) diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index 4642118f6..54c1cca5f 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.2 - 2025-09-04 +Nothing new + + ## 0.32.1 - 2025-08-15 Nothing new From e5d0b9363387cd3407ecc8111aa07de97607ad8c Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 8 Sep 2025 15:41:05 +0200 Subject: [PATCH 208/388] Preserve text format in truncated label tooltip (#7514) * Related https://github.com/rerun-io/rerun/issues/10906 This changes the label hover ui to use the provided layout job instead of the text so that the text format is preserved. --- crates/egui/src/widgets/label.rs | 2 +- tests/egui_tests/tests/regression_tests.rs | 23 ++++++++++++++++++- .../hovering_should_preserve_text_format.png | 3 +++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 007a1291a..115e7551f 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -282,7 +282,7 @@ impl Widget for Label { if ui.is_rect_visible(response.rect) { if show_tooltip_when_elided && galley.elided { // Show the full (non-elided) text on hover: - response = response.on_hover_text(galley.text()); + response = response.on_hover_text(galley.job.clone()); } let response_color = if interactive { diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs index 9a33c9359..babfb2320 100644 --- a/tests/egui_tests/tests/regression_tests.rs +++ b/tests/egui_tests/tests/regression_tests.rs @@ -1,4 +1,4 @@ -use egui::{Image, include_image}; +use egui::{Color32, Image, Label, RichText, TextWrapMode, include_image}; use egui_kittest::Harness; use egui_kittest::kittest::Queryable as _; @@ -12,3 +12,24 @@ fn image_button_should_have_alt_text() { harness.get_by_label("Egui"); } + +#[test] +fn hovering_should_preserve_text_format() { + let mut harness = Harness::builder().with_size((200.0, 70.0)).build_ui(|ui| { + ui.add( + Label::new( + RichText::new("Long text that should be elided and has lots of styling") + .italics() + .underline() + .color(Color32::LIGHT_BLUE), + ) + .wrap_mode(TextWrapMode::Truncate), + ); + }); + + harness.get_by_label_contains("Long text").hover(); + + harness.run_steps(5); + + harness.snapshot("hovering_should_preserve_text_format"); +} diff --git a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png new file mode 100644 index 000000000..9aa37308f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc03cdc22b410ed90fdbbc45ced5c61027463132c490673170aab9044d683f88 +size 10254 From d5b0a6f446f462442c63b717b325a1003087465f Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:29:41 -0400 Subject: [PATCH 209/388] More even text kerning (#7431) Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 4 +- crates/egui/src/atomics/atom_ext.rs | 2 +- crates/egui/src/containers/window.rs | 2 +- crates/egui/src/context.rs | 146 ++--- crates/egui/src/debug_text.rs | 2 +- crates/egui/src/painter.rs | 20 +- crates/egui/src/style.rs | 5 - crates/egui/src/ui.rs | 17 +- crates/egui/src/widget_text.rs | 10 +- crates/egui/src/widgets/label.rs | 4 +- crates/egui/src/widgets/text_edit/builder.rs | 6 +- crates/egui_demo_app/src/apps/http_app.rs | 2 +- crates/egui_demo_app/src/frame_history.rs | 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_lib/benches/benchmark.rs | 30 +- crates/egui_demo_lib/src/demo/code_editor.rs | 2 +- crates/egui_demo_lib/src/demo/code_example.rs | 2 +- crates/egui_demo_lib/src/demo/font_book.rs | 11 +- .../src/demo/misc_demo_window.rs | 4 +- crates/egui_demo_lib/src/demo/scrolling.rs | 2 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- .../src/easy_mark/easy_mark_viewer.rs | 2 +- .../tests/snapshots/demos/Bézier Curve.png | 4 +- .../tests/snapshots/demos/Clipboard Test.png | 4 +- .../tests/snapshots/demos/Code Editor.png | 4 +- .../tests/snapshots/demos/Code Example.png | 4 +- .../tests/snapshots/demos/Cursor Test.png | 4 +- .../tests/snapshots/demos/Dancing Strings.png | 4 +- .../tests/snapshots/demos/Drag and Drop.png | 4 +- .../tests/snapshots/demos/Extra Viewport.png | 4 +- .../tests/snapshots/demos/Font Book.png | 4 +- .../tests/snapshots/demos/Frame.png | 4 +- .../tests/snapshots/demos/Grid Test.png | 4 +- .../tests/snapshots/demos/Highlighting.png | 4 +- .../tests/snapshots/demos/ID Test.png | 4 +- .../snapshots/demos/Input Event History.png | 4 +- .../tests/snapshots/demos/Input Test.png | 4 +- .../snapshots/demos/Interactive Container.png | 4 +- .../tests/snapshots/demos/Layout Test.png | 4 +- .../snapshots/demos/Manual Layout Test.png | 4 +- .../tests/snapshots/demos/Misc Demos.png | 4 +- .../tests/snapshots/demos/Modals.png | 4 +- .../tests/snapshots/demos/Multi Touch.png | 4 +- .../tests/snapshots/demos/Painting.png | 4 +- .../tests/snapshots/demos/Panels.png | 4 +- .../tests/snapshots/demos/Popups.png | 4 +- .../tests/snapshots/demos/SVG Test.png | 4 +- .../tests/snapshots/demos/Scene.png | 4 +- .../tests/snapshots/demos/Screenshot.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 4 +- .../tests/snapshots/demos/Sliders.png | 4 +- .../tests/snapshots/demos/Strip.png | 4 +- .../tests/snapshots/demos/Table.png | 4 +- .../snapshots/demos/Tessellation Test.png | 4 +- .../tests/snapshots/demos/Text Layout.png | 4 +- .../tests/snapshots/demos/TextEdit.png | 4 +- .../tests/snapshots/demos/Tooltips.png | 4 +- .../tests/snapshots/demos/Undo Redo.png | 4 +- .../tests/snapshots/demos/Window Options.png | 4 +- .../snapshots/demos/Window Resize Test.png | 4 +- .../tests/snapshots/modals_1.png | 4 +- .../tests/snapshots/modals_2.png | 4 +- .../tests/snapshots/modals_3.png | 4 +- ...rop_should_prevent_focusing_lower_area.png | 4 +- .../snapshots/rendering_test/dpi_1.00.png | 4 +- .../snapshots/rendering_test/dpi_1.25.png | 4 +- .../snapshots/rendering_test/dpi_1.50.png | 4 +- .../snapshots/rendering_test/dpi_1.67.png | 4 +- .../snapshots/rendering_test/dpi_1.75.png | 4 +- .../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 +- .../tessellation_test/Thin filled.png | 4 +- .../tessellation_test/Thin stroked.png | 4 +- .../snapshots/widget_gallery_dark_x1.png | 4 +- .../snapshots/widget_gallery_dark_x2.png | 4 +- .../snapshots/widget_gallery_light_x1.png | 4 +- .../snapshots/widget_gallery_light_x2.png | 4 +- .../tests/snapshots/combobox_closed.png | 4 +- .../tests/snapshots/combobox_opened.png | 4 +- .../tests/snapshots/image_snapshots.png | 4 +- .../tests/snapshots/menu/closed_hovered.png | 4 +- .../tests/snapshots/menu/opened.png | 4 +- .../tests/snapshots/menu/submenu.png | 4 +- .../tests/snapshots/menu/subsubmenu.png | 4 +- .../override_text_color_interactive.png | 4 +- .../tests/snapshots/readme_example.png | 4 +- .../snapshots/should_wait_for_images.png | 4 +- .../tests/snapshots/test_masking.png | 4 +- .../tests/snapshots/test_scroll_initial.png | 4 +- .../tests/snapshots/test_scroll_scrolled.png | 4 +- .../tests/snapshots/test_shrink.png | 4 +- crates/epaint/src/lib.rs | 2 +- crates/epaint/src/shapes/shape.rs | 4 +- crates/epaint/src/shapes/text_shape.rs | 7 +- crates/epaint/src/text/font.rs | 579 ++++++++++-------- crates/epaint/src/text/fonts.rs | 542 ++++++++-------- crates/epaint/src/text/mod.rs | 2 +- crates/epaint/src/text/text_layout.rs | 381 ++++++------ crates/epaint/src/text/text_layout_types.rs | 8 +- tests/egui_tests/tests/snapshots/grow_all.png | 4 +- .../hovering_should_preserve_text_format.png | 4 +- .../tests/snapshots/layout/atoms_image.png | 4 +- .../tests/snapshots/layout/atoms_minimal.png | 4 +- .../snapshots/layout/atoms_multi_grow.png | 4 +- .../tests/snapshots/layout/button.png | 4 +- .../tests/snapshots/layout/button_image.png | 4 +- .../layout/button_image_shortcut.png | 4 +- .../tests/snapshots/layout/checkbox.png | 4 +- .../snapshots/layout/checkbox_checked.png | 4 +- .../tests/snapshots/layout/drag_value.png | 4 +- .../tests/snapshots/layout/radio.png | 4 +- .../tests/snapshots/layout/radio_checked.png | 4 +- .../snapshots/layout/selectable_value.png | 4 +- .../layout/selectable_value_selected.png | 4 +- .../tests/snapshots/layout/slider.png | 4 +- .../tests/snapshots/layout/text_edit.png | 4 +- .../egui_tests/tests/snapshots/max_width.png | 4 +- .../tests/snapshots/max_width_and_grow.png | 4 +- .../tests/snapshots/shrink_first_text.png | 4 +- .../tests/snapshots/shrink_last_text.png | 4 +- .../tests/snapshots/sides/default_long.png | 4 +- .../sides/default_long_fit_contents.png | 4 +- .../tests/snapshots/sides/default_short.png | 4 +- .../sides/default_short_fit_contents.png | 4 +- .../snapshots/sides/shrink_left_long.png | 4 +- .../sides/shrink_left_long_fit_contents.png | 4 +- .../snapshots/sides/shrink_left_short.png | 4 +- .../sides/shrink_left_short_fit_contents.png | 4 +- .../snapshots/sides/shrink_right_long.png | 4 +- .../sides/shrink_right_long_fit_contents.png | 4 +- .../snapshots/sides/shrink_right_short.png | 4 +- .../sides/shrink_right_short_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_left_long.png | 4 +- .../sides/wrap_left_long_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_left_short.png | 4 +- .../sides/wrap_left_short_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_right_long.png | 4 +- .../sides/wrap_right_long_fit_contents.png | 4 +- .../snapshots/sides/wrap_right_short.png | 4 +- .../sides/wrap_right_short_fit_contents.png | 4 +- .../tests/snapshots/size_max_size.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 +- .../tests/snapshots/visuals/checkbox.png | 4 +- .../snapshots/visuals/checkbox_checked.png | 4 +- .../tests/snapshots/visuals/drag_value.png | 4 +- .../tests/snapshots/visuals/radio.png | 4 +- .../tests/snapshots/visuals/radio_checked.png | 4 +- .../snapshots/visuals/selectable_value.png | 4 +- .../visuals/selectable_value_selected.png | 4 +- .../tests/snapshots/visuals/slider.png | 4 +- .../tests/snapshots/visuals/text_edit.png | 4 +- 162 files changed, 1203 insertions(+), 1131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 819c18bcf..eec56131f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", diff --git a/crates/egui/src/atomics/atom_ext.rs b/crates/egui/src/atomics/atom_ext.rs index 0c34544d8..6d008b84b 100644 --- a/crates/egui/src/atomics/atom_ext.rs +++ b/crates/egui/src/atomics/atom_ext.rs @@ -60,7 +60,7 @@ pub trait AtomExt<'a> { { let font_selection = FontSelection::default(); let font_id = font_selection.resolve(ui.style()); - let height = ui.fonts(|f| f.row_height(&font_id)); + let height = ui.fonts_mut(|f| f.row_height(&font_id)); self.atom_max_height(height) } } diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 39190d7a6..c3c45dfe4 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -476,7 +476,7 @@ impl Window<'_> { let (title_bar_height_with_margin, title_content_spacing) = if with_title_bar { let style = ctx.style(); let title_bar_inner_height = ctx - .fonts(|fonts| title.font_height(fonts, &style)) + .fonts_mut(|fonts| title.font_height(fonts, &style)) .at_least(style.spacing.interact_size.y); let title_bar_inner_height = title_bar_inner_height + window_frame.inner_margin.sum().y; let half_height = (title_bar_inner_height / 2.0).round() as _; diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 8dbbc2c1d..4360f66ce 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2,15 +2,15 @@ use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration}; -use emath::{GuiRounding as _, OrderedFloat}; +use emath::GuiRounding as _; use epaint::{ - ClippedPrimitive, ClippedShape, Color32, ImageData, ImageDelta, Pos2, Rect, StrokeKind, - TessellationOptions, TextureAtlas, TextureId, Vec2, + ClippedPrimitive, ClippedShape, Color32, ImageData, Pos2, Rect, StrokeKind, + TessellationOptions, TextureId, Vec2, emath::{self, TSTransform}, mutex::RwLock, stats::PaintStats, tessellator, - text::{FontInsert, FontPriority, Fonts}, + text::{FontInsert, FontPriority, Fonts, FontsView}, vec2, }; @@ -406,12 +406,7 @@ impl ViewportRepaintInfo { #[derive(Default)] struct ContextImpl { - /// Since we could have multiple viewports across multiple monitors with - /// different `pixels_per_point`, we need a `Fonts` instance for each unique - /// `pixels_per_point`. - /// This is because the `Fonts` depend on `pixels_per_point` for the font atlas - /// as well as kerning, font sizes, etc. - fonts: std::collections::BTreeMap, Fonts>, + fonts: Option, font_definitions: FontDefinitions, memory: Memory, @@ -575,12 +570,11 @@ impl ContextImpl { fn update_fonts_mut(&mut self) { profiling::function_scope!(); let input = &self.viewport().input; - let pixels_per_point = input.pixels_per_point(); let max_texture_side = input.max_texture_side; if let Some(font_definitions) = self.memory.new_font_definitions.take() { // New font definition loaded, so we need to reload all fonts. - self.fonts.clear(); + self.fonts = None; self.font_definitions = font_definitions; #[cfg(feature = "log")] log::trace!("Loading new font definitions"); @@ -589,7 +583,7 @@ impl ContextImpl { if !self.memory.add_fonts.is_empty() { let fonts = self.memory.add_fonts.drain(..); for font in fonts { - self.fonts.clear(); // recreate all the fonts + self.fonts = None; // recreate all the fonts for family in font.families { let fam = self .font_definitions @@ -614,26 +608,22 @@ impl ContextImpl { let mut is_new = false; - let fonts = self - .fonts - .entry(pixels_per_point.into()) - .or_insert_with(|| { - #[cfg(feature = "log")] - log::trace!("Creating new Fonts for pixels_per_point={pixels_per_point}"); + let fonts = self.fonts.get_or_insert_with(|| { + #[cfg(feature = "log")] + log::trace!("Creating new Fonts"); - is_new = true; - profiling::scope!("Fonts::new"); - Fonts::new( - pixels_per_point, - max_texture_side, - text_alpha_from_coverage, - self.font_definitions.clone(), - ) - }); + is_new = true; + profiling::scope!("Fonts::new"); + Fonts::new( + 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, text_alpha_from_coverage); + fonts.begin_pass(max_texture_side, text_alpha_from_coverage); } if is_new && self.memory.options.preload_font_glyphs { @@ -641,7 +631,10 @@ impl ContextImpl { // Preload the most common characters for the most common fonts. // This is not very important to do, but may save a few GPU operations. for font_id in self.memory.options.style().text_styles.values() { - fonts.lock().fonts.font(font_id).preload_common_characters(); + fonts + .fonts + .font(&font_id.family) + .preload_common_characters(); } } } @@ -1049,13 +1042,32 @@ impl Context { /// Not valid until first call to [`Context::run()`]. /// That's because since we don't know the proper `pixels_per_point` until then. #[inline] - pub fn fonts(&self, reader: impl FnOnce(&Fonts) -> R) -> R { + pub fn fonts(&self, reader: impl FnOnce(&FontsView<'_>) -> R) -> R { self.write(move |ctx| { let pixels_per_point = ctx.pixels_per_point(); reader( - ctx.fonts - .get(&pixels_per_point.into()) - .expect("No fonts available until first call to Context::run()"), + &ctx.fonts + .as_mut() + .expect("No fonts available until first call to Context::run()") + .with_pixels_per_point(pixels_per_point), + ) + }) + } + + /// Read-write access to [`Fonts`]. + /// + /// Not valid until first call to [`Context::run()`]. + /// That's because since we don't know the proper `pixels_per_point` until then. + #[inline] + pub fn fonts_mut(&self, reader: impl FnOnce(&mut FontsView<'_>) -> R) -> R { + self.write(move |ctx| { + let pixels_per_point = ctx.pixels_per_point(); + reader( + &mut ctx + .fonts + .as_mut() + .expect("No fonts available until first call to Context::run()") + .with_pixels_per_point(pixels_per_point), ) }) } @@ -1568,9 +1580,8 @@ impl Context { } = ModifierNames::SYMBOLS; let font_id = TextStyle::Body.resolve(&self.style()); - self.fonts(|f| { - let mut lock = f.lock(); - let font = lock.fonts.font(&font_id); + self.fonts_mut(|f| { + let mut font = f.fonts.font(&font_id.family); font.has_glyphs(alt) && font.has_glyphs(ctrl) && font.has_glyphs(shift) @@ -1920,14 +1931,12 @@ impl Context { pub fn set_fonts(&self, font_definitions: FontDefinitions) { profiling::function_scope!(); - let pixels_per_point = self.pixels_per_point(); - let mut update_fonts = true; self.read(|ctx| { - if let Some(current_fonts) = ctx.fonts.get(&pixels_per_point.into()) { + if let Some(current_fonts) = ctx.fonts.as_ref() { // NOTE: this comparison is expensive since it checks TTF data for equality - if current_fonts.lock().fonts.definitions() == &font_definitions { + if current_fonts.definitions() == &font_definitions { update_fonts = false; // no need to update } } @@ -1948,15 +1957,11 @@ impl Context { pub fn add_font(&self, new_font: FontInsert) { profiling::function_scope!(); - let pixels_per_point = self.pixels_per_point(); - let mut update_fonts = true; self.read(|ctx| { - if let Some(current_fonts) = ctx.fonts.get(&pixels_per_point.into()) { + if let Some(current_fonts) = ctx.fonts.as_ref() { if current_fonts - .lock() - .fonts .definitions() .font_data .contains_key(&new_font.name) @@ -2449,30 +2454,12 @@ impl ContextImpl { self.memory.end_pass(&viewport.this_pass.used_ids); - if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) { + if let Some(fonts) = self.fonts.as_mut() { let tex_mngr = &mut self.tex_manager.0.write(); if let Some(font_image_delta) = fonts.font_image_delta() { // A partial font atlas update, e.g. a new glyph has been entered. tex_mngr.set(TextureId::default(), font_image_delta); } - - if 1 < self.fonts.len() { - // We have multiple different `pixels_per_point`, - // e.g. because we have many viewports spread across - // monitors with different DPI scaling. - // All viewports share the same texture namespace and renderer, - // so the all use `TextureId::default()` for the font texture. - // This is a problem. - // We solve this with a hack: we always upload the full font atlas - // every frame, for all viewports. - // This ensures it is up-to-date, solving - // https://github.com/emilk/egui/issues/3664 - // at the cost of a lot of performance. - // (This will override any smaller delta that was uploaded above.) - profiling::scope!("full_font_atlas_update"); - let full_delta = ImageDelta::full(fonts.image(), TextureAtlas::texture_options()); - tex_mngr.set(TextureId::default(), full_delta); - } } // Inform the backend of all textures that have been updated (including font atlas). @@ -2615,24 +2602,6 @@ impl ContextImpl { self.memory.set_viewport_id(viewport_id); } - let active_pixels_per_point: std::collections::BTreeSet> = self - .viewports - .values() - .map(|v| v.input.pixels_per_point.into()) - .collect(); - self.fonts.retain(|pixels_per_point, _| { - if active_pixels_per_point.contains(pixels_per_point) { - true - } else { - #[cfg(feature = "log")] - log::trace!( - "Freeing Fonts with pixels_per_point={} because it is no longer needed", - pixels_per_point.into_inner() - ); - false - } - }); - platform_output.num_completed_passes += 1; FullOutput { @@ -2664,7 +2633,7 @@ impl Context { self.write(|ctx| { let tessellation_options = ctx.memory.options.tessellation_options; - let texture_atlas = if let Some(fonts) = ctx.fonts.get(&pixels_per_point.into()) { + let texture_atlas = if let Some(fonts) = ctx.fonts.as_ref() { fonts.texture_atlas() } else { #[cfg(feature = "log")] @@ -2673,13 +2642,8 @@ impl Context { .iter() .next() .expect("No fonts loaded") - .1 .texture_atlas() }; - let (font_tex_size, prepared_discs) = { - let atlas = texture_atlas.lock(); - (atlas.size(), atlas.prepared_discs()) - }; let paint_stats = PaintStats::from_shapes(&shapes); let clipped_primitives = { @@ -2687,8 +2651,8 @@ impl Context { tessellator::Tessellator::new( pixels_per_point, tessellation_options, - font_tex_size, - prepared_discs, + texture_atlas.size(), + texture_atlas.prepared_discs(), ) .tessellate_shapes(shapes) }; diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index 2cd1a2755..c3f938711 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -98,7 +98,7 @@ impl State { { // Paint location to left of `pos`: let location_galley = - ctx.fonts(|f| f.layout(location, font_id.clone(), color, f32::INFINITY)); + ctx.fonts_mut(|f| f.layout(location, font_id.clone(), color, f32::INFINITY)); let location_rect = Align2::RIGHT_TOP.anchor_size(pos - 4.0 * Vec2::X, location_galley.size()); painter.galley(location_rect.min, location_galley, color); diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index fe273970e..f121c8557 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use emath::GuiRounding as _; use epaint::{ CircleShape, ClippedShape, CornerRadius, PathStroke, RectShape, Shape, Stroke, StrokeKind, - text::{Fonts, Galley, LayoutJob}, + text::{FontsView, Galley, LayoutJob}, }; use crate::{ @@ -141,14 +141,22 @@ impl Painter { self.pixels_per_point } - /// Read-only access to the shared [`Fonts`]. + /// Read-only access to the shared [`FontsView`]. /// /// See [`Context`] documentation for how locks work. #[inline] - pub fn fonts(&self, reader: impl FnOnce(&Fonts) -> R) -> R { + pub fn fonts(&self, reader: impl FnOnce(&FontsView<'_>) -> R) -> R { self.ctx.fonts(reader) } + /// Read-write access to the shared [`FontsView`]. + /// + /// See [`Context`] documentation for how locks work. + #[inline] + pub fn fonts_mut(&self, reader: impl FnOnce(&mut FontsView<'_>) -> R) -> R { + self.ctx.fonts_mut(reader) + } + /// Where we paint #[inline] pub fn layer_id(&self) -> LayerId { @@ -525,7 +533,7 @@ impl Painter { color: crate::Color32, wrap_width: f32, ) -> Arc { - self.fonts(|f| f.layout(text, font_id, color, wrap_width)) + self.fonts_mut(|f| f.layout(text, font_id, color, wrap_width)) } /// Will line break at `\n`. @@ -539,7 +547,7 @@ impl Painter { font_id: FontId, color: crate::Color32, ) -> Arc { - self.fonts(|f| f.layout(text, font_id, color, f32::INFINITY)) + self.fonts_mut(|f| f.layout(text, font_id, color, f32::INFINITY)) } /// Lay out this text layut job in a galley. @@ -548,7 +556,7 @@ impl Painter { #[inline] #[must_use] pub fn layout_job(&self, layout_job: LayoutJob) -> Arc { - self.fonts(|f| f.layout_job(layout_job)) + self.fonts_mut(|f| f.layout_job(layout_job)) } /// Paint text that has already been laid out in a [`Galley`]. diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index ff8043beb..f43a549a3 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2790,7 +2790,6 @@ impl Widget for &mut FontTweak { scale, y_offset_factor, y_offset, - baseline_offset_factor, } = self; ui.label("Scale"); @@ -2806,10 +2805,6 @@ impl Widget for &mut FontTweak { ui.add(DragValue::new(y_offset).speed(-0.02)); ui.end_row(); - ui.label("baseline_offset_factor"); - ui.add(DragValue::new(baseline_offset_factor).speed(-0.0025)); - ui.end_row(); - if ui.button("Reset").clicked() { *self = Default::default(); } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index f595469b4..357e13530 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -3,6 +3,7 @@ use emath::GuiRounding as _; use epaint::mutex::RwLock; +use epaint::text::FontsView; use std::{any::Any, hash::Hash, sync::Arc}; use crate::ClosableTag; @@ -16,9 +17,7 @@ use crate::{ WidgetRect, WidgetText, containers::{CollapsingHeader, CollapsingResponse, Frame}, ecolor::Hsva, - emath, epaint, - epaint::text::Fonts, - grid, + emath, epaint, grid, layout::{Direction, Layout}, pass_state, placer::Placer, @@ -735,7 +734,7 @@ impl Ui { /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub fn text_style_height(&self, style: &TextStyle) -> f32 { - self.fonts(|f| f.row_height(&style.resolve(self.style()))) + self.fonts_mut(|f| f.row_height(&style.resolve(self.style()))) } /// Screen-space rectangle for clipping what we paint in this ui. @@ -847,11 +846,17 @@ impl Ui { self.ctx().output_mut(writer) } - /// Read-only access to [`Fonts`]. + /// Read-only access to [`FontsView`]. #[inline] - pub fn fonts(&self, reader: impl FnOnce(&Fonts) -> R) -> R { + pub fn fonts(&self, reader: impl FnOnce(&FontsView<'_>) -> R) -> R { self.ctx().fonts(reader) } + + /// Read-write access to [`FontsView`]. + #[inline] + pub fn fonts_mut(&self, reader: impl FnOnce(&mut FontsView<'_>) -> R) -> R { + self.ctx().fonts_mut(reader) + } } // ------------------------------------------------------------------------ diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index eb0111a76..c66e38d70 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -307,7 +307,7 @@ impl RichText { /// Read the font height of the selected text style. /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - pub fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 { + pub fn font_height(&self, fonts: &mut epaint::FontsView<'_>, style: &Style) -> f32 { let mut font_id = self.text_style.as_ref().map_or_else( || FontSelection::Default.resolve(style), |text_style| text_style.resolve(style), @@ -676,7 +676,7 @@ impl WidgetText { } /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - pub(crate) fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 { + pub(crate) fn font_height(&self, fonts: &mut epaint::FontsView<'_>, style: &Style) -> f32 { match self { Self::Text(_) => fonts.row_height(&FontSelection::Default.resolve(style)), Self::RichText(text) => text.font_height(fonts, style), @@ -762,7 +762,7 @@ impl WidgetText { }, ); layout_job.wrap = text_wrapping; - ctx.fonts(|f| f.layout_job(layout_job)) + ctx.fonts_mut(|f| f.layout_job(layout_job)) } Self::RichText(text) => { let mut layout_job = Arc::unwrap_or_clone(text).into_layout_job( @@ -771,12 +771,12 @@ impl WidgetText { default_valign, ); layout_job.wrap = text_wrapping; - ctx.fonts(|f| f.layout_job(layout_job)) + ctx.fonts_mut(|f| f.layout_job(layout_job)) } Self::LayoutJob(job) => { let mut job = Arc::unwrap_or_clone(job); job.wrap = text_wrapping; - ctx.fonts(|f| f.layout_job(job)) + ctx.fonts_mut(|f| f.layout_job(job)) } Self::Galley(galley) => galley, } diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 115e7551f..e0f21fd11 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -211,7 +211,7 @@ impl Label { if let Some(first_section) = layout_job.sections.first_mut() { first_section.leading_space = first_row_indentation; } - let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); + let galley = ui.fonts_mut(|fonts| fonts.layout_job(layout_job)); let pos = pos2(ui.max_rect().left(), ui.cursor().top()); assert!(!galley.rows.is_empty(), "Galleys are never empty"); @@ -252,7 +252,7 @@ impl Label { layout_job.justify = ui.layout().horizontal_justify(); } - let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); + let galley = ui.fonts_mut(|fonts| fonts.layout_job(layout_job)); 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 { diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 63dec9238..20cd079b0 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -266,7 +266,7 @@ impl<'t> TextEdit<'t> { /// let mut layouter = |ui: &egui::Ui, buf: &dyn egui::TextBuffer, wrap_width: f32| { /// let mut layout_job: egui::text::LayoutJob = my_memoized_highlighter(buf.as_str()); /// layout_job.wrap.max_width = wrap_width; - /// ui.fonts(|f| f.layout_job(layout_job)) + /// ui.fonts_mut(|f| f.layout_job(layout_job)) /// }; /// ui.add(egui::TextEdit::multiline(&mut my_code).layouter(&mut layouter)); /// # }); @@ -504,7 +504,7 @@ impl TextEdit<'_> { let hint_text_str = hint_text.text().to_owned(); let font_id = font_selection.resolve(ui.style()); - let row_height = ui.fonts(|f| f.row_height(&font_id)); + let row_height = ui.fonts_mut(|f| f.row_height(&font_id)); const MIN_WIDTH: f32 = 24.0; // Never make a [`TextEdit`] more narrow than this. let available_width = (ui.available_width() - margin.sum().x).at_least(MIN_WIDTH); let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width); @@ -522,7 +522,7 @@ impl TextEdit<'_> { } else { LayoutJob::simple_singleline(text, font_id_clone.clone(), text_color) }; - ui.fonts(|f| f.layout_job(layout_job)) + ui.fonts_mut(|f| f.layout_job(layout_job)) }; let layouter = layouter.unwrap_or(&mut default_layouter); diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index 2aed2adb7..f16aa5969 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -238,7 +238,7 @@ impl ColoredText { pub fn ui(&self, ui: &mut egui::Ui) { let mut job = self.0.clone(); job.wrap.max_width = ui.available_width(); - let galley = ui.fonts(|f| f.layout_job(job)); + let galley = ui.fonts_mut(|f| f.layout_job(job)); ui.add(egui::Label::new(galley).selectable(true)); } } diff --git a/crates/egui_demo_app/src/frame_history.rs b/crates/egui_demo_app/src/frame_history.rs index 0b34f858a..52ff8342e 100644 --- a/crates/egui_demo_app/src/frame_history.rs +++ b/crates/egui_demo_app/src/frame_history.rs @@ -90,7 +90,7 @@ impl FrameHistory { )); let cpu_usage = to_screen.inverse().transform_pos(pointer_pos).y; let text = format!("{:.1} ms", 1e3 * cpu_usage); - shapes.push(ui.fonts(|f| { + shapes.push(ui.fonts_mut(|f| { Shape::text( f, pos2(rect.left(), y), diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 52e165c9b..3ccc458fc 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:991cd41c5c9c4e42d8408259a1f276c56470665ee924082405217a83f630c536 -size 334990 +oid sha256:c9a4ac7d3f959100ae760e32b1fa35ba5bbaa0dd3b3d25472cd9c311430e766e +size 335270 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index 18552e0f6..7d9c5dfd8 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:187da3058c8311292b9fa3387cb0f059a4449ac982ab3a93743427bd7ed602f2 -size 92380 +oid sha256:2c4f0263b6ab61b9b589f6cb7b83ed020e0131bea88a7d2db24a8b2dff026f69 +size 93090 diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index f06e16cba..c44c6d3a1 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:fc3dbdcd483d4da7a9c1a00f0245a7882997fbcd2d26f8d6a6d2d855f3382063 -size 179724 +oid sha256:3ee4ef83971dc7df940326444723bc9ebd57378476686085d68574c35d3d6513 +size 182173 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index e6f108e98..629dbb24c 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:c8ad2c2d494e2287b878049091688069e4d86b69ae72b89cb7ecbe47d8c35e33 -size 100766 +oid sha256:47792205570445b5fc3a64ffbc6a4804c7b23b074af90ed1baca691f73f96963 +size 102366 diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 02f098e67..48e7d5207 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -165,14 +165,12 @@ pub fn criterion_benchmark(c: &mut Criterion) { let wrap_width = 512.0; let font_id = egui::FontId::default(); let text_color = egui::Color32::WHITE; - let fonts = egui::epaint::text::Fonts::new( - pixels_per_point, + let mut fonts = egui::epaint::text::Fonts::new( max_texture_side, egui::epaint::AlphaFromCoverage::default(), egui::FontDefinitions::default(), ); { - let mut locked_fonts = fonts.lock(); c.bench_function("text_layout_uncached", |b| { b.iter(|| { use egui::epaint::text::{LayoutJob, layout}; @@ -183,13 +181,13 @@ pub fn criterion_benchmark(c: &mut Criterion) { text_color, wrap_width, ); - layout(&mut locked_fonts.fonts, job.into()) + layout(&mut fonts.fonts, pixels_per_point, job.into()) }); }); } c.bench_function("text_layout_cached", |b| { b.iter(|| { - fonts.layout( + fonts.with_pixels_per_point(pixels_per_point).layout( LOREM_IPSUM_LONG.to_owned(), font_id.clone(), text_color, @@ -211,24 +209,30 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut rng = rand::rng(); b.iter(|| { - fonts.begin_pass( - pixels_per_point, - max_texture_side, - egui::epaint::AlphaFromCoverage::default(), - ); + fonts.begin_pass(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(); let idx = rng.random_range(0..string.len()); new_string.remove(idx); - fonts.layout(new_string, font_id.clone(), text_color, wrap_width); + fonts.with_pixels_per_point(pixels_per_point).layout( + new_string, + font_id.clone(), + text_color, + wrap_width, + ); }); }); - let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, text_color, wrap_width); + let galley = fonts.with_pixels_per_point(pixels_per_point).layout( + LOREM_IPSUM_LONG.to_owned(), + font_id, + text_color, + wrap_width, + ); let font_image_size = fonts.font_image_size(); - let prepared_discs = fonts.texture_atlas().lock().prepared_discs(); + let prepared_discs = fonts.texture_atlas().prepared_discs(); let mut tessellator = egui::epaint::Tessellator::new( 1.0, Default::default(), diff --git a/crates/egui_demo_lib/src/demo/code_editor.rs b/crates/egui_demo_lib/src/demo/code_editor.rs index 2d67f7d46..6a4720311 100644 --- a/crates/egui_demo_lib/src/demo/code_editor.rs +++ b/crates/egui_demo_lib/src/demo/code_editor.rs @@ -85,7 +85,7 @@ impl crate::View for CodeEditor { language, ); layout_job.wrap.max_width = wrap_width; - ui.fonts(|f| f.layout_job(layout_job)) + ui.fonts_mut(|f| f.layout_job(layout_job)) }; egui::ScrollArea::vertical().show(ui, |ui| { diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 52cafad0c..23f561506 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -85,7 +85,7 @@ impl CodeExample { ui.horizontal(|ui| { let font_id = egui::TextStyle::Monospace.resolve(ui.style()); - let indentation = 2.0 * 4.0 * ui.fonts(|f| f.glyph_width(&font_id, ' ')); + let indentation = 2.0 * 4.0 * ui.fonts_mut(|f| f.glyph_width(&font_id, ' ')); ui.add_space(indentation); egui::Grid::new("code_samples") diff --git a/crates/egui_demo_lib/src/demo/font_book.rs b/crates/egui_demo_lib/src/demo/font_book.rs index 1ef9867f9..39acf844a 100644 --- a/crates/egui_demo_lib/src/demo/font_book.rs +++ b/crates/egui_demo_lib/src/demo/font_book.rs @@ -77,7 +77,7 @@ impl crate::View for FontBook { let available_glyphs = self .available_glyphs .entry(self.font_id.family.clone()) - .or_insert_with(|| available_characters(ui, self.font_id.family.clone())); + .or_insert_with(|| available_characters(ui, &self.font_id.family)); ui.separator(); @@ -140,11 +140,10 @@ fn char_info_ui(ui: &mut egui::Ui, chr: char, glyph_info: &GlyphInfo, font_id: e }); } -fn available_characters(ui: &egui::Ui, family: egui::FontFamily) -> BTreeMap { - ui.fonts(|f| { - f.lock() - .fonts - .font(&egui::FontId::new(10.0, family)) // size is arbitrary for getting the characters +fn available_characters(ui: &egui::Ui, family: &egui::FontFamily) -> BTreeMap { + ui.fonts_mut(|f| { + f.fonts + .font(family) .characters() .iter() .filter(|(chr, _fonts)| !chr.is_whitespace() && !chr.is_ascii_control()) 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 b5a2402b7..bb62f1822 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -213,7 +213,7 @@ fn label_ui(ui: &mut egui::Ui) { ui.horizontal_wrapped(|ui| { // Trick so we don't have to add spaces in the text below: - let width = ui.fonts(|f|f.glyph_width(&TextStyle::Body.resolve(ui.style()), ' ')); + let width = ui.fonts_mut(|f|f.glyph_width(&TextStyle::Body.resolve(ui.style()), ' ')); ui.spacing_mut().item_spacing.x = width; ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110))); @@ -792,7 +792,7 @@ impl TextRotation { let start_pos = self.size / 2.0; - let s = ui.ctx().fonts(|f| { + let s = ui.ctx().fonts_mut(|f| { let mut t = egui::Shape::text( f, rect.min + start_pos, diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index ca3f6e90c..63fc81dbb 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -191,7 +191,7 @@ fn huge_content_painter(ui: &mut egui::Ui) { ui.add_space(4.0); let font_id = TextStyle::Body.resolve(ui.style()); - let row_height = ui.fonts(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y; + let row_height = ui.fonts_mut(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y; let num_rows = 10_000; ScrollArea::vertical() 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 47d0beeaf..976bdd394 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 @@ -83,7 +83,7 @@ impl EasyMarkEditor { let mut layouter = |ui: &egui::Ui, easymark: &dyn TextBuffer, wrap_width: f32| { let mut layout_job = highlighter.highlight(ui.style(), easymark.as_str()); layout_job.wrap.max_width = wrap_width; - ui.fonts(|f| f.layout_job(layout_job)) + ui.fonts_mut(|f| f.layout_job(layout_job)) }; ui.add( 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 41d99fb8a..f9d078b28 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 @@ -162,7 +162,7 @@ fn bullet_point(ui: &mut Ui, width: f32) -> Response { fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response { let font_id = TextStyle::Body.resolve(ui.style()); - let row_height = ui.fonts(|f| f.row_height(&font_id)); + let row_height = ui.fonts_mut(|f| f.row_height(&font_id)); let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover()); let text = format!("{number}."); let text_color = ui.visuals().strong_text_color(); 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 0bf7d928c..39223555c 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:13262df01a7f2cd5655b8b0bb9379ae02a851c877314375f047a7d749908125c -size 31368 +oid sha256:514f2d25acec42f1a7baf40716549fe233f61a19cca675165d53f99b2433bb76 +size 31669 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 449c88683..54fd62562 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:27d5aa7b7e6bd5f59c1765e98ca4588545284456e4cc255799ea797950e09850 -size 26461 +oid sha256:693d26c8195ef8062d81127290a1e2e252b317f6430aba8cabf1f1c8b25a3213 +size 26895 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png index abd66feb1..e5e08d8f7 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b768086b0f79d76f08b21ef315ad5333ae81aa5b592dfbf47535b40d10cc19bc -size 26422 +oid sha256:d55b12672145f5a1b3fd7d8f063735dcce06d1e6dc6fa06af93d7d04c0460ab9 +size 26928 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index 94601b4d9..e225a8dde 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d36b7a37d8fcb727a015475f98b54552b37339e1087d0a1af3fd0fefe9a8b9cf -size 78742 +oid sha256:1054c1cb66803f36119e8e698f69dfaf1583167a8eba6316915a21c7303dc22c +size 78324 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png index 007000864..06832b754 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c03eadbd3ba486100303ab9b350d82d5ba31b7aa694641538e8b347a1bc18b0 -size 61400 +oid sha256:4d43b44d4ae5c168762c74b3c34f9ae5a57b63ff3470dfc272a69625b57c8710 +size 62921 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png index 8fa22e08d..53a03ba14 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a63cee05ac47fdf52ae3a1398f7e230a29ffdfd62ac63f3acc74cedba9100069 -size 25840 +oid sha256:d6a7ac851b248bfe69f486ccaf12063ccc7e73fb01b4e7d07902677b5db6118b +size 25946 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png index 1bb7af6e1..2aedac9ae 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e3e0330de3f68593329d2f36649127d5ac70109232c68f5c7ce310fa919fda5 -size 20348 +oid sha256:8e37b5c785cd011dbbade5975147702fe7d458c22699c83e307033339f614c26 +size 20797 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png index 9fd711dbc..6f8feb5ef 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2882a9842f51a7c3e9642a9a3d260407e1194648f47574608822a293bd3b1d56 -size 10465 +oid sha256:24e31eeaf1b7c07fb1ddb3aa92a876595436c20fa54937cf35c44fb7a7a6d890 +size 10774 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png index 2551a5218..1ea554b02 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e4ba53720713a8083997acb990fe4fc68ba4b9ffc7a4e58f17faa66de0da091 -size 128261 +oid sha256:a1c130d0a2cacc4fcc55d9c6947ce239a68cada8c207a03a04b08e822adb9cd5 +size 127038 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index e802bcec8..e54845856 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:08deb70e760326a1274ebcbd51849aae958ace52ec1aab36fff93a9b8dd98f4d -size 24029 +oid sha256:294f03e8961a96705ca219da37555a4c856849ac2e8cea73bca29948e68526c6 +size 24505 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 09ccf9c6a..a19dafc5e 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:828ee860ed31930d67deb2b6efee8bf2aec3c3266cf05b5510d1565dc7090e2f -size 99106 +oid sha256:d5bb6d587903f3bd06ed8e3410ba8f31aa88fe355adb36023e4949bf89c009b4 +size 101649 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index e8b6bd55b..5a623f8a8 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:403321533e97eac1aaf994d0ddeb5d53f06070a4b6a5c913c0ba22a3c60ad761 -size 17604 +oid sha256:12aa0ce4cd746cffc9fafc1daa43342df7b251f92a7e330be44b1b73e671c9f7 +size 18142 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png index b7f376a3d..2274ecb7c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3978a1dd2479bab2f234411b49d414fa3cf43ae18025db570f3c7a84f4a16e7 -size 111696 +oid sha256:cc2575366c79922f95a7b40766b6a5a91fdac7bce49c1eb057d992679d11030f +size 115934 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png index 40ea1255e..80f75d67d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6e29013cf12bd663cd7243baad03585c8d5b95d592f96aa8d7910abf5dcc2db -size 24541 +oid sha256:a7b1f709a5e8be577dbdd6122935dc1969fbe355155b78b00b862be2bdb268d1 +size 24876 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 46e5daeae..dac41f75d 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:8071d1a913ed35c4edab01dcce28996ee1c365ca393e11fd3f99cef65c7736ec -size 50357 +oid sha256:b3201768ee61eb87aba5fac21b3382b160296acd3a0d7caab62360efe63cd94d +size 51714 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png index d934345b9..e354d67c5 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:641e5c7d4deccc8eb0374db4707dc356285a5c72186f9021d0d601c22bc5115f -size 21894 +oid sha256:28112d906a73833fb6052402df4717a17fd374ab7c3a5f84348463e025689b58 +size 22255 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png index 00958a6e0..2d1948615 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cefcd7aa537e98e3a560fc458028f7e6b4f67e92343b94771f3ba21dce0298c -size 46666 +oid sha256:8b485f4dd8b72fa6b8c689b4091a0660afd4f1301287382c44c16c3bb4fc5e0c +size 47315 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 3e4c97ce5..11e7bc7d0 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:9ff7b37547644d542e7fe3486954479d0e28ee30be37f7c938b820192eddcfb9 -size 24078 +oid sha256:90d78b13159965cdaac9c67aa54a11390c0a18a0fdcee7f818edd06f87fa865f +size 24313 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 1a65dbc7c..d2151b1bd 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5211f7be04c3c28555cc3b53c798c165854441e9847d9bf7d67ad6b2fb0e72cc -size 64618 +oid sha256:c672c2ad3c33863a09b35e00fcd761ba91488d194273788c9790ac475d20e33c +size 66212 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png index e77312e7f..c2f998766 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f467edf4a84c8a98d96f168d843edb201ad2ee067dcd9d8d9ea214a02a41b1f -size 32182 +oid sha256:0d12e4f2fbfebc8ba3050f317e98f2a485ec7961db9190104143257499d206ac +size 32700 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index a5036a36f..7fc83efa7 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99c68d8904a48205bbf02eb134715d289881be5b868949dbd22602f2a35a1d47 -size 35718 +oid sha256:a80efaa91120ab8b830037073b5c177045261da2a8f146c859bc76317a6a3b1a +size 36328 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index d39c76efe..6f56df920 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8d3c5c2032c8eb36f60b2422eb8dd257b7b6417b1d93b120b3347e37a047c74 -size 17447 +oid sha256:a654b9ebd75fb1fea733378ffcd50876dae406610ef070c3aa487db0a5f48eb1 +size 17625 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 87b675bc0..22daed0ed 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:376657d119ace8d96b52bbf0e9daf379f8e0d5ee6a1be840b3efc210a02b6364 -size 277816 +oid sha256:c91f592571ba654d0a96791662ae7530a1db4c1630b57c795d1c006ea6e46f19 +size 256975 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png index 27b088e29..e926f466e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88cebf1a05400c967f8930271eb3f6a983fe82e9afa266682a2bd3862e2ab62f -size 57055 +oid sha256:87df1de1677b993bdaaf0c0f13db1e0c9a52bdf91cbdc8c4487357e09cbfc453 +size 56985 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png b/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png index 9e14e624d..f96fce916 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22515363a812443b65cbe9060e2352e18eed04dc382fc993c33bd8a4b5ddff91 -size 24817 +oid sha256:a11b0aeb8b8a7ff3acd54b99869af03cd04cc2edf13afc713ce924c52d39262d +size 24826 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index 760c84e8f..0f8ca667e 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:aabc0e3821a2d9b21708e9b8d9be02ad55055ccabe719a93af921dba2384b4b3 -size 34297 +oid sha256:b17c6044a9975a871da3062a5e1f55e500ebd4b0c325ee8c1d9277d8f7ba24e3 +size 35158 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 7a57f9408..32dd79e99 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15d023fcd551cbf7b643bae2591751e075fdcd8ef9386e5ab28ac2cd99c0efee -size 23230 +oid sha256:165b7029b921d3c4a2ad7164eaf288efc3e5f022341dc89ea214f50890072c49 +size 23385 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 5d1261a7e..16912ea51 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:059238da7f4eebf6f33da593ee5e756a710d1ea1c19ebab4494dd0f9362e822f -size 179995 +oid sha256:45a5e9542b928cf1624d84f6bf8e339dc78e02adb4d90ae83fad914941319631 +size 186697 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 89e3c12f4..4c18fae80 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:599ecaca9a324200096e445992de027fb9e9a93d56fc5cb918777aa306d20f58 -size 115446 +oid sha256:d96d6f381aa5e2ac87ee8a7a3fcf1d07a98532642ba5d45e3c24a5c331bafd96 +size 119551 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index 40ba0313c..cadb7ea81 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8a0b214ec1cc325b88918f25da87bfdb469cbcc6bf6646a0ac0a1d5f21a26e6 -size 25614 +oid sha256:b97435e619f0655d6332ed1f779af6b93c65b5264b09e7f05242db0b196c8c27 +size 26019 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index f6e367f3d..dd8ef6869 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b51b334db17a29df66e010c06d6cb3599013b569c068cf55b9603ed40c03eae -size 75313 +oid sha256:a834ebd9c0e66b4e4e60d5dd02274624005e368ae4c9b6f0a34feef51ecd6648 +size 76635 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 462a40ad9..75cd7ad69 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:a3f8873c9cfb80ddeb1ccc0fa04c1c84ea936db1685238f5d29ee6e89f55e457 -size 68814 +oid sha256:a77520c0176ff60c506174998fa6e0cc9473103e93efb05858f52c82fe1b3852 +size 69473 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index ef9969e53..a0dd98a53 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f74f765e6f187a9cf8f3ff254b1fa5826d0c2719ef904560d56aebae7526fa7 -size 64799 +oid sha256:beeb28e1e1d25281ad69736be3aae98ae6ca20c16873be5596f2e608894536b8 +size 66019 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 3bdadb62e..96b998fe2 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e82ae562cf2c987624667cd9324e21726411802c2a901fe36d9f9e4e61c6b980 -size 20968 +oid sha256:806b43782ee228900ecb9c7b1fb7b57a8894669db1475a13852215f64d18b2c5 +size 21352 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index 0652b37ef..c790b5999 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a328a035ebf0dd10b935902e8e17a8fce7455f2dcf21c90f44e44ce9e99c2677 -size 62318 +oid sha256:3b70081c1423731d387df294f5685c56d265b6b66dfd95e606624665d45d3766 +size 64463 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 6da000366..fce774dc2 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:0bcca7b25740375f882acd6707b360092a78f96a1b7c807b5c22904a9c8efbd8 -size 12872 +oid sha256:1a837402657274b1a31e757005cb1fb0f5b345f9d3427be700357b982455baec +size 13081 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 6b889a87b..b61e004c9 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:288d8c2a5aa14fb1b9a7bb6dbc098c7cfbe5bb5391baf6566785d824affb9396 -size 35306 +oid sha256:fda993429a2454cf84ae9cc73ae4242aa50a80a5fd8920dc35601bf8442e9669 +size 36020 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png index 4f39f7753..41115407b 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8c3dd4dd971ad19d6695f063779f0a25c9b4ad4a15d4e5e080ac5d832605d95 -size 42521 +oid sha256:c8ee6b7e3e4c4d3dd96b1b7f6111a9e032e2ee96c157ff7d669332d15b23129d +size 43557 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index f38387d3c..e5e0ddc0b 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_1.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b1ef1ed7d902b72fac55a83865cff451c04c8ef230096a4dc420a699936334a -size 47540 +oid sha256:702842a12c11f131bf41ad49542369b0aa520bf2ec89e460c069177c48e630b4 +size 48301 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index 991159212..119ff2e5d 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:630041fbea61422695bb52fb3d5b00ad877cb0a55cdc264d3d6c665e7d2339a7 -size 47502 +oid sha256:ab58d458abb369d4925e6ca614cdfd5f87171de5def5daa23f0fa002af8c7b18 +size 48229 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index ed47d5dbc..a83010763 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c831bbc1f4f52e0b1d2868af3e5632717b2fd8df196b507151dcafc61ddbd45 -size 43772 +oid sha256:a89afce6dbbc27ebac9989be3219d96aac4f51f09dea0c10c9a13e3e6e5b7d54 +size 44194 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index c14d596e2..7e0bac996 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b5ecf62a1a4edbecb65948796695084d3ac82d6bdf6b80a5f09241e76987f64 -size 43676 +oid sha256:331d86036fea0271cda3969de4c59650df90c1d6442d61f99200fa699d5b9d74 +size 44318 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 200de9835..d04c406a7 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:39bd11647241521c0ad5c7163a1af4f1aa86792018657091a2d47bb7f2c48b47 -size 598408 +oid sha256:89326fda609fdd4b6ce6aa5cd0881d2a720ee498c7283670085fb4116738117d +size 619548 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 ea9298ad6..bda95de70 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:080a59163ab60d60738cfab5afac7cfbddfb585d8a0ee7d03540924b458badea -size 833822 +oid sha256:fde14e4bb03251a7ff4ba508e3c78eb33204781dc67dec440f93a146d74f6691 +size 813974 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 86ec338c7..7d389ceb5 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:216d3d028f48f4bfbd6aca0a25500655d4eb4d5581a387397ed0b048d57fc0c3 -size 984737 +oid sha256:d5c95515a85e1bb827ff5507843c30d39864fdf0578890ce788889578ae65724 +size 992333 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 3b239324a..ea6dc57ca 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:399fc3d64e7ff637425effe6c80d684be1cf8bb9b555458352d0ae6c62bddb5a -size 1109505 +oid sha256:53423e41be4856056639d14aca6695966465d8ed0379a97deeb101aa41ff560b +size 1129280 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 8d4a1b365..ef72be63c 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:30ce4874a1adb8acf8c8e404f3e19e683eca3133cdef15befbc50d6c09618094 -size 1241773 +oid sha256:56ec1367a030b816a01b5580f454d1a2138cc4dc1fd2534526743b33fd4d1be9 +size 1239055 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 854ee6b29..4c0ac30ea 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:135fbe5f4ee485ee671931b64c16410b9073d40bedb23dc2545fc863448e8c63 -size 1398091 +oid sha256:3d9ab25243e4850888e43b268801963d8fafb68aa27e0a7cfc1f4b563c8326e8 +size 1410062 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 852bc6bb2..50bf1982b 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:1b0fe7aa33506c59142aff764c6b229e8c55b35c8038312b22ea2659987a081a -size 45578 +oid sha256:ec6720a0b4d1db5ddcd08e4ee9834b56b9d0bded7d7e7194865f804d74bb76b2 +size 46251 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 49ba9ad07..5d1ececad 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:3a3512ea7235640db79e86aa84039621784270201c9213c910f15e7451e5600b -size 87336 +oid sha256:234f2b50e096f1f1545531edeb5a7865e0a73e2035967ce3270602512c3a9a18 +size 88006 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 6130a530e..26a97fe62 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:dc4918a534f26b72d42ef20221e26c0f91a0252870a1987c1fe4cc4aa3c74872 -size 119406 +oid sha256:d4611a5844cb85a000ec1fb7b51b6a75ba8ada24da108d962b16b0317e55fa7f +size 120119 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 7969d6bee..50bae8acb 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:71182570a65693839fd7cd7390025731ab3f3f88ab55bc67d8be6466fe5a2c11 -size 51843 +oid sha256:e6fcca95ef83f8fd57359ac3bb52aa2bb338a726845fbe0370c41e59b99f15d3 +size 52535 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 49141c40d..cf996488b 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:a0dc0294f990730da34fcbbc53f44280306ec6179826c20a6c9ee946e1148b61 -size 55042 +oid sha256:c15f24d4b9a969b7dc275a3402e2fc36a1b86f3bfaf4f43b8c3e7ae36634e70e +size 55729 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 e8f61ae77..73c70b4ee 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:3004adfe5a864bdc768ceb42d1f09b6debcaf5413f8fea4d36b0aff99e4584f9 -size 55511 +oid sha256:58bbd42732e48c1792765dc0c48d57ef0e4441a8b4e97eed936d564bef661116 +size 56210 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 139648c38..69aa782b5 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:b99360833f59a212a965a13d52485ab8ad0e6420b9288b2d6936507067c22a85 -size 36395 +oid sha256:8546adf43506733e48e3c8988f7e869bffb2a674629dc9d66e52a5aae0123653 +size 37132 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 10ad7603b..cd8804dc9 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:82aa004f668f0ac6b493717b4bff8436ccc1e991c7fb3fcde5b5f3a123c06b9f -size 36428 +oid sha256:aff779555f23d9360f58f1b7dc2686c72b848cc5d0a889ba4d5446fc4b252d65 +size 37112 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 9cd2d630e..daabdb90b 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:7e21bb01ae6e4226402a97b7086b49604cdde6b41a6770199df68dc940cd9a45 -size 64748 +oid sha256:7ad3e3de6e1c12eccd12750c44f52e863f28c3234cbc4f3091674e8c03a9d503 +size 66162 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 f881f639c..586bf57e3 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:0626bc45888ad250bf4b49c7f7f462a93ab91e3a2817fd7d0902411043c97132 -size 153289 +oid sha256:e9208c3b89665e8aab19ab5e7b427ed8251381c92c3b7c51a708f80c61be28b2 +size 152614 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 5b88cc531..62c85f873 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:919a82c95468300bcd09471eb31d53d25d50cdcb02c27ddbc759d24e65da92b6 -size 59398 +oid sha256:b55a25a1ccff076036b574c82a923d13ff353c2f7a4927f2d911d04121782c05 +size 60492 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 a1971cad6..7e5f3baf8 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:a55e39a640b0e2cc992286a86dcf38460f1abcc7b964df9022549ca1a94c4df5 -size 146408 +oid sha256:ae7992752e2fce96d0083143f07f216d27472ee057995ac2630cecccfeb44bcc +size 146455 diff --git a/crates/egui_kittest/tests/snapshots/combobox_closed.png b/crates/egui_kittest/tests/snapshots/combobox_closed.png index b7a105c8b..01d7d4d03 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_closed.png +++ b/crates/egui_kittest/tests/snapshots/combobox_closed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bab1f08160bc43410b9d49ebfaae8a471309e670fccd31456a09176513361e6e -size 4417 +oid sha256:9e42d26fa8188790c1dfed8e6a1a9f36a01b066a798dd1c878eeb3f0d1fb7e52 +size 4475 diff --git a/crates/egui_kittest/tests/snapshots/combobox_opened.png b/crates/egui_kittest/tests/snapshots/combobox_opened.png index b406cf5d8..7c392a7c7 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_opened.png +++ b/crates/egui_kittest/tests/snapshots/combobox_opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3a4ea95569b812ea46bc706f91d5ac03fa18ef1707a726b729422e9e9b18680 -size 7305 +oid sha256:03564030d1ce4d1698b2e831df405d88d3930de2894ff2b601adf1723c160383 +size 7443 diff --git a/crates/egui_kittest/tests/snapshots/image_snapshots.png b/crates/egui_kittest/tests/snapshots/image_snapshots.png index 5c1bf9f4d..7df240eb1 100644 --- a/crates/egui_kittest/tests/snapshots/image_snapshots.png +++ b/crates/egui_kittest/tests/snapshots/image_snapshots.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c618906fe1ff781c20cb89747879fa1ac63a115c624a2695adb9eb6ae157cd40 -size 2095 +oid sha256:668999dda2ce857c5a43dd7bdaf88d2062105683f2628b7f41b8885c423693df +size 2136 diff --git a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png index 5e35fe18a..3d8bfd207 100644 --- a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png +++ b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29074de792c3c266b451ed28c940d7278d533570efecf5a64ae8bbae2b851a52 -size 10533 +oid sha256:d62d6e8eb73acaaa4851268f33ca6a9411f5c96eeaae5795bb5f71af4f8e31ba +size 10819 diff --git a/crates/egui_kittest/tests/snapshots/menu/opened.png b/crates/egui_kittest/tests/snapshots/menu/opened.png index 655eaf443..c52ecaf09 100644 --- a/crates/egui_kittest/tests/snapshots/menu/opened.png +++ b/crates/egui_kittest/tests/snapshots/menu/opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a12bfee8b9c6b6845ff3cadc40bdb707375f2177095fcd7f0b9ad1fcf1e9f00 -size 21118 +oid sha256:670b2c2887f3be0d808faca41195caebf227420034a725494bb5a07122f4cabe +size 21881 diff --git a/crates/egui_kittest/tests/snapshots/menu/submenu.png b/crates/egui_kittest/tests/snapshots/menu/submenu.png index c4f200799..8dde92fe0 100644 --- a/crates/egui_kittest/tests/snapshots/menu/submenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/submenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e70f7ec229d94919346192141272273ff2f56032e1ae3137cc04ffd21aabe51 -size 28361 +oid sha256:52318e0106cc60d9442b2caa9d4cfa6b0831e9a85e2ed5415be67e9cdb113303 +size 29122 diff --git a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png index 16d510eaf..ceb49b1ca 100644 --- a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3295fc14054c31efd7a5c6508f52c4a80b0fb8f91a452808bcfe3e4dddecc9c -size 32992 +oid sha256:10ac5ab3174646c2ec943c43ee954d7450f2d435f14b184bc824b858f1f036c2 +size 33901 diff --git a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png index 036cdd007..7f3a5599f 100644 --- a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png +++ b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2ef3a6fb7d43c85b0f8cdb0eb7a784aa7ae5af9b61ae296e0d1a41871eb2724 -size 19713 +oid sha256:89d172c28662826c8b0897a703730c629ca438755a14f040f029944d55f24353 +size 19966 diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index 2be9c9cdb..eed33654c 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:730bdd28319b140433802740728f096adda749014a1629114b18d6f674ee4d86 -size 1893 +oid sha256:b17893705837c90f589b23b1e5b418e5ce93c5601c4abf348b8fc6ef416e4278 +size 1947 diff --git a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png index bb5924936..4a3f79eb4 100644 --- a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png +++ b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8c872f818a2e88cce35d015d16406c225ab6792cc6bfd232de94dbf2665df26 -size 2142 +oid sha256:b0b3a6fdd415cfae89d08fe0d10b0dab2327bba5f0fdb2032d3db271811313e6 +size 2182 diff --git a/crates/egui_kittest/tests/snapshots/test_masking.png b/crates/egui_kittest/tests/snapshots/test_masking.png index 932e9d5e4..def54053d 100644 --- a/crates/egui_kittest/tests/snapshots/test_masking.png +++ b/crates/egui_kittest/tests/snapshots/test_masking.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdb0c0b955e3d3773a00afa61d4c44999de447aa71dd737181563101f09f5ac9 -size 5444 +oid sha256:dc85c7d6a9fe285ad27693169fd9a9fead0286637e3f77a349e573e660a13c47 +size 5697 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png index 32969d743..00ff45847 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70d76e55327de17163bc9c7e128c28153f95db3229dec919352a024eb80544f1 -size 7399 +oid sha256:dd0968233f5145a3708fe5ff771473abd824952e71182c2311670417c6a92f04 +size 7623 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png index 361925d04..8ac89d1aa 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4b7b3145401b7cf9815a652a0914b230892ffda3b5e23fea530dafee9c0c3d3 -size 8110 +oid sha256:edc4149d67817420db2a2877a1eb83ad7569fd7aa8a4bf75a6dd93b2b59e9615 +size 8305 diff --git a/crates/egui_kittest/tests/snapshots/test_shrink.png b/crates/egui_kittest/tests/snapshots/test_shrink.png index 0004a0f61..dc83dbda3 100644 --- a/crates/egui_kittest/tests/snapshots/test_shrink.png +++ b/crates/egui_kittest/tests/snapshots/test_shrink.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be6f0caa911d93543edb39ba6d07d7617ec283b37bc62622a68a18960eb840ab -size 2871 +oid sha256:99ec3f7a81bbe351d0a894005fad95129f36d5c8cc29b4648ddfb9ddce201913 +size 2983 diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 5e6dc27e1..1bf0285bd 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -62,7 +62,7 @@ pub use self::{ stats::PaintStats, stroke::{PathStroke, Stroke, StrokeKind}, tessellator::{TessellationOptions, Tessellator}, - text::{FontFamily, FontId, Fonts, Galley}, + text::{FontFamily, FontId, Fonts, FontsView, Galley}, texture_atlas::TextureAtlas, texture_handle::TextureHandle, textures::TextureManager, diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 10de38d15..8ee852c61 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -7,7 +7,7 @@ use emath::{Align2, Pos2, Rangef, Rect, TSTransform, Vec2, pos2}; use crate::{ Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId, stroke::PathStroke, - text::{FontId, Fonts, Galley}, + text::{FontId, FontsView, Galley}, }; use super::{ @@ -299,7 +299,7 @@ impl Shape { #[expect(clippy::needless_pass_by_value)] pub fn text( - fonts: &Fonts, + fonts: &mut FontsView<'_>, pos: Pos2, anchor: Align2, text: impl ToString, diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index 9505dc49b..349707eac 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -15,7 +15,7 @@ pub struct TextShape { /// Usually the top left corner of the first character. pub pos: Pos2, - /// The laid out text, from [`Fonts::layout_job`]. + /// The laid out text, from [`FontsView::layout_job`]. pub galley: Arc, /// Add this underline to the whole text. @@ -181,8 +181,7 @@ mod tests { #[test] fn text_bounding_box_under_rotation() { - let fonts = Fonts::new( - 1.0, + let mut fonts = Fonts::new( 1024, AlphaFromCoverage::default(), FontDefinitions::default(), @@ -190,7 +189,7 @@ mod tests { let font = FontId::monospace(12.0); let mut t = crate::Shape::text( - &fonts, + &mut fonts.with_pixels_per_point(1.0), Pos2::ZERO, emath::Align2::CENTER_CENTER, "testing123", diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index dd095c443..f3d098994 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -1,12 +1,14 @@ use std::collections::BTreeMap; -use std::sync::Arc; -use emath::{GuiRounding as _, Vec2, vec2}; +use ab_glyph::{Font as _, OutlinedGlyph, PxScale}; +use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2}; use crate::{ TextureAtlas, - mutex::{Mutex, RwLock}, - text::FontTweak, + text::{ + FontTweak, + fonts::{CachedFamily, FontFaceKey}, + }, }; // ---------------------------------------------------------------------------- @@ -34,108 +36,168 @@ impl UvRect { } } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GlyphInfo { + /// Used for pair-kerning. + /// + /// Doesn't need to be unique. + /// + /// Is `None` for a special "invisible" glyph. + pub(crate) id: Option, + + /// In [`ab_glyph`]s "unscaled" coordinate system. + pub advance_width_unscaled: OrderedFloat, +} + +impl GlyphInfo { + /// A valid, but invisible, glyph of zero-width. + pub const INVISIBLE: Self = Self { + id: None, + advance_width_unscaled: OrderedFloat(0.0), + }; +} + +// Subpixel binning, taken from cosmic-text: +// https://github.com/pop-os/cosmic-text/blob/974ddaed96b334f560b606ebe5d2ca2d2f9f23ef/src/glyph_cache.rs + +/// Bin for subpixel positioning of glyphs. +/// +/// For accurate glyph positioning, we want to render each glyph at a subpixel coordinate. However, we also want to +/// cache each glyph's bitmap. As a compromise, we bin each subpixel offset into one of four fractional values. This +/// means one glyph can have up to four subpixel-positioned bitmaps in the cache. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub(super) enum SubpixelBin { + #[default] + Zero, + One, + Two, + Three, +} + +impl SubpixelBin { + /// Bin the given position and return the new integral coordinate. + fn new(pos: f32) -> (i32, Self) { + let trunc = pos as i32; + let fract = pos - trunc as f32; + + #[expect(clippy::collapsible_else_if)] + if pos.is_sign_negative() { + if fract > -0.125 { + (trunc, Self::Zero) + } else if fract > -0.375 { + (trunc - 1, Self::Three) + } else if fract > -0.625 { + (trunc - 1, Self::Two) + } else if fract > -0.875 { + (trunc - 1, Self::One) + } else { + (trunc - 1, Self::Zero) + } + } else { + if fract < 0.125 { + (trunc, Self::Zero) + } else if fract < 0.375 { + (trunc, Self::One) + } else if fract < 0.625 { + (trunc, Self::Two) + } else if fract < 0.875 { + (trunc, Self::Three) + } else { + (trunc + 1, Self::Zero) + } + } + } + + pub fn as_float(&self) -> f32 { + match self { + Self::Zero => 0.0, + Self::One => 0.25, + Self::Two => 0.5, + Self::Three => 0.75, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Default)] +pub struct GlyphAllocation { /// Used for pair-kerning. /// /// Doesn't need to be unique. /// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care. pub(crate) id: ab_glyph::GlyphId, - /// Unit: points. - pub advance_width: f32, + /// Unit: screen pixels. + pub advance_width_px: f32, - /// Texture coordinates. + /// UV rectangle for drawing. pub uv_rect: UvRect, } -impl Default for GlyphInfo { - /// Basically a zero-width space. - fn default() -> Self { - Self { - id: ab_glyph::GlyphId(0), - advance_width: 0.0, - uv_rect: Default::default(), - } +#[derive(Hash, PartialEq, Eq)] +struct GlyphCacheKey(u64); + +impl nohash_hasher::IsEnabled for GlyphCacheKey {} + +impl GlyphCacheKey { + fn new(glyph_id: ab_glyph::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self { + let ScaledMetrics { + pixels_per_point, + px_scale_factor, + .. + } = *metrics; + debug_assert!( + 0.0 < pixels_per_point && pixels_per_point.is_finite(), + "Bad pixels_per_point {pixels_per_point}" + ); + debug_assert!( + 0.0 < px_scale_factor && px_scale_factor.is_finite(), + "Bad px_scale_factor: {px_scale_factor}" + ); + Self(crate::util::hash(( + glyph_id, + pixels_per_point.to_bits(), + px_scale_factor.to_bits(), + bin, + ))) } } // ---------------------------------------------------------------------------- -/// A specific font with a size. +/// A specific font face. /// The interface uses points as the unit for everything. pub struct FontImpl { name: String, ab_glyph_font: ab_glyph::FontArc, + tweak: FontTweak, + glyph_info_cache: ahash::HashMap, + glyph_alloc_cache: ahash::HashMap, +} - /// Maximum character height - scale_in_pixels: u32, +trait FontExt { + fn px_scale_factor(&self, scale: f32) -> f32; +} - height_in_points: f32, - - // move each character by this much (hack) - y_offset_in_points: f32, - - ascent: f32, - pixels_per_point: f32, - glyph_info_cache: RwLock>, // TODO(emilk): standard Mutex - atlas: Arc>, +impl FontExt for T +where + T: ab_glyph::Font, +{ + fn px_scale_factor(&self, scale: f32) -> f32 { + let units_per_em = self.units_per_em().unwrap_or_else(|| { + panic!("The font unit size exceeds the expected range (16..=16384)") + }); + scale / units_per_em + } } impl FontImpl { - pub fn new( - atlas: Arc>, - pixels_per_point: f32, - name: String, - ab_glyph_font: ab_glyph::FontArc, - scale_in_pixels: f32, - tweak: FontTweak, - ) -> Self { - assert!( - scale_in_pixels > 0.0, - "scale_in_pixels is smaller than 0, got: {scale_in_pixels:?}" - ); - assert!( - pixels_per_point > 0.0, - "pixels_per_point must be greater than 0, got: {pixels_per_point:?}" - ); - - use ab_glyph::{Font as _, ScaleFont as _}; - let scaled = ab_glyph_font.as_scaled(scale_in_pixels); - let ascent = (scaled.ascent() / pixels_per_point).round_ui(); - let descent = (scaled.descent() / pixels_per_point).round_ui(); - let line_gap = (scaled.line_gap() / pixels_per_point).round_ui(); - - // Tweak the scale as the user desired - let scale_in_pixels = scale_in_pixels * tweak.scale; - let scale_in_points = scale_in_pixels / pixels_per_point; - - let baseline_offset = (scale_in_points * tweak.baseline_offset_factor).round_ui(); - - let y_offset_points = - ((scale_in_points * tweak.y_offset_factor) + tweak.y_offset).round_ui(); - - // Center scaled glyphs properly: - let height = ascent + descent; - let y_offset_points = y_offset_points - (1.0 - tweak.scale) * 0.5 * height; - - // Round to an even number of physical pixels to get even kerning. - // See https://github.com/emilk/egui/issues/382 - let scale_in_pixels = scale_in_pixels.round() as u32; - - // Round to closest pixel: - let y_offset_in_points = (y_offset_points * pixels_per_point).round() / pixels_per_point; - + pub fn new(name: String, ab_glyph_font: ab_glyph::FontArc, tweak: FontTweak) -> Self { Self { name, ab_glyph_font, - scale_in_pixels, - height_in_points: ascent - descent + line_gap, - y_offset_in_points, - ascent: ascent + baseline_offset, - pixels_per_point, + tweak, glyph_info_cache: Default::default(), - atlas, + glyph_alloc_cache: Default::default(), } } @@ -161,7 +223,6 @@ impl FontImpl { /// An un-ordered iterator over all supported characters. fn characters(&self) -> impl Iterator + '_ { - use ab_glyph::Font as _; self.ab_glyph_font .codepoint_ids() .map(|(_, chr)| chr) @@ -169,11 +230,9 @@ impl FontImpl { } /// `\n` will result in `None` - fn glyph_info(&self, c: char) -> Option { - { - if let Some(glyph_info) = self.glyph_info_cache.read().get(&c) { - return Some(*glyph_info); - } + pub(super) fn glyph_info(&mut self, c: char) -> Option { + if let Some(glyph_info) = self.glyph_info_cache.get(&c) { + return Some(*glyph_info); } if self.ignore_character(c) { @@ -183,10 +242,12 @@ impl FontImpl { if c == '\t' { if let Some(space) = self.glyph_info(' ') { let glyph_info = GlyphInfo { - advance_width: crate::text::TAB_SIZE as f32 * space.advance_width, + advance_width_unscaled: (crate::text::TAB_SIZE as f32 + * space.advance_width_unscaled.0) + .into(), ..space }; - self.glyph_info_cache.write().insert(c, glyph_info); + self.glyph_info_cache.insert(c, glyph_info); return Some(glyph_info); } } @@ -197,91 +258,150 @@ impl FontImpl { // https://en.wikipedia.org/wiki/Thin_space if let Some(space) = self.glyph_info(' ') { - let em = self.height_in_points; // TODO(emilk): is this right? - let advance_width = f32::min(em / 6.0, space.advance_width * 0.5); + let em = self.ab_glyph_font.units_per_em().unwrap_or(1.0); + let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); let glyph_info = GlyphInfo { - advance_width, + advance_width_unscaled: advance_width.into(), ..space }; - self.glyph_info_cache.write().insert(c, glyph_info); + self.glyph_info_cache.insert(c, glyph_info); return Some(glyph_info); } } if invisible_char(c) { - let glyph_info = GlyphInfo::default(); - self.glyph_info_cache.write().insert(c, glyph_info); + let glyph_info = GlyphInfo::INVISIBLE; + self.glyph_info_cache.insert(c, glyph_info); return Some(glyph_info); } // Add new character: - use ab_glyph::Font as _; let glyph_id = self.ab_glyph_font.glyph_id(c); if glyph_id.0 == 0 { None // unsupported character } else { - let glyph_info = self.allocate_glyph(glyph_id); - self.glyph_info_cache.write().insert(c, glyph_info); + let glyph_info = GlyphInfo { + id: Some(glyph_id), + advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(), + }; + self.glyph_info_cache.insert(c, glyph_info); Some(glyph_info) } } #[inline] - pub fn pair_kerning( + pub(super) fn pair_kerning_pixels( &self, + metrics: &ScaledMetrics, last_glyph_id: ab_glyph::GlyphId, glyph_id: ab_glyph::GlyphId, ) -> f32 { - use ab_glyph::{Font as _, ScaleFont as _}; - self.ab_glyph_font - .as_scaled(self.scale_in_pixels as f32) - .kern(last_glyph_id, glyph_id) - / self.pixels_per_point + self.ab_glyph_font.kern_unscaled(last_glyph_id, glyph_id) * metrics.px_scale_factor } - /// Height of one row of text in points. - /// - /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - #[inline(always)] - pub fn row_height(&self) -> f32 { - self.height_in_points + #[inline] + pub fn pair_kerning( + &self, + metrics: &ScaledMetrics, + last_glyph_id: ab_glyph::GlyphId, + glyph_id: ab_glyph::GlyphId, + ) -> f32 { + self.pair_kerning_pixels(metrics, last_glyph_id, glyph_id) / metrics.pixels_per_point } #[inline(always)] - pub fn pixels_per_point(&self) -> f32 { - self.pixels_per_point + pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics { + let pt_scale_factor = self + .ab_glyph_font + .px_scale_factor(font_size * self.tweak.scale); + let ascent = (self.ab_glyph_font.ascent_unscaled() * pt_scale_factor).round_ui(); + let descent = (self.ab_glyph_font.descent_unscaled() * pt_scale_factor).round_ui(); + let line_gap = (self.ab_glyph_font.line_gap_unscaled() * pt_scale_factor).round_ui(); + + let scale = font_size * self.tweak.scale * pixels_per_point; + let px_scale_factor = self.ab_glyph_font.px_scale_factor(scale); + + let y_offset_in_points = ((font_size * self.tweak.scale * self.tweak.y_offset_factor) + + self.tweak.y_offset) + .round_ui(); + + ScaledMetrics { + pixels_per_point, + px_scale_factor, + y_offset_in_points, + ascent, + row_height: ascent - descent + line_gap, + } } - /// This is the distance from the top to the baseline. - /// - /// Unit: points. - #[inline(always)] - pub fn ascent(&self) -> f32 { - self.ascent - } + pub fn allocate_glyph( + &mut self, + atlas: &mut TextureAtlas, + metrics: &ScaledMetrics, + glyph_info: GlyphInfo, + chr: char, + h_pos: f32, + ) -> (GlyphAllocation, i32) { + let advance_width_px = glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor; - fn allocate_glyph(&self, glyph_id: ab_glyph::GlyphId) -> GlyphInfo { - assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0"); - use ab_glyph::{Font as _, ScaleFont as _}; + let Some(glyph_id) = glyph_info.id else { + // Invisible. + return (GlyphAllocation::default(), h_pos as i32); + }; - let glyph = glyph_id.with_scale_and_position( - self.scale_in_pixels as f32, - ab_glyph::Point { x: 0.0, y: 0.0 }, - ); + // CJK scripts contain a lot of characters and could hog the glyph atlas if we stored 4 subpixel offsets per + // glyph. + let (h_pos_round, bin) = if is_cjk(chr) { + (h_pos.round() as i32, SubpixelBin::Zero) + } else { + SubpixelBin::new(h_pos) + }; - let uv_rect = self.ab_glyph_font.outline_glyph(glyph).map(|glyph| { - let bb = glyph.px_bounds(); + let entry = match self + .glyph_alloc_cache + .entry(GlyphCacheKey::new(glyph_id, metrics, bin)) + { + std::collections::hash_map::Entry::Occupied(glyph_alloc) => { + let mut glyph_alloc = *glyph_alloc.get(); + glyph_alloc.advance_width_px = advance_width_px; // Hack to get `\t` and thin space to work, since they use the same glyph id as ` ` (space). + return (glyph_alloc, h_pos_round); + } + std::collections::hash_map::Entry::Vacant(entry) => entry, + }; + + debug_assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0"); + + let uv_rect = self.ab_glyph_font.outline(glyph_id).map(|outline| { + let glyph = ab_glyph::Glyph { + id: glyph_id, + // We bypass ab-glyph's scaling method because it uses the wrong scale + // (https://github.com/alexheretic/ab-glyph/issues/15), and this field is never accessed when + // rasterizing. We can just put anything here. + scale: PxScale::from(0.0), + position: ab_glyph::Point { + x: bin.as_float(), + y: 0.0, + }, + }; + let outlined = OutlinedGlyph::new( + glyph, + outline, + ab_glyph::PxScaleFactor { + horizontal: metrics.px_scale_factor, + vertical: metrics.px_scale_factor, + }, + ); + let bb = outlined.px_bounds(); let glyph_width = bb.width() as usize; let glyph_height = bb.height() as usize; if glyph_width == 0 || glyph_height == 0 { UvRect::default() } 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| { + outlined.draw(|x, y, v| { if 0.0 < v { let px = glyph_pos.0 + x as usize; let py = glyph_pos.1 + y as usize; @@ -292,11 +412,11 @@ impl FontImpl { }; let offset_in_pixels = vec2(bb.min.x, bb.min.y); - let offset = - offset_in_pixels / self.pixels_per_point + self.y_offset_in_points * Vec2::Y; + let offset = offset_in_pixels / metrics.pixels_per_point + + metrics.y_offset_in_points * Vec2::Y; UvRect { offset, - size: vec2(glyph_width as f32, glyph_height as f32) / self.pixels_per_point, + size: vec2(glyph_width as f32, glyph_height as f32) / metrics.pixels_per_point, min: [glyph_pos.0 as u16, glyph_pos.1 as u16], max: [ (glyph_pos.0 + glyph_width) as u16, @@ -307,79 +427,25 @@ impl FontImpl { }); let uv_rect = uv_rect.unwrap_or_default(); - let advance_width_in_points = self - .ab_glyph_font - .as_scaled(self.scale_in_pixels as f32) - .h_advance(glyph_id) - / self.pixels_per_point; - - GlyphInfo { + let allocation = GlyphAllocation { id: glyph_id, - advance_width: advance_width_in_points, + advance_width_px, uv_rect, - } + }; + entry.insert(allocation); + (allocation, h_pos_round) } } -type FontIndex = usize; - // TODO(emilk): rename? /// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis) -pub struct Font { - fonts: Vec>, - - /// Lazily calculated. - characters: Option>>, - - replacement_glyph: (FontIndex, GlyphInfo), - pixels_per_point: f32, - row_height: f32, - glyph_info_cache: ahash::HashMap, +pub struct Font<'a> { + pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap, + pub(super) cached_family: &'a mut CachedFamily, + pub(super) atlas: &'a mut TextureAtlas, } -impl Font { - pub fn new(fonts: Vec>) -> Self { - if fonts.is_empty() { - return Self { - fonts, - characters: None, - replacement_glyph: Default::default(), - pixels_per_point: 1.0, - row_height: 0.0, - glyph_info_cache: Default::default(), - }; - } - - let pixels_per_point = fonts[0].pixels_per_point(); - let row_height = fonts[0].row_height(); - - let mut slf = Self { - fonts, - characters: None, - replacement_glyph: Default::default(), - pixels_per_point, - row_height, - glyph_info_cache: Default::default(), - }; - - const PRIMARY_REPLACEMENT_CHAR: char = '◻'; // white medium square - const FALLBACK_REPLACEMENT_CHAR: char = '?'; // fallback for the fallback - - let replacement_glyph = slf - .glyph_info_no_cache_or_fallback(PRIMARY_REPLACEMENT_CHAR) - .or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR)) - .unwrap_or_else(|| { - #[cfg(feature = "log")] - log::warn!( - "Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}. Will use empty glyph." - ); - (0, GlyphInfo::default()) - }); - slf.replacement_glyph = replacement_glyph; - - slf - } - +impl Font<'_> { pub fn preload_characters(&mut self, s: &str) { for c in s.chars() { self.glyph_info(c); @@ -399,9 +465,10 @@ impl Font { /// All supported characters, and in which font they are available in. pub fn characters(&mut self) -> &BTreeMap> { - self.characters.get_or_insert_with(|| { + self.cached_family.characters.get_or_insert_with(|| { let mut characters: BTreeMap> = Default::default(); - for font in &self.fonts { + for font_id in &self.cached_family.fonts { + let font = self.fonts_by_id.get(font_id).expect("Nonexistent font ID"); for chr in font.characters() { characters.entry(chr).or_default().push(font.name.clone()); } @@ -410,34 +477,29 @@ impl Font { }) } - #[inline(always)] - pub fn round_to_pixel(&self, point: f32) -> f32 { - (point * self.pixels_per_point).round() / self.pixels_per_point - } - - /// Height of one row of text. In points. - /// - /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - #[inline(always)] - pub fn row_height(&self) -> f32 { - self.row_height - } - - pub fn uv_rect(&self, c: char) -> UvRect { - self.glyph_info_cache - .get(&c) - .map(|gi| gi.1.uv_rect) + pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics { + self.cached_family + .fonts + .first() + .and_then(|key| self.fonts_by_id.get(key)) + .map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size)) .unwrap_or_default() } /// Width of this character in points. - pub fn glyph_width(&mut self, c: char) -> f32 { - self.glyph_info(c).1.advance_width + pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 { + let (key, glyph_info) = self.glyph_info(c); + let font = &self + .fonts_by_id + .get(&key) + .expect("Nonexistent font ID") + .ab_glyph_font; + glyph_info.advance_width_unscaled.0 * font.px_scale_factor(font_size) } /// Can we display this glyph? pub fn has_glyph(&mut self, c: char) -> bool { - self.glyph_info(c) != self.replacement_glyph // TODO(emilk): this is a false negative if the user asks about the replacement character itself 🤦‍♂️ + self.glyph_info(c) != self.cached_family.replacement_glyph // TODO(emilk): this is a false negative if the user asks about the replacement character itself 🤦‍♂️ } /// Can we display all the glyphs in this text? @@ -446,44 +508,46 @@ impl Font { } /// `\n` will (intentionally) show up as the replacement character. - fn glyph_info(&mut self, c: char) -> (FontIndex, GlyphInfo) { - if let Some(font_index_glyph_info) = self.glyph_info_cache.get(&c) { + pub(crate) fn glyph_info(&mut self, c: char) -> (FontFaceKey, GlyphInfo) { + if let Some(font_index_glyph_info) = self.cached_family.glyph_info_cache.get(&c) { return *font_index_glyph_info; } - let font_index_glyph_info = self.glyph_info_no_cache_or_fallback(c); - let font_index_glyph_info = font_index_glyph_info.unwrap_or(self.replacement_glyph); - self.glyph_info_cache.insert(c, font_index_glyph_info); + let font_index_glyph_info = self + .cached_family + .glyph_info_no_cache_or_fallback(c, self.fonts_by_id); + let font_index_glyph_info = + font_index_glyph_info.unwrap_or(self.cached_family.replacement_glyph); + self.cached_family + .glyph_info_cache + .insert(c, font_index_glyph_info); font_index_glyph_info } +} - #[inline] - pub(crate) fn font_impl_and_glyph_info(&mut self, c: char) -> (Option<&FontImpl>, GlyphInfo) { - if self.fonts.is_empty() { - return (None, self.replacement_glyph.1); - } - let (font_index, glyph_info) = self.glyph_info(c); - let font_impl = &self.fonts[font_index]; - (Some(font_impl), glyph_info) - } +/// Metrics for a font at a specific screen-space scale. +#[derive(Clone, Copy, Debug, PartialEq, Default)] +pub struct ScaledMetrics { + /// The DPI part of the screen-space scale. + pub pixels_per_point: f32, - pub(crate) fn ascent(&self) -> f32 { - if let Some(first) = self.fonts.first() { - first.ascent() - } else { - self.row_height - } - } + /// Scale factor, relative to the font's units per em (so, probably much less than 1). + /// + /// Translates "unscaled" units to physical (screen) pixels. + pub px_scale_factor: f32, - fn glyph_info_no_cache_or_fallback(&mut self, c: char) -> Option<(FontIndex, GlyphInfo)> { - for (font_index, font_impl) in self.fonts.iter().enumerate() { - if let Some(glyph_info) = font_impl.glyph_info(c) { - self.glyph_info_cache.insert(c, (font_index, glyph_info)); - return Some((font_index, glyph_info)); - } - } - None - } + /// Vertical offset, in UI points. + pub y_offset_in_points: f32, + + /// This is the distance from the top to the baseline. + /// + /// Unit: points. + pub ascent: f32, + + /// Height of one row of text in points. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. + pub row_height: f32, } /// Code points that will always be invisible (zero width). @@ -532,3 +596,28 @@ fn invisible_char(c: char) -> bool { | '\u{FEFF}' // ZERO WIDTH NO-BREAK SPACE ) } + +#[inline] +pub(super) fn is_cjk_ideograph(c: char) -> bool { + ('\u{4E00}' <= c && c <= '\u{9FFF}') + || ('\u{3400}' <= c && c <= '\u{4DBF}') + || ('\u{2B740}' <= c && c <= '\u{2B81F}') +} + +#[inline] +pub(super) fn is_kana(c: char) -> bool { + ('\u{3040}' <= c && c <= '\u{309F}') // Hiragana block + || ('\u{30A0}' <= c && c <= '\u{30FF}') // Katakana block +} + +#[inline] +pub(super) fn is_cjk(c: char) -> bool { + // TODO(bigfarts): Add support for Korean Hangul. + is_cjk_ideograph(c) || is_kana(c) +} + +#[inline] +pub(super) fn is_cjk_break_allowed(c: char) -> bool { + // See: https://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages#Characters_not_permitted_on_the_start_of_a_line. + !")]}〕〉》」』】〙〗〟'\"⦆»ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻‐゠–〜?!‼⁇⁈⁉・、:;,。.".contains(c) +} diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index f9c1f4f8d..a78242fd4 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -1,11 +1,16 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::{ + collections::BTreeMap, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; use crate::{ AlphaFromCoverage, TextureAtlas, - mutex::{Mutex, MutexGuard}, text::{ Galley, LayoutJob, LayoutSection, - font::{Font, FontImpl}, + font::{Font, FontImpl, GlyphInfo}, }, }; use emath::{NumExt as _, OrderedFloat}; @@ -179,13 +184,6 @@ pub struct FontTweak { /// /// Example value: `2.0`. pub y_offset: f32, - - /// When using this font's metrics to layout a row, - /// shift the entire row downwards by this fraction of the font size (in points). - /// - /// A positive value shifts the text downwards. - /// A negative value shifts it upwards. - pub baseline_offset_factor: f32, } impl Default for FontTweak { @@ -194,7 +192,6 @@ impl Default for FontTweak { scale: 1.0, y_offset_factor: 0.0, y_offset: 0.0, - baseline_offset_factor: 0.0, } } } @@ -407,6 +404,92 @@ impl FontDefinitions { } } +/// Unique ID for looking up a single font face/file. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) struct FontFaceKey(u64); + +impl FontFaceKey { + pub const INVALID: Self = Self(0); + + fn new() -> Self { + static KEY_COUNTER: AtomicU64 = AtomicU64::new(1); + Self(crate::util::hash( + KEY_COUNTER.fetch_add(1, Ordering::Relaxed), + )) + } +} + +// Safe, because we hash the value in the constructor. +impl nohash_hasher::IsEnabled for FontFaceKey {} + +/// Cached data for working with a font family (e.g. doing character lookups). +#[derive(Debug)] +pub(super) struct CachedFamily { + pub fonts: Vec, + + /// Lazily calculated. + pub characters: Option>>, + + pub replacement_glyph: (FontFaceKey, GlyphInfo), + + pub glyph_info_cache: ahash::HashMap, +} + +impl CachedFamily { + fn new( + fonts: Vec, + fonts_by_id: &mut nohash_hasher::IntMap, + ) -> Self { + if fonts.is_empty() { + return Self { + fonts, + characters: None, + replacement_glyph: (FontFaceKey::INVALID, GlyphInfo::INVISIBLE), + glyph_info_cache: Default::default(), + }; + } + + let mut slf = Self { + fonts, + characters: None, + replacement_glyph: (FontFaceKey::INVALID, GlyphInfo::INVISIBLE), + glyph_info_cache: Default::default(), + }; + + const PRIMARY_REPLACEMENT_CHAR: char = '◻'; // white medium square + const FALLBACK_REPLACEMENT_CHAR: char = '?'; // fallback for the fallback + + let replacement_glyph = slf + .glyph_info_no_cache_or_fallback(PRIMARY_REPLACEMENT_CHAR, fonts_by_id) + .or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR, fonts_by_id)) + .unwrap_or_else(|| { + #[cfg(feature = "log")] + log::warn!( + "Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}. Will use empty glyph." + ); + (FontFaceKey::INVALID, GlyphInfo::INVISIBLE) + }); + slf.replacement_glyph = replacement_glyph; + + slf + } + + pub(crate) fn glyph_info_no_cache_or_fallback( + &mut self, + c: char, + fonts_by_id: &mut nohash_hasher::IntMap, + ) -> Option<(FontFaceKey, GlyphInfo)> { + for font_key in &self.fonts { + let font_impl = fonts_by_id.get_mut(font_key).expect("Nonexistent font ID"); + if let Some(glyph_info) = font_impl.glyph_info(c) { + self.glyph_info_cache.insert(c, (*font_key, glyph_info)); + return Some((*font_key, glyph_info)); + } + } + None + } +} + // ---------------------------------------------------------------------------- /// The collection of fonts used by `epaint`. @@ -418,31 +501,25 @@ impl FontDefinitions { /// If you are using `egui`, use `egui::Context::set_fonts` and `egui::Context::fonts`. /// /// You need to call [`Self::begin_pass`] and [`Self::font_image_delta`] once every frame. -#[derive(Clone)] -pub struct Fonts(Arc>); +pub struct Fonts { + pub fonts: FontsImpl, + galley_cache: GalleyCache, +} impl Fonts { /// Create a new [`Fonts`] for text layout. /// This call is expensive, so only create one [`Fonts`] and then reuse it. /// - /// * `pixels_per_point`: how many physical pixels per logical "point". /// * `max_texture_side`: largest supported texture size (one side). 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, - text_alpha_from_coverage, - definitions, - ), + Self { + fonts: FontsImpl::new(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 @@ -453,114 +530,156 @@ impl Fonts { /// 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, + &mut self, 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 max_texture_side_changed = self.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 - || text_alpha_from_coverage_changed - || font_atlas_almost_full; + self.fonts.atlas.text_alpha_from_coverage != text_alpha_from_coverage; + let font_atlas_almost_full = self.fonts.atlas.fill_ratio() > 0.8; + let needs_recreate = + max_texture_side_changed || text_alpha_from_coverage_changed || font_atlas_almost_full; if needs_recreate { - let definitions = fonts_and_cache.fonts.definitions.clone(); + let definitions = self.fonts.definitions.clone(); - *fonts_and_cache = FontsAndCache { - fonts: FontsImpl::new( - pixels_per_point, - max_texture_side, - text_alpha_from_coverage, - definitions, - ), + *self = Self { + fonts: FontsImpl::new(max_texture_side, text_alpha_from_coverage, definitions), galley_cache: Default::default(), }; } - fonts_and_cache.galley_cache.flush_cache(); + self.galley_cache.flush_cache(); } /// Call at the end of each frame (before painting) to get the change to the font texture since last call. - pub fn font_image_delta(&self) -> Option { - self.lock().fonts.atlas.lock().take_delta() - } - - /// Access the underlying [`FontsAndCache`]. - #[doc(hidden)] - #[inline] - pub fn lock(&self) -> MutexGuard<'_, FontsAndCache> { - self.0.lock() - } - - #[inline] - pub fn pixels_per_point(&self) -> f32 { - self.lock().fonts.pixels_per_point + pub fn font_image_delta(&mut self) -> Option { + self.fonts.atlas.take_delta() } #[inline] pub fn max_texture_side(&self) -> usize { - self.lock().fonts.max_texture_side + self.fonts.max_texture_side + } + + #[inline] + pub fn definitions(&self) -> &FontDefinitions { + &self.fonts.definitions } /// The font atlas. /// Pass this to [`crate::Tessellator`]. - pub fn texture_atlas(&self) -> Arc> { - self.lock().fonts.atlas.clone() + pub fn texture_atlas(&self) -> &TextureAtlas { + &self.fonts.atlas } /// The full font atlas image. #[inline] pub fn image(&self) -> crate::ColorImage { - self.lock().fonts.atlas.lock().image().clone() + self.fonts.atlas.image().clone() } /// Current size of the font image. /// Pass this to [`crate::Tessellator`]. pub fn font_image_size(&self) -> [usize; 2] { - self.lock().fonts.atlas.lock().size() - } - - /// Width of this character in points. - #[inline] - pub fn glyph_width(&self, font_id: &FontId, c: char) -> f32 { - self.lock().fonts.glyph_width(font_id, c) + self.fonts.atlas.size() } /// Can we display this glyph? - #[inline] - pub fn has_glyph(&self, font_id: &FontId, c: char) -> bool { - self.lock().fonts.has_glyph(font_id, c) + pub fn has_glyph(&mut self, font_id: &FontId, c: char) -> bool { + self.fonts.font(&font_id.family).has_glyph(c) } /// Can we display all the glyphs in this text? - pub fn has_glyphs(&self, font_id: &FontId, s: &str) -> bool { - self.lock().fonts.has_glyphs(font_id, s) + pub fn has_glyphs(&mut self, font_id: &FontId, s: &str) -> bool { + self.fonts.font(&font_id.family).has_glyphs(s) + } + + pub fn num_galleys_in_cache(&self) -> usize { + self.galley_cache.num_galleys_in_cache() + } + + /// How full is the font atlas? + /// + /// This increases as new fonts and/or glyphs are used, + /// but can also decrease in a call to [`Self::begin_pass`]. + pub fn font_atlas_fill_ratio(&self) -> f32 { + self.fonts.atlas.fill_ratio() + } + + /// Returns a [`FontsView`] with the given `pixels_per_point` that can be used to do text layout. + pub fn with_pixels_per_point(&mut self, pixels_per_point: f32) -> FontsView<'_> { + FontsView { + fonts: &mut self.fonts, + galley_cache: &mut self.galley_cache, + pixels_per_point, + } + } +} + +// ---------------------------------------------------------------------------- + +/// The context's collection of fonts, with this context's `pixels_per_point`. This is what you use to do text layout. +pub struct FontsView<'a> { + pub fonts: &'a mut FontsImpl, + galley_cache: &'a mut GalleyCache, + pixels_per_point: f32, +} + +impl FontsView<'_> { + #[inline] + pub fn max_texture_side(&self) -> usize { + self.fonts.max_texture_side + } + + #[inline] + pub fn definitions(&self) -> &FontDefinitions { + &self.fonts.definitions + } + + /// The full font atlas image. + #[inline] + pub fn image(&self) -> crate::ColorImage { + self.fonts.atlas.image().clone() + } + + /// Current size of the font image. + /// Pass this to [`crate::Tessellator`]. + pub fn font_image_size(&self) -> [usize; 2] { + self.fonts.atlas.size() + } + + /// Width of this character in points. + pub fn glyph_width(&mut self, font_id: &FontId, c: char) -> f32 { + self.fonts + .font(&font_id.family) + .glyph_width(c, font_id.size) + } + + /// Can we display this glyph? + pub fn has_glyph(&mut self, font_id: &FontId, c: char) -> bool { + self.fonts.font(&font_id.family).has_glyph(c) + } + + /// Can we display all the glyphs in this text? + pub fn has_glyphs(&mut self, font_id: &FontId, s: &str) -> bool { + self.fonts.font(&font_id.family).has_glyphs(s) } /// Height of one row of text in points. /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - #[inline] - pub fn row_height(&self, font_id: &FontId) -> f32 { - self.lock().fonts.row_height(font_id) + pub fn row_height(&mut self, font_id: &FontId) -> f32 { + self.fonts + .font(&font_id.family) + .scaled_metrics(self.pixels_per_point, font_id.size) + .row_height } /// List of all known font families. pub fn families(&self) -> Vec { - self.lock() - .fonts - .definitions - .families - .keys() - .cloned() - .collect() + self.fonts.definitions.families.keys().cloned().collect() } /// Layout some text. @@ -571,27 +690,33 @@ impl Fonts { /// /// The implementation uses memoization so repeated calls are cheap. #[inline] - pub fn layout_job(&self, job: LayoutJob) -> Arc { - self.lock().layout_job(job) + pub fn layout_job(&mut self, job: LayoutJob) -> Arc { + let allow_split_paragraphs = true; // Optimization for editing text with many paragraphs. + self.galley_cache.layout( + self.fonts, + self.pixels_per_point, + job, + allow_split_paragraphs, + ) } pub fn num_galleys_in_cache(&self) -> usize { - self.lock().galley_cache.num_galleys_in_cache() + self.galley_cache.num_galleys_in_cache() } /// How full is the font atlas? /// /// This increases as new fonts and/or glyphs are used, - /// but can also decrease in a call to [`Self::begin_pass`]. + /// but can also decrease in a call to [`Fonts::begin_pass`]. pub fn font_atlas_fill_ratio(&self) -> f32 { - self.lock().fonts.atlas.lock().fill_ratio() + self.fonts.atlas.fill_ratio() } /// Will wrap text at the given width and line break at `\n`. /// /// The implementation uses memoization so repeated calls are cheap. pub fn layout( - &self, + &mut self, text: String, font_id: FontId, color: crate::Color32, @@ -605,7 +730,7 @@ impl Fonts { /// /// The implementation uses memoization so repeated calls are cheap. pub fn layout_no_wrap( - &self, + &mut self, text: String, font_id: FontId, color: crate::Color32, @@ -618,7 +743,7 @@ impl Fonts { /// /// The implementation uses memoization so repeated calls are cheap. pub fn layout_delayed_color( - &self, + &mut self, text: String, font_id: FontId, wrap_width: f32, @@ -629,118 +754,75 @@ impl Fonts { // ---------------------------------------------------------------------------- -pub struct FontsAndCache { - pub fonts: FontsImpl, - galley_cache: GalleyCache, -} - -impl FontsAndCache { - fn layout_job(&mut self, job: LayoutJob) -> Arc { - let allow_split_paragraphs = true; // Optimization for editing text with many paragraphs. - self.galley_cache - .layout(&mut self.fonts, job, allow_split_paragraphs) - } -} - -// ---------------------------------------------------------------------------- - /// The collection of fonts used by `epaint`. /// /// Required in order to paint text. pub struct FontsImpl { - pixels_per_point: f32, max_texture_side: usize, definitions: FontDefinitions, - atlas: Arc>, - font_impl_cache: FontImplCache, - sized_family: ahash::HashMap<(OrderedFloat, FontFamily), Font>, + atlas: TextureAtlas, + fonts_by_id: nohash_hasher::IntMap, + fonts_by_name: ahash::HashMap, + family_cache: ahash::HashMap, } impl FontsImpl { /// Create a new [`FontsImpl`] for text layout. /// This call is expensive, so only create one [`FontsImpl`] and then reuse it. pub fn new( - pixels_per_point: f32, max_texture_side: usize, text_alpha_from_coverage: AlphaFromCoverage, definitions: FontDefinitions, ) -> Self { - assert!( - 0.0 < pixels_per_point && pixels_per_point < 100.0, - "pixels_per_point out of range: {pixels_per_point}" - ); - 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], text_alpha_from_coverage); - let atlas = Arc::new(Mutex::new(atlas)); - - let font_impl_cache = - FontImplCache::new(atlas.clone(), pixels_per_point, &definitions.font_data); + let mut fonts_by_id: nohash_hasher::IntMap = Default::default(); + let mut font_impls: ahash::HashMap = Default::default(); + for (name, font_data) in &definitions.font_data { + let tweak = font_data.tweak; + let ab_glyph = ab_glyph_font_from_font_data(name, font_data); + let font_impl = FontImpl::new(name.clone(), ab_glyph, tweak); + let key = FontFaceKey::new(); + fonts_by_id.insert(key, font_impl); + font_impls.insert(name.clone(), key); + } Self { - pixels_per_point, max_texture_side, definitions, atlas, - font_impl_cache, - sized_family: Default::default(), + fonts_by_id, + fonts_by_name: font_impls, + family_cache: Default::default(), } } - #[inline(always)] - pub fn pixels_per_point(&self) -> f32 { - self.pixels_per_point - } + /// Get the right font implementation from [`FontFamily`]. + pub fn font(&mut self, family: &FontFamily) -> Font<'_> { + let cached_family = self.family_cache.entry(family.clone()).or_insert_with(|| { + let fonts = &self.definitions.families.get(family); + let fonts = + fonts.unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts")); - #[inline] - pub fn definitions(&self) -> &FontDefinitions { - &self.definitions - } + let fonts: Vec = fonts + .iter() + .map(|font_name| { + *self + .fonts_by_name + .get(font_name) + .unwrap_or_else(|| panic!("No font data found for {font_name:?}")) + }) + .collect(); - /// Get the right font implementation from size and [`FontFamily`]. - pub fn font(&mut self, font_id: &FontId) -> &mut Font { - let FontId { size, family } = font_id; - let mut size = *size; - size = size.at_least(0.1).at_most(2048.0); - - self.sized_family - .entry((OrderedFloat(size), family.clone())) - .or_insert_with(|| { - let fonts = &self.definitions.families.get(family); - let fonts = fonts - .unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts")); - - let fonts: Vec> = fonts - .iter() - .map(|font_name| self.font_impl_cache.font_impl(size, font_name)) - .collect(); - - Font::new(fonts) - }) - } - - /// Width of this character in points. - fn glyph_width(&mut self, font_id: &FontId, c: char) -> f32 { - self.font(font_id).glyph_width(c) - } - - /// Can we display this glyph? - pub fn has_glyph(&mut self, font_id: &FontId, c: char) -> bool { - self.font(font_id).has_glyph(c) - } - - /// Can we display all the glyphs in this text? - pub fn has_glyphs(&mut self, font_id: &FontId, s: &str) -> bool { - self.font(font_id).has_glyphs(s) - } - - /// Height of one row of text in points. - /// - /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - fn row_height(&mut self, font_id: &FontId) -> f32 { - self.font(font_id).row_height() + CachedFamily::new(fonts, &mut self.fonts_by_id) + }); + Font { + fonts_by_id: &mut self.fonts_by_id, + cached_family, + atlas: &mut self.atlas, + } } } @@ -770,6 +852,7 @@ impl GalleyCache { &mut self, fonts: &mut FontsImpl, mut job: LayoutJob, + pixels_per_point: f32, allow_split_paragraphs: bool, ) -> (u64, Arc) { if job.wrap.max_width.is_finite() { @@ -797,7 +880,7 @@ impl GalleyCache { job.wrap.max_width = job.wrap.max_width.round(); } - let hash = crate::util::hash(&job); // TODO(emilk): even faster hasher? + let hash = crate::util::hash((&job, OrderedFloat(pixels_per_point))); // TODO(emilk): even faster hasher? let galley = match self.cache.entry(hash) { std::collections::hash_map::Entry::Occupied(entry) => { @@ -825,14 +908,13 @@ 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_individually(fonts, &job); + self.layout_each_paragraph_individually(fonts, &job, pixels_per_point); debug_assert_eq!( child_hashes.len(), child_galleys.len(), "Bug in `layout_each_paragraph_individuallly`" ); - let galley = - Arc::new(Galley::concat(job, &child_galleys, fonts.pixels_per_point)); + let galley = Arc::new(Galley::concat(job, &child_galleys, pixels_per_point)); self.cache.insert( hash, @@ -844,7 +926,7 @@ impl GalleyCache { ); galley } else { - let galley = super::layout(fonts, job); + let galley = super::layout(fonts, pixels_per_point, job); let galley = Arc::new(galley); entry.insert(CachedGalley { last_used: self.generation, @@ -862,10 +944,12 @@ impl GalleyCache { fn layout( &mut self, fonts: &mut FontsImpl, + pixels_per_point: f32, job: LayoutJob, allow_split_paragraphs: bool, ) -> Arc { - self.layout_internal(fonts, job, allow_split_paragraphs).1 + self.layout_internal(fonts, job, pixels_per_point, allow_split_paragraphs) + .1 } /// Split on `\n` and lay out (and cache) each paragraph individually. @@ -873,6 +957,7 @@ impl GalleyCache { &mut self, fonts: &mut FontsImpl, job: &LayoutJob, + pixels_per_point: f32, ) -> (Vec>, Vec) { profiling::function_scope!(); @@ -952,7 +1037,8 @@ impl GalleyCache { } // TODO(emilk): we could lay out each paragraph in parallel to get a nice speedup on multicore machines. - let (hash, galley) = self.layout_internal(fonts, paragraph_job, false); + let (hash, galley) = + self.layout_internal(fonts, paragraph_job, pixels_per_point, false); child_hashes.push(hash); // This will prevent us from invalidating cache entries unnecessarily: @@ -996,77 +1082,6 @@ fn should_cache_each_paragraph_individually(job: &LayoutJob) -> bool { job.break_on_newline && job.wrap.max_rows == usize::MAX && job.text.contains('\n') } -// ---------------------------------------------------------------------------- - -struct FontImplCache { - atlas: Arc>, - pixels_per_point: f32, - ab_glyph_fonts: BTreeMap, - - /// Map font pixel sizes and names to the cached [`FontImpl`]. - cache: ahash::HashMap<(u32, String), Arc>, -} - -impl FontImplCache { - pub fn new( - atlas: Arc>, - pixels_per_point: f32, - font_data: &BTreeMap>, - ) -> Self { - let ab_glyph_fonts = font_data - .iter() - .map(|(name, font_data)| { - let tweak = font_data.tweak; - let ab_glyph = ab_glyph_font_from_font_data(name, font_data); - (name.clone(), (tweak, ab_glyph)) - }) - .collect(); - - Self { - atlas, - pixels_per_point, - ab_glyph_fonts, - cache: Default::default(), - } - } - - pub fn font_impl(&mut self, scale_in_points: f32, font_name: &str) -> Arc { - use ab_glyph::Font as _; - - let (tweak, ab_glyph_font) = self - .ab_glyph_fonts - .get(font_name) - .unwrap_or_else(|| panic!("No font data found for {font_name:?}")) - .clone(); - - let scale_in_pixels = self.pixels_per_point * scale_in_points; - - // Scale the font properly (see https://github.com/emilk/egui/issues/2068). - let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| { - panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)") - }); - let font_scaling = ab_glyph_font.height_unscaled() / units_per_em; - let scale_in_pixels = scale_in_pixels * font_scaling; - - self.cache - .entry(( - (scale_in_pixels * tweak.scale).round() as u32, - font_name.to_owned(), - )) - .or_insert_with(|| { - Arc::new(FontImpl::new( - self.atlas.clone(), - self.pixels_per_point, - font_name.to_owned(), - ab_glyph_font, - scale_in_pixels, - tweak, - )) - }) - .clone() - } -} - #[cfg(feature = "default_fonts")] #[cfg(test)] mod tests { @@ -1178,7 +1193,6 @@ mod tests { for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] { let max_texture_side = 4096; let mut fonts = FontsImpl::new( - pixels_per_point, max_texture_side, AlphaFromCoverage::default(), FontDefinitions::default(), @@ -1190,9 +1204,19 @@ mod tests { job.halign = halign; job.justify = justify; - let whole = GalleyCache::default().layout(&mut fonts, job.clone(), false); + let whole = GalleyCache::default().layout( + &mut fonts, + pixels_per_point, + job.clone(), + false, + ); - let split = GalleyCache::default().layout(&mut fonts, job.clone(), true); + let split = GalleyCache::default().layout( + &mut fonts, + pixels_per_point, + job.clone(), + true, + ); for (i, row) in whole.rows.iter().enumerate() { println!( @@ -1231,7 +1255,6 @@ mod tests { for pixels_per_point in pixels_per_point { let mut fonts = FontsImpl::new( - pixels_per_point, 1024, AlphaFromCoverage::default(), FontDefinitions::default(), @@ -1244,12 +1267,13 @@ mod tests { job.round_output_to_gui = round_output_to_gui; - let galley_wrapped = layout(&mut fonts, job.clone().into()); + let galley_wrapped = + layout(&mut fonts, pixels_per_point, job.clone().into()); job.wrap = TextWrapping::no_max_width(); let text = job.text.clone(); - let galley_unwrapped = layout(&mut fonts, job.into()); + let galley_unwrapped = layout(&mut fonts, pixels_per_point, job.into()); let intrinsic_size = galley_wrapped.intrinsic_size(); let unwrapped_size = galley_unwrapped.size(); diff --git a/crates/epaint/src/text/mod.rs b/crates/epaint/src/text/mod.rs index cf5c8ebfc..e0f4a3a98 100644 --- a/crates/epaint/src/text/mod.rs +++ b/crates/epaint/src/text/mod.rs @@ -12,7 +12,7 @@ pub const TAB_SIZE: usize = 4; pub use { fonts::{ FontData, FontDefinitions, FontFamily, FontId, FontInsert, FontPriority, FontTweak, Fonts, - FontsImpl, InsertFontFamily, + FontsImpl, FontsView, InsertFontFamily, }, text_layout::*, text_layout_types::*, diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index fd71506ef..e65f10701 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -2,7 +2,14 @@ use std::sync::Arc; use emath::{Align, GuiRounding as _, NumExt as _, Pos2, Rect, Vec2, pos2, vec2}; -use crate::{Color32, Mesh, Stroke, Vertex, stroke::PathStroke, text::font::Font}; +use crate::{ + Color32, Mesh, Stroke, Vertex, + stroke::PathStroke, + text::{ + font::{ScaledMetrics, is_cjk, is_cjk_break_allowed}, + fonts::FontFaceKey, + }, +}; use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals}; @@ -41,8 +48,8 @@ impl PointScale { /// Temporary storage before line-wrapping. #[derive(Clone)] struct Paragraph { - /// Start of the next glyph to be added. - pub cursor_x: f32, + /// Start of the next glyph to be added. In screen-space / physical pixels. + pub cursor_x_px: f32, /// This is included in case there are no glyphs pub section_index_at_start: u32, @@ -56,7 +63,7 @@ struct Paragraph { impl Paragraph { pub fn from_section_index(section_index_at_start: u32) -> Self { Self { - cursor_x: 0.0, + cursor_x_px: 0.0, section_index_at_start, glyphs: vec![], empty_paragraph_height: 0.0, @@ -66,9 +73,9 @@ impl Paragraph { /// Layout text into a [`Galley`]. /// -/// In most cases you should use [`crate::Fonts::layout_job`] instead +/// In most cases you should use [`crate::FontsView::layout_job`] instead /// since that memoizes the input, making subsequent layouting of the same text much faster. -pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { +pub fn layout(fonts: &mut FontsImpl, pixels_per_point: f32, job: Arc) -> Galley { profiling::function_scope!(); if job.wrap.max_rows == 0 { @@ -80,7 +87,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { mesh_bounds: Rect::NOTHING, num_vertices: 0, num_indices: 0, - pixels_per_point: fonts.pixels_per_point(), + pixels_per_point, elided: true, intrinsic_size: Vec2::ZERO, }; @@ -90,10 +97,17 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { let mut paragraphs = vec![Paragraph::from_section_index(0)]; for (section_index, section) in job.sections.iter().enumerate() { - layout_section(fonts, &job, section_index as u32, section, &mut paragraphs); + layout_section( + fonts, + pixels_per_point, + &job, + section_index as u32, + section, + &mut paragraphs, + ); } - let point_scale = PointScale::new(fonts.pixels_per_point()); + let point_scale = PointScale::new(pixels_per_point); let intrinsic_size = calculate_intrinsic_size(point_scale, &job, ¶graphs); @@ -102,7 +116,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { if elided { if let Some(last_placed) = rows.last_mut() { let last_row = Arc::make_mut(&mut last_placed.row); - replace_last_glyph_with_overflow_character(fonts, &job, last_row); + replace_last_glyph_with_overflow_character(fonts, pixels_per_point, &job, last_row); if let Some(last) = last_row.glyphs.last() { last_row.size.x = last.max_x(); } @@ -133,6 +147,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { // Ignores the Y coordinate. fn layout_section( fonts: &mut FontsImpl, + pixels_per_point: f32, job: &LayoutJob, section_index: u32, section: &LayoutSection, @@ -143,11 +158,13 @@ fn layout_section( byte_range, format, } = section; - let font = fonts.font(&format.font_id); + let mut font = fonts.font(&format.font_id.family); + let font_size = format.font_id.size; + let font_metrics = font.scaled_metrics(pixels_per_point, font_size); let line_height = section .format .line_height - .unwrap_or_else(|| font.row_height()); + .unwrap_or(font_metrics.row_height); let extra_letter_spacing = section.format.extra_letter_spacing; let mut paragraph = out_paragraphs.last_mut().unwrap(); @@ -155,40 +172,70 @@ fn layout_section( paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? } - paragraph.cursor_x += leading_space; + paragraph.cursor_x_px += leading_space * pixels_per_point; let mut last_glyph_id = None; + // Optimization: only recompute `ScaledMetrics` when the concrete `FontImpl` changes. + let mut current_font = FontFaceKey::INVALID; + let mut current_font_impl_metrics = ScaledMetrics::default(); + for chr in job.text[byte_range.clone()].chars() { if job.break_on_newline && chr == '\n' { out_paragraphs.push(Paragraph::from_section_index(section_index)); paragraph = out_paragraphs.last_mut().unwrap(); paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? } else { - let (font_impl, glyph_info) = font.font_impl_and_glyph_info(chr); - if let Some(font_impl) = font_impl { - if let Some(last_glyph_id) = last_glyph_id { - paragraph.cursor_x += font_impl.pair_kerning(last_glyph_id, glyph_info.id); - paragraph.cursor_x += extra_letter_spacing; - } + let (font_id, glyph_info) = font.glyph_info(chr); + let mut font_impl = font.fonts_by_id.get_mut(&font_id); + if current_font != font_id { + current_font = font_id; + current_font_impl_metrics = font_impl + .as_ref() + .map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size)) + .unwrap_or_default(); } + if let (Some(font_impl), Some(last_glyph_id), Some(glyph_id)) = + (&font_impl, last_glyph_id, glyph_info.id) + { + paragraph.cursor_x_px += font_impl.pair_kerning_pixels( + ¤t_font_impl_metrics, + last_glyph_id, + glyph_id, + ); + + // Only apply extra_letter_spacing to glyphs after the first one: + paragraph.cursor_x_px += extra_letter_spacing * pixels_per_point; + } + + let (glyph_alloc, physical_x) = if let Some(font_impl) = font_impl.as_mut() { + font_impl.allocate_glyph( + font.atlas, + ¤t_font_impl_metrics, + glyph_info, + chr, + paragraph.cursor_x_px, + ) + } else { + Default::default() + }; + paragraph.glyphs.push(Glyph { chr, - pos: pos2(paragraph.cursor_x, f32::NAN), - advance_width: glyph_info.advance_width, + pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN), + advance_width: glyph_alloc.advance_width_px / pixels_per_point, line_height, - font_impl_height: font_impl.map_or(0.0, |f| f.row_height()), - font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()), - font_height: font.row_height(), - font_ascent: font.ascent(), - uv_rect: glyph_info.uv_rect, + font_impl_height: current_font_impl_metrics.row_height, + font_impl_ascent: current_font_impl_metrics.ascent, + font_height: font_metrics.row_height, + font_ascent: font_metrics.ascent, + uv_rect: glyph_alloc.uv_rect, section_index, }); - paragraph.cursor_x += glyph_info.advance_width; - paragraph.cursor_x = font.round_to_pixel(paragraph.cursor_x); - last_glyph_id = Some(glyph_info.id); + paragraph.cursor_x_px += glyph_alloc.advance_width_px; + last_glyph_id = Some(glyph_alloc.id); } } } @@ -398,149 +445,105 @@ fn line_break( /// Called before we have any Y coordinates. fn replace_last_glyph_with_overflow_character( fonts: &mut FontsImpl, + pixels_per_point: f32, job: &LayoutJob, row: &mut Row, ) { - fn row_width(row: &Row) -> f32 { - if let (Some(first), Some(last)) = (row.glyphs.first(), row.glyphs.last()) { - last.max_x() - first.pos.x - } else { - 0.0 - } - } - - fn row_height(section: &LayoutSection, font: &Font) -> f32 { - section - .format - .line_height - .unwrap_or_else(|| font.row_height()) - } - let Some(overflow_character) = job.wrap.overflow_character else { return; }; - // We always try to just append the character first: - if let Some(last_glyph) = row.glyphs.last() { - let section_index = last_glyph.section_index; - let section = &job.sections[section_index as usize]; - let font = fonts.font(§ion.format.font_id); - let line_height = row_height(section, font); - - let (_, last_glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); - - let mut x = last_glyph.pos.x + last_glyph.advance_width; - - let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character); - - { - // Kerning: - x += section.format.extra_letter_spacing; - if let Some(font_impl) = font_impl { - x += font_impl.pair_kerning(last_glyph_info.id, replacement_glyph_info.id); - } - } - - row.glyphs.push(Glyph { - chr: overflow_character, - pos: pos2(x, f32::NAN), - advance_width: replacement_glyph_info.advance_width, - line_height, - font_impl_height: font_impl.map_or(0.0, |f| f.row_height()), - font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()), - font_height: font.row_height(), - font_ascent: font.ascent(), - uv_rect: replacement_glyph_info.uv_rect, - section_index, - }); - } else { - let section_index = row.section_index_at_start; - let section = &job.sections[section_index as usize]; - let font = fonts.font(§ion.format.font_id); - let line_height = row_height(section, font); - - let x = 0.0; // TODO(emilk): heed paragraph leading_space 😬 - - let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character); - - row.glyphs.push(Glyph { - chr: overflow_character, - pos: pos2(x, f32::NAN), - advance_width: replacement_glyph_info.advance_width, - line_height, - font_impl_height: font_impl.map_or(0.0, |f| f.row_height()), - font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()), - font_height: font.row_height(), - font_ascent: font.ascent(), - uv_rect: replacement_glyph_info.uv_rect, - section_index, - }); - } - - if row_width(row) <= job.effective_wrap_width() || row.glyphs.len() == 1 { - return; // we are done - } - - // We didn't fit it. Remove it again… - row.glyphs.pop(); - - // …then go into a loop where we replace the last character with the overflow character - // until we fit within the max_width: - + let mut section_index = row + .glyphs + .last() + .map(|g| g.section_index) + .unwrap_or(row.section_index_at_start); loop { - let (prev_glyph, last_glyph) = match row.glyphs.as_mut_slice() { - [.., prev, last] => (Some(prev), last), - [.., last] => (None, last), - _ => { - unreachable!("We've already explicitly handled the empty row"); - } + let section = &job.sections[section_index as usize]; + let extra_letter_spacing = section.format.extra_letter_spacing; + let mut font = fonts.font(§ion.format.font_id.family); + let font_size = section.format.font_id.size; + + let (font_id, glyph_info) = font.glyph_info(overflow_character); + let mut font_impl = font.fonts_by_id.get_mut(&font_id); + let font_impl_metrics = font_impl + .as_mut() + .map(|f| f.scaled_metrics(pixels_per_point, font_size)) + .unwrap_or_default(); + + let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() { + // Kern the overflow character properly + let pair_kerning = font_impl + .as_mut() + .map(|font_impl| { + if let (Some(prev_glyph_id), Some(overflow_glyph_id)) = ( + font_impl.glyph_info(prev_glyph.chr).and_then(|g| g.id), + font_impl.glyph_info(overflow_character).and_then(|g| g.id), + ) { + font_impl.pair_kerning(&font_impl_metrics, prev_glyph_id, overflow_glyph_id) + } else { + 0.0 + } + }) + .unwrap_or_default(); + + prev_glyph.max_x() + extra_letter_spacing + pair_kerning + } else { + 0.0 // TODO(emilk): heed paragraph leading_space 😬 }; - let section = &job.sections[last_glyph.section_index as usize]; - let extra_letter_spacing = section.format.extra_letter_spacing; - let font = fonts.font(§ion.format.font_id); + let replacement_glyph_width = font_impl + .as_mut() + .and_then(|f| f.glyph_info(overflow_character)) + .map(|i| i.advance_width_unscaled.0 * font_impl_metrics.px_scale_factor) + .unwrap_or_default(); - if let Some(prev_glyph) = prev_glyph { - let prev_glyph_id = font.font_impl_and_glyph_info(prev_glyph.chr).1.id; + // Check if we're within width budget: + if overflow_glyph_x + replacement_glyph_width <= job.effective_wrap_width() + || row.glyphs.is_empty() + { + // we are done - // Undo kerning with previous glyph: - let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); - last_glyph.pos.x -= extra_letter_spacing; - if let Some(font_impl) = font_impl { - last_glyph.pos.x -= font_impl.pair_kerning(prev_glyph_id, glyph_info.id); - } + let (replacement_glyph_alloc, physical_x) = font_impl + .as_mut() + .map(|f| { + f.allocate_glyph( + font.atlas, + &font_impl_metrics, + glyph_info, + overflow_character, + overflow_glyph_x * pixels_per_point, + ) + }) + .unwrap_or_default(); - // Replace the glyph: - last_glyph.chr = overflow_character; - let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); - last_glyph.advance_width = glyph_info.advance_width; - last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent()); - last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height()); - last_glyph.uv_rect = glyph_info.uv_rect; + let font_metrics = font.scaled_metrics(pixels_per_point, font_size); + let line_height = section + .format + .line_height + .unwrap_or(font_metrics.row_height); - // Reapply kerning: - last_glyph.pos.x += extra_letter_spacing; - if let Some(font_impl) = font_impl { - last_glyph.pos.x += font_impl.pair_kerning(prev_glyph_id, glyph_info.id); - } - - // Check if we're within width budget: - if row_width(row) <= job.effective_wrap_width() || row.glyphs.len() == 1 { - return; // We are done - } - - // We didn't fit - pop the last glyph and try again. - row.glyphs.pop(); - } else { - // Just replace and be done with it. - last_glyph.chr = overflow_character; - let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); - last_glyph.advance_width = glyph_info.advance_width; - last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent()); - last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height()); - last_glyph.uv_rect = glyph_info.uv_rect; + row.glyphs.push(Glyph { + chr: overflow_character, + pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN), + advance_width: replacement_glyph_alloc.advance_width_px / pixels_per_point, + line_height, + font_impl_height: font_impl_metrics.row_height, + font_impl_ascent: font_impl_metrics.ascent, + font_height: font_metrics.row_height, + font_ascent: font_metrics.ascent, + uv_rect: replacement_glyph_alloc.uv_rect, + section_index, + }); return; } + + // We didn't fit - pop the last glyph and try again. + if let Some(last_glyph) = row.glyphs.pop() { + section_index = last_glyph.section_index; + } else { + section_index = row.section_index_at_start; + } } } @@ -1043,31 +1046,6 @@ impl RowBreakCandidates { } } -#[inline] -fn is_cjk_ideograph(c: char) -> bool { - ('\u{4E00}' <= c && c <= '\u{9FFF}') - || ('\u{3400}' <= c && c <= '\u{4DBF}') - || ('\u{2B740}' <= c && c <= '\u{2B81F}') -} - -#[inline] -fn is_kana(c: char) -> bool { - ('\u{3040}' <= c && c <= '\u{309F}') // Hiragana block - || ('\u{30A0}' <= c && c <= '\u{30FF}') // Katakana block -} - -#[inline] -fn is_cjk(c: char) -> bool { - // TODO(bigfarts): Add support for Korean Hangul. - is_cjk_ideograph(c) || is_kana(c) -} - -#[inline] -fn is_cjk_break_allowed(c: char) -> bool { - // See: https://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages#Characters_not_permitted_on_the_start_of_a_line. - !")]}〕〉》」』】〙〗〟'\"⦆»ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻‐゠–〜?!‼⁇⁈⁉・、:;,。.".contains(c) -} - // ---------------------------------------------------------------------------- #[cfg(test)] @@ -1078,15 +1056,15 @@ mod tests { #[test] fn test_zero_max_width() { + let pixels_per_point = 1.0; 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()); + let galley = layout(&mut fonts, pixels_per_point, layout_job.into()); assert_eq!(galley.rows.len(), 1); } @@ -1094,8 +1072,9 @@ mod tests { fn test_truncate_with_newline() { // No matter where we wrap, we should be appending the newline character. + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new( - 1.0, 1024, AlphaFromCoverage::default(), FontDefinitions::default(), @@ -1114,7 +1093,7 @@ mod tests { layout_job.wrap.max_rows = 1; layout_job.wrap.break_anywhere = break_anywhere; - let galley = layout(&mut fonts, layout_job.into()); + let galley = layout(&mut fonts, pixels_per_point, layout_job.into()); assert!(galley.elided); assert_eq!(galley.rows.len(), 1); @@ -1133,7 +1112,7 @@ mod tests { layout_job.wrap.max_rows = 1; layout_job.wrap.break_anywhere = false; - let galley = layout(&mut fonts, layout_job.into()); + let galley = layout(&mut fonts, pixels_per_point, layout_job.into()); assert!(galley.elided); assert_eq!(galley.rows.len(), 1); @@ -1144,8 +1123,8 @@ mod tests { #[test] fn test_cjk() { + let pixels_per_point = 1.0; let mut fonts = FontsImpl::new( - 1.0, 1024, AlphaFromCoverage::default(), FontDefinitions::default(), @@ -1155,7 +1134,7 @@ mod tests { TextFormat::default(), ); layout_job.wrap.max_width = 90.0; - let galley = layout(&mut fonts, layout_job.into()); + let galley = layout(&mut fonts, pixels_per_point, layout_job.into()); assert_eq!( galley.rows.iter().map(|row| row.text()).collect::>(), vec!["日本語と", "Englishの混在", "した文章"] @@ -1164,8 +1143,8 @@ mod tests { #[test] fn test_pre_cjk() { + let pixels_per_point = 1.0; let mut fonts = FontsImpl::new( - 1.0, 1024, AlphaFromCoverage::default(), FontDefinitions::default(), @@ -1175,7 +1154,7 @@ mod tests { TextFormat::default(), ); layout_job.wrap.max_width = 110.0; - let galley = layout(&mut fonts, layout_job.into()); + let galley = layout(&mut fonts, pixels_per_point, layout_job.into()); assert_eq!( galley.rows.iter().map(|row| row.text()).collect::>(), vec!["日本語とEnglish", "の混在した文章"] @@ -1184,8 +1163,8 @@ mod tests { #[test] fn test_truncate_width() { + let pixels_per_point = 1.0; let mut fonts = FontsImpl::new( - 1.0, 1024, AlphaFromCoverage::default(), FontDefinitions::default(), @@ -1195,7 +1174,7 @@ mod tests { layout_job.wrap.max_width = f32::INFINITY; layout_job.wrap.max_rows = 1; layout_job.round_output_to_gui = false; - let galley = layout(&mut fonts, layout_job.into()); + let galley = layout(&mut fonts, pixels_per_point, layout_job.into()); assert!(galley.elided); assert_eq!( galley.rows.iter().map(|row| row.text()).collect::>(), @@ -1208,19 +1187,22 @@ mod tests { #[test] fn test_empty_row() { + let pixels_per_point = 1.0; 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 font_height = fonts + .font(&font_id.family) + .scaled_metrics(pixels_per_point, font_id.size) + .row_height; let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY); - let galley = layout(&mut fonts, job.into()); + let galley = layout(&mut fonts, pixels_per_point, job.into()); assert_eq!(galley.rows.len(), 1, "Expected one row"); assert_eq!( @@ -1242,19 +1224,22 @@ mod tests { #[test] fn test_end_with_newline() { + let pixels_per_point = 1.0; 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 font_height = fonts + .font(&font_id.family) + .scaled_metrics(pixels_per_point, font_id.size) + .row_height; let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY); - let galley = layout(&mut fonts, job.into()); + let galley = layout(&mut fonts, pixels_per_point, job.into()); assert_eq!(galley.rows.len(), 2, "Expected two rows"); assert_eq!( diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 7635dcede..e6a0c6607 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -8,14 +8,14 @@ use super::{ cursor::{CCursor, LayoutCursor}, font::UvRect, }; -use crate::{Color32, FontId, Mesh, Stroke}; +use crate::{Color32, FontId, Mesh, Stroke, text::FontsView}; use emath::{Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2, pos2, vec2}; /// Describes the task of laying out text. /// /// This supports mixing different fonts, color and formats (underline etc). /// -/// Pass this to [`crate::Fonts::layout_job`] or [`crate::text::layout`]. +/// Pass this to [`crate::FontsView::layout_job`] or [`crate::text::layout`]. /// /// ## Example: /// ``` @@ -184,7 +184,7 @@ impl LayoutJob { /// The height of the tallest font used in the job. /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - pub fn font_height(&self, fonts: &crate::Fonts) -> f32 { + pub fn font_height(&self, fonts: &mut FontsView<'_>) -> f32 { let mut max_height = 0.0_f32; for section in &self.sections { max_height = max_height.max(fonts.row_height(§ion.format.font_id)); @@ -504,7 +504,7 @@ impl TextWrapping { /// Text that has been laid out, ready for painting. /// -/// You can create a [`Galley`] using [`crate::Fonts::layout_job`]; +/// You can create a [`Galley`] using [`crate::FontsView::layout_job`]; /// /// Needs to be recreated if the underlying font atlas texture changes, which /// happens under the following conditions: diff --git a/tests/egui_tests/tests/snapshots/grow_all.png b/tests/egui_tests/tests/snapshots/grow_all.png index 7ef69ea16..e065c70f0 100644 --- a/tests/egui_tests/tests/snapshots/grow_all.png +++ b/tests/egui_tests/tests/snapshots/grow_all.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34f0c49cef96c7c3d08dbe835efd9366a4ced6ad2c6aa7facb0de08fd1a44648 -size 14011 +oid sha256:4d815e6dd4f35df988cd8f52d19e607aa51f56ba0fdb7391a1c010ea769200aa +size 14237 diff --git a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png index 9aa37308f..46492a251 100644 --- a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png +++ b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc03cdc22b410ed90fdbbc45ced5c61027463132c490673170aab9044d683f88 -size 10254 +oid sha256:880b50269a3b7a544901655f199dd2fe0bd7f004e180c6d3a1375000f6d138a5 +size 10271 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_image.png b/tests/egui_tests/tests/snapshots/layout/atoms_image.png index 759c10b7a..d15676184 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_image.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a46aebd7c611b01885819c80a4622eea44681de7f4902cf4b5debccd4b8fc000 -size 387626 +oid sha256:bedaa1bc229b0135f39ab79db1e0e62ff9c86b057aa4a2c6d1f9e6c051f12eaf +size 404014 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png index a4b0861ec..91a7d0d33 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73423d2df441a5851d7fb8100455b1126d83ada4409b191865d42609cb8badde -size 384699 +oid sha256:6028b96a4d209b654f5a376e594ac4f680bb4cd8796d96d9acafb2f8b74fff6f +size 396750 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png index 4ecf7a4e1..7bdfdc46b 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd3f36929cd6ba4caf12eee5e366387c8fc30addabc0fb0e34188aa8c0c8f5ef -size 299564 +oid sha256:963e4a024d6bea8e1554305286c59a28e8fbf09e3bbfb57cb93c85b7868b0fe9 +size 303826 diff --git a/tests/egui_tests/tests/snapshots/layout/button.png b/tests/egui_tests/tests/snapshots/layout/button.png index 51b6004b2..ef67e84e2 100644 --- a/tests/egui_tests/tests/snapshots/layout/button.png +++ b/tests/egui_tests/tests/snapshots/layout/button.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4caea23dd3b110412bb17aabfaeb99328c2f33c57426476e60ea96cedb58c1dd -size 315644 +oid sha256:455888286bea147481f797edf31e4d119118d3d38933b7e5f41a6c3d82146751 +size 323220 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png index 59c41c2fd..4e6908277 100644 --- a/tests/egui_tests/tests/snapshots/layout/button_image.png +++ b/tests/egui_tests/tests/snapshots/layout/button_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8659eefdffe496e06c4c46906f1821e8a481b2b5e58bc4a0589e26edbf97d3a9 -size 339845 +oid sha256:6811a7d4d041fdb2155d32d0e6ebfe4425a94bc5e3fff2f0380f65003487854b +size 347791 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 41bbdd066..05e9e4306 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:f4ceba068adcac969ddb20c3f249ab057e4f914a62b59fee0222b54b15ec8813 -size 424828 +oid sha256:1a481554267ad3e4c65f3552c64181d7cf0602193795ec4560e511a33586e879 +size 430746 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox.png b/tests/egui_tests/tests/snapshots/layout/checkbox.png index b11e71adc..c60b61189 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af4d7c3b64ac66df76a10283a4762382a8afd5cb0872bf28c000d771dced583d -size 386444 +oid sha256:08d455bd1b3bc259ab051ad6fcea713271a00ac11cb4a97399655457249e8abe +size 398447 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png index ba2cdfb4b..8a84f35f2 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bccccd5a51455ccb7f6963baf8c8ce36b2097458f7e85c77333736bab27288f3 -size 416329 +oid sha256:9df1bcba7179ef9a6e1a7478049918c5019b21e93f623fb01c6c5c26225632b4 +size 428446 diff --git a/tests/egui_tests/tests/snapshots/layout/drag_value.png b/tests/egui_tests/tests/snapshots/layout/drag_value.png index 3433e6bd4..495712a80 100644 --- a/tests/egui_tests/tests/snapshots/layout/drag_value.png +++ b/tests/egui_tests/tests/snapshots/layout/drag_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf56d809f8fbb63462c4112217a869820d32be906565c2122d4fd1938c2206c2 -size 239539 +oid sha256:e544781b4d83dca5d938657bf488db7df37a9bda5334b48c34cd6a15642b0ecc +size 244477 diff --git a/tests/egui_tests/tests/snapshots/layout/radio.png b/tests/egui_tests/tests/snapshots/layout/radio.png index f71421a0e..7642aa187 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio.png +++ b/tests/egui_tests/tests/snapshots/layout/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a943ef2ebd13bcf87fc7d480b0eb1e91243236eb4affa6e17a9fd2c3ce14c4d -size 336517 +oid sha256:33fe0d203e53c9adab6317f8fccfc742e68a7e12efcb972825c4ec5b3f34753d +size 339179 diff --git a/tests/egui_tests/tests/snapshots/layout/radio_checked.png b/tests/egui_tests/tests/snapshots/layout/radio_checked.png index fc2b0b36e..19faf9b40 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/radio_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:591f5001e8c5a63d063929a7bb357658a1f07b2a1be93adac3790938de1fbbc4 -size 356858 +oid sha256:523d529c9c12af56f8e1de2a325287ca7c8f97421d7fa20464e49fbfd08a17e6 +size 359389 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value.png b/tests/egui_tests/tests/snapshots/layout/selectable_value.png index e3ea1e025..f6825f605 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3018bc027223a535541fa7e87d3654ab9604c5c4ca9db5751f9ff1f8e9f5e76 -size 389206 +oid sha256:6c10baa7eb1d6e4fbf7503f1bc15de623f7bab104cb1af9f4d7912de612cb45a +size 404088 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png index baae7aafc..0b46f297d 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe816c0eff10c65f4634b0e795ac49d07ebfb064cba7199561028db8fa85c05c -size 402833 +oid sha256:b84eede8fae61c6417ecb59908efa13170cce843e5ed20f810360bd7c1acb8b4 +size 416638 diff --git a/tests/egui_tests/tests/snapshots/layout/slider.png b/tests/egui_tests/tests/snapshots/layout/slider.png index a84359b51..d1a39e2fe 100644 --- a/tests/egui_tests/tests/snapshots/layout/slider.png +++ b/tests/egui_tests/tests/snapshots/layout/slider.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce99e4a0da54c6349e270b4fdf6572db95eb997df6221cdc21f4aba8e123c7a3 -size 336775 +oid sha256:7db0cec954ff4eccd1e814107034b205ee0af6ba7d4ee523bd5f22d6631dd871 +size 341575 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit.png b/tests/egui_tests/tests/snapshots/layout/text_edit.png index 459b974e1..dfe942fcb 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7825ca0dac7c330c269f4469ecf35c02cc147ed9d966ace5576fb2f52599449c -size 233025 +oid sha256:0a2fb352590e99ea3593ad1b9be8ae4de422892567468c979dd35bd675390730 +size 239449 diff --git a/tests/egui_tests/tests/snapshots/max_width.png b/tests/egui_tests/tests/snapshots/max_width.png index 43f3a59f0..5fc8690ac 100644 --- a/tests/egui_tests/tests/snapshots/max_width.png +++ b/tests/egui_tests/tests/snapshots/max_width.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2a324562613323b6c94eaab6b7975576eb85f6d095ff818d2d36e116d370a8e -size 8752 +oid sha256:e014a3d42234c9fe2d7f78ad79cd290e53e387688347b91991a4ec557f809f82 +size 9041 diff --git a/tests/egui_tests/tests/snapshots/max_width_and_grow.png b/tests/egui_tests/tests/snapshots/max_width_and_grow.png index 8f261d32d..6f61d9c49 100644 --- a/tests/egui_tests/tests/snapshots/max_width_and_grow.png +++ b/tests/egui_tests/tests/snapshots/max_width_and_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73e61063bf68035aebc244236747dd3af13d1c80df2c4cbbdacda8f796342e62 -size 8754 +oid sha256:8301a3ea55b19269bada62cb7dd4ac296b9c2f7f3d11954f3eb9478641e3597b +size 9043 diff --git a/tests/egui_tests/tests/snapshots/shrink_first_text.png b/tests/egui_tests/tests/snapshots/shrink_first_text.png index fb4152877..36210b716 100644 --- a/tests/egui_tests/tests/snapshots/shrink_first_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_first_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bcc46a2cf63d6361b28cf7b730a3bf717dbe87065c24c3ead6fddf593ea3080 -size 11606 +oid sha256:904f5fad4c9548bdd3df1e0350066d6ceac36fd8573573f6201e326e867eb029 +size 12032 diff --git a/tests/egui_tests/tests/snapshots/shrink_last_text.png b/tests/egui_tests/tests/snapshots/shrink_last_text.png index 8cbdc8c06..e9294bacf 100644 --- a/tests/egui_tests/tests/snapshots/shrink_last_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_last_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db71fdcc89ca9f058559a2c2cd6bd1f0b7df1a0875d1d57f89a9437f14dfe649 -size 12136 +oid sha256:8d0b548a375ca69f4427ec7008d7bf3f452272d33b7d18d5b63a587f63041c61 +size 12623 diff --git a/tests/egui_tests/tests/snapshots/sides/default_long.png b/tests/egui_tests/tests/snapshots/sides/default_long.png index 26e40ed63..4c1ba903e 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_long.png +++ b/tests/egui_tests/tests/snapshots/sides/default_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4866b8bbefe79275480578f5465ec7485a642253f7ef66423e0f0e84b33a8d8 -size 7890 +oid sha256:d8b7ee31e8c1b8ec2f46cc5e9d61d7925d68a5e0bfa8a6fe1cf511111c8129e7 +size 8191 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 index 15e9dc464..cb1aa5acf 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png +++ b/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 -size 8552 +oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 +size 8971 diff --git a/tests/egui_tests/tests/snapshots/sides/default_short.png b/tests/egui_tests/tests/snapshots/sides/default_short.png index 756a3068f..8783d2a44 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_short.png +++ b/tests/egui_tests/tests/snapshots/sides/default_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f -size 1647 +oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 +size 1718 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 index 6f3189261..e137eb319 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png +++ b/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f -size 1276 +oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 +size 1318 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png index 39e1bab98..ec941bf8f 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88e1557dffa7295e7e7e37ed175fcec40aab939f9b67137a1ce33811e8ae4722 -size 7148 +oid sha256:1ee1cb1948c5d070c79248fe12727626872ff57ff3ce027d957f33eeb1d309ac +size 7450 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 index 15e9dc464..cb1aa5acf 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 -size 8552 +oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 +size 8971 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png index 756a3068f..8783d2a44 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f -size 1647 +oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 +size 1718 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 index 6f3189261..e137eb319 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f -size 1276 +oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 +size 1318 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png index 3326d9527..435225195 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:508209ca303751ef323301b25bb3878410742ea79339b75363d2681b98d2712b -size 7068 +oid sha256:b1f7df988359773b96ac7645eb919260076b090eb980fe900a46ad765c69bc4f +size 7353 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 index 15e9dc464..cb1aa5acf 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 -size 8552 +oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 +size 8971 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png index 756a3068f..8783d2a44 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f -size 1647 +oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 +size 1718 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 index 6f3189261..e137eb319 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f -size 1276 +oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 +size 1318 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png index 36929a413..f6cfcde28 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0c9e39c18fc5bb1fc02a86dbf02e3ffca5537dbe8986d5c5b50cb4984c97466 -size 9085 +oid sha256:510dde01a46ce7fdf1142bc88e7873ba83c5d52a6a93a6f375ff565c1ad5d11f +size 9468 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 index 15e9dc464..cb1aa5acf 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 -size 8552 +oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 +size 8971 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png index 756a3068f..8783d2a44 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f -size 1647 +oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 +size 1718 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 index 6f3189261..e137eb319 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f -size 1276 +oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 +size 1318 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png index 47398293f..76de1f273 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6e9ba0acb573853ef5b3dedb1156d99cdf80338ccb160093960e8aaa41bd5df -size 9048 +oid sha256:25c0c51f39b515609a51a07514606c474c4bd8516e5597569b58a31dfedfb790 +size 9448 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 index 15e9dc464..cb1aa5acf 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:931af33f4548924b3bb75a2e513b9e689bce94436b10a9f811140eb11e9d6442 -size 8552 +oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 +size 8971 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png index 756a3068f..8783d2a44 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6448d44c1c9bed08cd6a4af39b141f3a4ca203ca5f7b967cbc0e0d0c2b4fb73f -size 1647 +oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 +size 1718 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 index 6f3189261..e137eb319 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44622002ebe287208d26359a06804b1f8737f86eb482322bdf3881a1fe53941f -size 1276 +oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 +size 1318 diff --git a/tests/egui_tests/tests/snapshots/size_max_size.png b/tests/egui_tests/tests/snapshots/size_max_size.png index 742ae57f7..4a221a5b8 100644 --- a/tests/egui_tests/tests/snapshots/size_max_size.png +++ b/tests/egui_tests/tests/snapshots/size_max_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:161522c5bc65a03f3a12615729788b0a13b24a8b0d4d42de7f2c8a8f0ff46687 -size 8727 +oid sha256:08c69a55622468c906581534fe874f1b51515922ec4e92b2d219feec56dbf941 +size 8992 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png index 364f7771f..b874abe60 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:a573976aacbb629c88c285089fca28ba7998a0c28ecee9f783920d67929a1e2d -size 9735 +oid sha256:db667120ae1f7909c8f1a9f956ec435d415ea5759c5ea40b8af4bc37cd477da1 +size 10004 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png index c38571a6e..1a2870238 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:9fbb9aca2006aeca555c138f1ebdb89409026f1bed48da74cd0fa03dcd8facbe -size 10746 +oid sha256:24105bf3867e453ebe6457d30ba3c6e7356f6522137bab46bca01b28ac19c372 +size 11205 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 f633bd599..5cc894260 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:7390b7a44bd0132d61f71598c3c8f13f0feba4f2e28b019dce9ab74ae17d9e14 -size 13783 +oid sha256:773f4bb9e40340bd07e18ff98f95a34c5ddb3daae797625383c79bc54d406837 +size 13897 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 bcf0f01c7..c750d4d61 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:e3f62e86ef77c26dd2d54c5f94221506d8ad1517eeb26c107c6c321f9e94af6c -size 13474 +oid sha256:6ad507f427d9cf10778aa9289f499a7364f08c55c8d8d8fec565d9d0363467ee +size 13553 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png index 00f4bd8b3..116086858 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:befff30cab57644f4e19e90d5d8a7d59093d57b8af3865c94ecffcb7ece9f2fb -size 12327 +oid sha256:4bdfc4468b385f3b83ed7bfa72f0cd7cf07e6d6a60c07db876c9c55c009fc721 +size 12723 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index f1b144d2e..4ebe6b427 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:a5dffe081a596a17928e1fd53df04c4eac69c0bcebc75ecec7fbc9133a5380b0 -size 13461 +oid sha256:289f3202b84d5e17cdda65edd8ed9c4201278e1e9a8422eab1579972e2d8baf2 +size 13807 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png index 56b5bb0e3..be8981733 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:c7e66a490236b306ce03c504d29490cdadc3708a79e21e3b46d11df8eb22a26b -size 7309 +oid sha256:100ae526549f0d85466fa41c3f8cb60dc0d2501729444fff7ed566b09fa9d457 +size 7493 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png index 8a364b32f..0af777736 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:297dda97ae7cc4415c6679ad914a5fbee96cad527e98f2afc797b665d9b7f48f -size 11070 +oid sha256:9f78fc2de7c20d08c98909a5f53517e91d0ac46f98c0c76ff350d858e2867907 +size 11170 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index 32e8a3944..1efaa705e 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:0bb35eac75aed7f0fcef4d467d894e4af459f0f3efe43c265384e25f31cde4c4 -size 11744 +oid sha256:1ae4d4a7d079b2557f1b7b505d91cc7438f4052db6f6a149990bd11754b1ca49 +size 11845 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png index 49ddf6077..cdec6780a 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96057a478dd4242aa74fea9d11ff4f5181a0c414291dbabb536076af85b83542 -size 12453 +oid sha256:52babe11fc4af5eb9d9137107594a59b7987cc9d3116e444cc8d739408f35cba +size 12871 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 c9d3e32d2..7405322ad 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:b02284574a8b5959d473cdc786ac4b323e0d7c0ebeca2933688cf51ef62a391d -size 12513 +oid sha256:130704c3320a88309e7ad7a3ca441741727fe891c080e0593377c083f5b548c8 +size 13104 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index 1129add08..8ad98c155 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:c4f3597075a77afbde2ddb7513d3309eb60f78cea6e5d4c108bbc5faed3ec937 -size 11035 +oid sha256:af0764e450a544af4eab1c9c123fe4c46f313b84f070c0e0b756c273db2d2fed +size 11204 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png index 1e1e4d394..bf87660fb 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:9353e6d39d309e7a6e6c0a17be819809c2dbea8979e9e73b3c73b67b07124a36 -size 7031 +oid sha256:078a3d58c6e2cc2e9189d257b09f3c2c19721c09666bfe48103884005fb4eb6c +size 7228 From 9cc7f2ec16ccbb3359e6cf3e1d86e55cb7fa1845 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 8 Sep 2025 17:37:49 +0200 Subject: [PATCH 210/388] Improve deadlock detection output (#7515) --- .../egui_extras/src/loaders/ehttp_loader.rs | 1 - crates/egui_extras/src/loaders/file_loader.rs | 1 - crates/epaint/src/mutex.rs | 27 ++++++++++++------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/egui_extras/src/loaders/ehttp_loader.rs b/crates/egui_extras/src/loaders/ehttp_loader.rs index 1d3e20c90..d1cc68df9 100644 --- a/crates/egui_extras/src/loaders/ehttp_loader.rs +++ b/crates/egui_extras/src/loaders/ehttp_loader.rs @@ -101,7 +101,6 @@ impl BytesLoader for EhttpLoader { { let entry = entry.get_mut(); *entry = Poll::Ready(result); - ctx.request_repaint(); log::trace!("Finished loading {uri:?}"); true } else { diff --git a/crates/egui_extras/src/loaders/file_loader.rs b/crates/egui_extras/src/loaders/file_loader.rs index 5e66b76da..13fd7ca69 100644 --- a/crates/egui_extras/src/loaders/file_loader.rs +++ b/crates/egui_extras/src/loaders/file_loader.rs @@ -100,7 +100,6 @@ impl BytesLoader for FileLoader { 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:?}"); true } else { diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 4b6a66f94..2f264f58c 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -31,9 +31,12 @@ impl Mutex { #[cfg_attr(debug_assertions, track_caller)] pub fn lock(&self) -> MutexGuard<'_, T> { if cfg!(debug_assertions) { - self.0 - .try_lock_for(DEADLOCK_DURATION) - .expect("Looks like a deadlock!") + self.0.try_lock_for(DEADLOCK_DURATION).unwrap_or_else(|| { + panic!( + "DEBUG PANIC: Failed to acquire Mutex after {}s. Deadlock?", + DEADLOCK_DURATION.as_secs() + ) + }) } else { self.0.lock() } @@ -74,9 +77,12 @@ impl RwLock { #[cfg_attr(debug_assertions, track_caller)] pub fn read(&self) -> RwLockReadGuard<'_, T> { let guard = if cfg!(debug_assertions) { - self.0 - .try_read_for(DEADLOCK_DURATION) - .expect("Looks like a deadlock!") + self.0.try_read_for(DEADLOCK_DURATION).unwrap_or_else(|| { + panic!( + "DEBUG PANIC: Failed to acquire RwLock read after {}s. Deadlock?", + DEADLOCK_DURATION.as_secs() + ) + }) } else { self.0.read() }; @@ -91,9 +97,12 @@ impl RwLock { #[cfg_attr(debug_assertions, track_caller)] pub fn write(&self) -> RwLockWriteGuard<'_, T> { let guard = if cfg!(debug_assertions) { - self.0 - .try_write_for(DEADLOCK_DURATION) - .expect("Looks like a deadlock!") + self.0.try_write_for(DEADLOCK_DURATION).unwrap_or_else(|| { + panic!( + "DEBUG PANIC: Failed to acquire RwLock write after {}s. Deadlock?", + DEADLOCK_DURATION.as_secs() + ) + }) } else { self.0.write() }; From a8e36e33130a3d9631a68d4672d86ac440950f17 Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:39:52 -0400 Subject: [PATCH 211/388] Improve `OrderedFloat` hash performance (#7512) Co-authored-by: Lucas Meurer --- crates/emath/src/ordered_float.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/emath/src/ordered_float.rs b/crates/emath/src/ordered_float.rs index dd58a22a2..369541fed 100644 --- a/crates/emath/src/ordered_float.rs +++ b/crates/emath/src/ordered_float.rs @@ -124,13 +124,15 @@ mod private { #[inline] fn hash(&self, state: &mut H) { - if *self == 0.0 { - state.write_u8(0); - } else if self.is_nan() { - state.write_u8(1); + let bits = if self.is_nan() { + // "Canonical" NaN. + 0x7fc00000 } else { - self.to_bits().hash(state); - } + // A trick taken from the `ordered-float` crate: -0.0 + 0.0 == +0.0. + // https://github.com/reem/rust-ordered-float/blob/1841f0541ea0e56779cbac03de2705149e020675/src/lib.rs#L2178-L2181 + (self + 0.0).to_bits() + }; + bits.hash(state); } } @@ -142,13 +144,13 @@ mod private { #[inline] fn hash(&self, state: &mut H) { - if *self == 0.0 { - state.write_u8(0); - } else if self.is_nan() { - state.write_u8(1); + let bits = if self.is_nan() { + // "Canonical" NaN. + 0x7ff8000000000000 } else { - self.to_bits().hash(state); - } + (self + 0.0).to_bits() + }; + bits.hash(state); } } } From 742b1dc92007a0d5dd22d2cd9736ced04c0370c2 Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:40:45 -0400 Subject: [PATCH 212/388] Optimize `Mesh::add_rect_with_uv` (#7511) --- crates/epaint/src/mesh.rs | 52 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index fbff16ba2..8a0a39495 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -169,9 +169,7 @@ impl Mesh { /// Add a triangle. #[inline(always)] pub fn add_triangle(&mut self, a: u32, b: u32, c: u32) { - self.indices.push(a); - self.indices.push(b); - self.indices.push(c); + self.indices.extend_from_slice(&[a, b, c]); } /// Make room for this many additional triangles (will reserve 3x as many indices). @@ -189,33 +187,35 @@ impl Mesh { } /// Rectangle with a texture and color. + #[inline(always)] pub fn add_rect_with_uv(&mut self, rect: Rect, uv: Rect, color: Color32) { #![allow(clippy::identity_op)] - let idx = self.vertices.len() as u32; - self.add_triangle(idx + 0, idx + 1, idx + 2); - self.add_triangle(idx + 2, idx + 1, idx + 3); + self.indices + .extend_from_slice(&[idx + 0, idx + 1, idx + 2, idx + 2, idx + 1, idx + 3]); - self.vertices.push(Vertex { - pos: rect.left_top(), - uv: uv.left_top(), - color, - }); - self.vertices.push(Vertex { - pos: rect.right_top(), - uv: uv.right_top(), - color, - }); - self.vertices.push(Vertex { - pos: rect.left_bottom(), - uv: uv.left_bottom(), - color, - }); - self.vertices.push(Vertex { - pos: rect.right_bottom(), - uv: uv.right_bottom(), - color, - }); + self.vertices.extend_from_slice(&[ + Vertex { + pos: rect.left_top(), + uv: uv.left_top(), + color, + }, + Vertex { + pos: rect.right_top(), + uv: uv.right_top(), + color, + }, + Vertex { + pos: rect.left_bottom(), + uv: uv.left_bottom(), + color, + }, + Vertex { + pos: rect.right_bottom(), + uv: uv.right_bottom(), + color, + }, + ]); } /// Uniformly colored rectangle. From b822977e7fa875cde09eccb38a6c15f2b64fcf69 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 8 Sep 2025 18:17:55 +0200 Subject: [PATCH 213/388] Remove `preload_font_glyphs` flag (#7516) --- crates/egui/src/context.rs | 12 ------------ crates/egui/src/memory/mod.rs | 10 ---------- crates/epaint/src/text/font.rs | 11 ----------- 3 files changed, 33 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 4360f66ce..20c9ab25e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -625,18 +625,6 @@ impl ContextImpl { profiling::scope!("Fonts::begin_pass"); fonts.begin_pass(max_texture_side, text_alpha_from_coverage); } - - if is_new && self.memory.options.preload_font_glyphs { - profiling::scope!("preload_font_glyphs"); - // Preload the most common characters for the most common fonts. - // This is not very important to do, but may save a few GPU operations. - for font_id in self.memory.options.style().text_styles.values() { - fonts - .fonts - .font(&font_id.family) - .preload_common_characters(); - } - } } #[cfg(feature = "accesskit")] diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 1e8cc4c92..904d24c01 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -272,14 +272,6 @@ pub struct Options { /// which is supported by `eframe`. pub screen_reader: bool, - /// If true, the most common glyphs (ASCII) are pre-rendered to the texture atlas. - /// - /// Only the fonts in [`Style::text_styles`] will be pre-cached. - /// - /// This can lead to fewer texture operations, but may use up the texture atlas quicker - /// if you are changing [`Style::text_styles`], or have a lot of text styles. - pub preload_font_glyphs: bool, - /// Check reusing of [`Id`]s, and show a visual warning on screen when one is found. /// /// By default this is `true` in debug builds. @@ -316,7 +308,6 @@ impl Default for Options { repaint_on_widget_change: false, max_passes: NonZeroUsize::new(2).unwrap(), screen_reader: false, - preload_font_glyphs: true, warn_on_id_clash: cfg!(debug_assertions), // Input: @@ -372,7 +363,6 @@ impl Options { repaint_on_widget_change, max_passes, screen_reader: _, // needs to come from the integration - preload_font_glyphs: _, warn_on_id_clash, input_options, reduce_texture_memory, diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index f3d098994..372389188 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -452,17 +452,6 @@ impl Font<'_> { } } - pub fn preload_common_characters(&mut self) { - // Preload the printable ASCII characters [32, 126] (which excludes control codes): - const FIRST_ASCII: usize = 32; // 32 == space - const LAST_ASCII: usize = 126; - for c in (FIRST_ASCII..=LAST_ASCII).map(|c| c as u8 as char) { - self.glyph_info(c); - } - self.glyph_info('°'); - self.glyph_info(crate::text::PASSWORD_REPLACEMENT_CHAR); - } - /// All supported characters, and in which font they are available in. pub fn characters(&mut self) -> &BTreeMap> { self.cached_family.characters.get_or_insert_with(|| { From 01ee23c1a0ec4d8ac375f94a078391fca93f3461 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 8 Sep 2025 18:27:28 +0200 Subject: [PATCH 214/388] Add Zoom Factor to options panel (#7517) --- crates/egui/src/data/output.rs | 9 ++------- crates/egui/src/memory/mod.rs | 7 ++++++- crates/egui_demo_app/src/wrap_app.rs | 9 ++------- crates/egui_demo_lib/src/demo/scrolling.rs | 9 ++------- .../egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs | 2 +- crates/epaint/src/stats.rs | 9 ++------- 6 files changed, 15 insertions(+), 30 deletions(-) diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 0a6589655..58153c8f7 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -294,10 +294,11 @@ pub enum UserAttentionType { /// egui emits a [`CursorIcon`] in [`PlatformOutput`] each frame as a request to the integration. /// /// Loosely based on . -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum CursorIcon { /// Normal cursor icon, whatever that is. + #[default] Default, /// Show no cursor @@ -457,12 +458,6 @@ impl CursorIcon { ]; } -impl Default for CursorIcon { - fn default() -> Self { - Self::Default - } -} - /// Things that happened during this frame that the integration may be interested in. /// /// In particular, these events may be useful for accessibility, i.e. for screen readers. diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 904d24c01..eaf543528 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -357,7 +357,7 @@ impl Options { theme_preference, fallback_theme: _, system_theme: _, - zoom_factor: _, // TODO(emilk) + zoom_factor, zoom_with_keyboard, tessellation_options, repaint_on_widget_change, @@ -384,6 +384,11 @@ impl Options { "Repaint if any widget moves or changes id", ); + ui.horizontal(|ui| { + ui.label("Zoom factor:"); + ui.add(crate::DragValue::new(zoom_factor).range(0.10..=10.0)); + }); + ui.checkbox( zoom_with_keyboard, "Zoom with keyboard (Cmd +, Cmd -, Cmd 0)", diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 3775d93d2..eb901010a 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -80,9 +80,10 @@ impl eframe::App for ColorTestApp { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum Anchor { + #[default] Demo, EasyMarkEditor, @@ -138,12 +139,6 @@ impl From for egui::WidgetText { } } -impl Default for Anchor { - fn default() -> Self { - Self::Demo - } -} - // ---------------------------------------------------------------------------- #[derive(Clone, Copy, Debug)] diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 63fc81dbb..8c1250a9c 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -4,8 +4,9 @@ use egui::{ }; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] enum ScrollDemo { + #[default] ScrollAppearance, ScrollTo, ManyLines, @@ -14,12 +15,6 @@ enum ScrollDemo { Bidirectional, } -impl Default for ScrollDemo { - fn default() -> Self { - Self::ScrollAppearance - } -} - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] #[derive(Default, PartialEq)] diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs index 5c6bc2ef0..8ed3eb387 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs @@ -108,7 +108,7 @@ pub fn highlight_easymark(egui_style: &egui::Style, mut text: &str) -> egui::tex // Swallow everything up to the next special character: let line_end = text[skip..] .find('\n') - .map_or_else(|| text.len(), |i| (skip + i + 1)); + .map_or_else(|| text.len(), |i| skip + i + 1); let end = text[skip..] .find(&['*', '`', '~', '_', '/', '$', '^', '\\', '<', '['][..]) .map_or_else(|| text.len(), |i| (skip + i).max(1)); diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 2acf1e93c..1eef4f444 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -3,19 +3,14 @@ use crate::{ClippedShape, Galley, Mesh, Primitive, Shape}; /// Size of the elements in a vector/array. -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Default, PartialEq)] enum ElementSize { + #[default] Unknown, Homogeneous(usize), Heterogenous, } -impl Default for ElementSize { - fn default() -> Self { - Self::Unknown - } -} - /// Aggregate information about a bunch of allocations. #[derive(Clone, Copy, Default, PartialEq)] pub struct AllocInfo { From c2912369ca02e05a773b740a22245f390e743bea Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 9 Sep 2025 09:35:25 +0200 Subject: [PATCH 215/388] Improve text demo: more fine control of letter spacing (#7520) --- crates/egui_demo_lib/src/demo/text_layout.rs | 11 +++++------ .../tests/snapshots/demos/Text Layout.png | 4 ++-- crates/epaint/src/text/text_layout_types.rs | 2 -- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/text_layout.rs b/crates/egui_demo_lib/src/demo/text_layout.rs index e4e7ddcc3..8f100b55d 100644 --- a/crates/egui_demo_lib/src/demo/text_layout.rs +++ b/crates/egui_demo_lib/src/demo/text_layout.rs @@ -5,7 +5,7 @@ pub struct TextLayoutDemo { break_anywhere: bool, max_rows: usize, overflow_character: Option, - extra_letter_spacing_pixels: i32, + extra_letter_spacing: f32, line_height_pixels: u32, lorem_ipsum: bool, } @@ -16,7 +16,7 @@ impl Default for TextLayoutDemo { max_rows: 6, break_anywhere: true, overflow_character: Some('…'), - extra_letter_spacing_pixels: 0, + extra_letter_spacing: 0.0, line_height_pixels: 0, lorem_ipsum: true, } @@ -45,7 +45,7 @@ impl crate::View for TextLayoutDemo { break_anywhere, max_rows, overflow_character, - extra_letter_spacing_pixels, + extra_letter_spacing, line_height_pixels, lorem_ipsum, } = self; @@ -85,7 +85,7 @@ impl crate::View for TextLayoutDemo { ui.end_row(); ui.label("Extra letter spacing:"); - ui.add(egui::DragValue::new(extra_letter_spacing_pixels).suffix(" pixels")); + ui.add(egui::DragValue::new(extra_letter_spacing).speed(0.1)); ui.end_row(); ui.label("Line height:"); @@ -126,14 +126,13 @@ impl crate::View for TextLayoutDemo { egui::ScrollArea::vertical() .auto_shrink(false) .show(ui, |ui| { - let extra_letter_spacing = points_per_pixel * *extra_letter_spacing_pixels as f32; let line_height = (*line_height_pixels != 0) .then_some(points_per_pixel * *line_height_pixels as f32); let mut job = LayoutJob::single_section( text.to_owned(), egui::TextFormat { - extra_letter_spacing, + extra_letter_spacing: *extra_letter_spacing, line_height, ..Default::default() }, diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index a0dd98a53..90875e3e5 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:beeb28e1e1d25281ad69736be3aae98ae6ca20c16873be5596f2e608894536b8 -size 66019 +oid sha256:ff51d191c5883bf68c0961aef70842543285e6513598dc57893edf5b4ccc5856 +size 65490 diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index e6a0c6607..d1656efb2 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -269,8 +269,6 @@ pub struct TextFormat { /// Extra spacing between letters, in points. /// /// Default: 0.0. - /// - /// For even text it is recommended you round this to an even number of _pixels_. pub extra_letter_spacing: f32, /// Explicit line height of the text in points. From 835e4f17dfc628fa90ea7c018778162cb7d2df11 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 9 Sep 2025 11:17:07 +0200 Subject: [PATCH 216/388] feat: Add serde serialization to SyntectSettings (#7506) --- crates/egui_extras/src/syntax_highlighting.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index c08c69750..2a5fd39fc 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -470,6 +470,7 @@ impl CodeTheme { // ---------------------------------------------------------------------------- #[cfg(feature = "syntect")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SyntectSettings { pub ps: syntect::parsing::SyntaxSet, pub ts: syntect::highlighting::ThemeSet, From 75b50c90e85f18ba5a324af81ac129fc46450618 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 9 Sep 2025 11:18:00 +0200 Subject: [PATCH 217/388] Remove deprecated `Harness::wgpu_snapshot` and related fns (#7504) --- crates/egui_kittest/src/snapshot.rs | 42 ----------------------------- 1 file changed, 42 deletions(-) diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index d6c141635..bc58e7b4c 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -636,48 +636,6 @@ impl Harness<'_, State> { } } -// Deprecated wgpu_snapshot functions -// TODO(lucasmerlin): Remove in 0.32 -#[expect(clippy::missing_errors_doc)] -#[cfg(feature = "wgpu")] -impl Harness<'_, State> { - #[deprecated( - since = "0.31.0", - note = "Use `try_snapshot_options` instead. This function will be removed in 0.32" - )] - pub fn try_wgpu_snapshot_options( - &mut self, - name: impl Into, - options: &SnapshotOptions, - ) -> SnapshotResult { - self.try_snapshot_options(name, options) - } - - #[deprecated( - 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: impl Into) -> SnapshotResult { - self.try_snapshot(name) - } - - #[deprecated( - 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: impl Into, options: &SnapshotOptions) { - self.snapshot_options(name, options); - } - - #[deprecated( - since = "0.31.0", - note = "Use `snapshot` instead. This function will be removed in 0.32" - )] - pub fn wgpu_snapshot(&mut self, name: &str) { - self.snapshot(name); - } -} - /// Utility to collect snapshot errors and display them at the end of the test. /// /// # Example From 2afc43c0ecc1db34f6038525b6135c94cf7d9355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Pakalns?= Date: Tue, 9 Sep 2025 12:21:54 +0300 Subject: [PATCH 218/388] Support on hover tooltip that is noninteractable even with interactable content (#5543) --- crates/egui/src/containers/tooltip.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/containers/tooltip.rs b/crates/egui/src/containers/tooltip.rs index 99bc95d5c..682b11fd8 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -72,7 +72,7 @@ impl Tooltip<'_> { let mut tooltip = Self::for_widget(response); tooltip.popup = tooltip .popup - .open(response.enabled() && Self::should_show_tooltip(response)); + .open(response.enabled() && Self::should_show_tooltip(response, true)); tooltip } @@ -81,7 +81,7 @@ impl Tooltip<'_> { let mut tooltip = Self::for_widget(response); tooltip.popup = tooltip .popup - .open(!response.enabled() && Self::should_show_tooltip(response)); + .open(!response.enabled() && Self::should_show_tooltip(response, true)); tooltip } @@ -211,7 +211,10 @@ impl Tooltip<'_> { } /// Should we show a tooltip for this response? - pub fn should_show_tooltip(response: &Response) -> bool { + /// + /// Argument `allow_interactive_tooltip` controls whether mouse can interact with tooltip that + /// contains interactive widgets + pub fn should_show_tooltip(response: &Response, allow_interactive_tooltip: bool) -> bool { if response.ctx.memory(|mem| mem.everything_is_visible()) { return true; } @@ -264,12 +267,13 @@ impl Tooltip<'_> { let tooltip_id = Self::next_tooltip_id(&response.ctx, response.id); let tooltip_layer_id = LayerId::new(Order::Tooltip, tooltip_id); - let tooltip_has_interactive_widget = response.ctx.viewport(|vp| { - vp.prev_pass - .widgets - .get_layer(tooltip_layer_id) - .any(|w| w.enabled && w.sense.interactive()) - }); + let tooltip_has_interactive_widget = allow_interactive_tooltip + && response.ctx.viewport(|vp| { + vp.prev_pass + .widgets + .get_layer(tooltip_layer_id) + .any(|w| w.enabled && w.sense.interactive()) + }); if tooltip_has_interactive_widget { // We keep the tooltip open if hovered, From ea76b4eeca061085a98184442170f30019e8e8c0 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 9 Sep 2025 13:44:59 +0200 Subject: [PATCH 219/388] Add `egui_kittest::HarnessBuilder::with_os` and set the default to `Nix` (#7493) Co-authored-by: Emil Ernerfeldt --- crates/egui_kittest/src/builder.rs | 17 +++++++++++++++++ crates/egui_kittest/src/lib.rs | 2 ++ 2 files changed, 19 insertions(+) diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index 021019a89..75ebd54b2 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -8,6 +8,7 @@ pub struct HarnessBuilder { pub(crate) screen_rect: Rect, pub(crate) pixels_per_point: f32, pub(crate) theme: egui::Theme, + pub(crate) os: egui::os::OperatingSystem, pub(crate) max_steps: u64, pub(crate) step_dt: f32, pub(crate) state: PhantomData, @@ -26,6 +27,7 @@ impl Default for HarnessBuilder { max_steps: 4, step_dt: 1.0 / 4.0, wait_for_pending_images: true, + os: egui::os::OperatingSystem::Nix, } } } @@ -54,6 +56,21 @@ impl HarnessBuilder { self } + /// Override the [`egui::os::OperatingSystem`] reported to egui. + /// + /// This affects e.g. the way shortcuts are displayed. So for snapshot tests, + /// it makes sense to set this to a specific OS, so snapshots don't change when running + /// the same tests on different OSes. + /// + /// Default is [`egui::os::OperatingSystem::Nix`]. + /// Use [`egui::os::OperatingSystem::from_target_os()`] to use the current OS (this restores + /// eguis default behavior). + #[inline] + pub fn with_os(mut self, os: egui::os::OperatingSystem) -> Self { + self.os = os; + 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 ae4d42f4b..0f99da170 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -93,6 +93,7 @@ impl<'a, State> Harness<'a, State> { screen_rect, pixels_per_point, theme, + os, max_steps, step_dt, state: _, @@ -101,6 +102,7 @@ impl<'a, State> Harness<'a, State> { } = builder; let ctx = ctx.unwrap_or_default(); ctx.set_theme(theme); + ctx.set_os(os); ctx.enable_accesskit(); ctx.all_styles_mut(|style| { // Disable cursor blinking so it doesn't interfere with snapshots From 9db03983dda344bea51dfdc698c6a6fdd3637f6c Mon Sep 17 00:00:00 2001 From: Zakarum Date: Tue, 9 Sep 2025 13:45:44 +0200 Subject: [PATCH 220/388] Fix `TextEdit`'s in RTL layouts (#5547) Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widgets/text_edit/builder.rs | 30 +++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 20cd079b0..1b585dab4 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -731,10 +731,32 @@ impl TextEdit<'_> { // Condition `!clip_text` is important to avoid breaking layout for `TextEdit::singleline` (PR #5640) let extra_size = galley.size() - rect.size(); if extra_size.x > 0.0 || extra_size.y > 0.0 { - ui.allocate_rect( - Rect::from_min_size(outer_rect.max, extra_size), - Sense::hover(), - ); + match ui.layout().main_dir() { + crate::Direction::LeftToRight | crate::Direction::TopDown => { + ui.allocate_rect( + Rect::from_min_size(outer_rect.max, extra_size), + Sense::hover(), + ); + } + crate::Direction::RightToLeft => { + ui.allocate_rect( + Rect::from_min_size( + emath::pos2(outer_rect.min.x - extra_size.x, outer_rect.max.y), + extra_size, + ), + Sense::hover(), + ); + } + crate::Direction::BottomUp => { + ui.allocate_rect( + Rect::from_min_size( + emath::pos2(outer_rect.min.x, outer_rect.max.y - extra_size.y), + extra_size, + ), + Sense::hover(), + ); + } + } } } From ec5bc35c38de4659f999bd859922df9350f336d7 Mon Sep 17 00:00:00 2001 From: Sven Niederberger <73159570+s-nie@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:49:35 +0200 Subject: [PATCH 221/388] Add an option to limit the repaint rate in the web runner (#7482) Co-authored-by: Lucas Meurer Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/epi.rs | 6 ++++ crates/eframe/src/web/app_runner.rs | 3 +- crates/eframe/src/web/backend.rs | 47 +++++++++++++++++++++++------ crates/eframe/src/web/events.rs | 6 ++-- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 4b5e69d11..5502b6341 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -509,6 +509,10 @@ pub struct WebOptions { /// /// Defaults to true. pub should_prevent_default: Box bool>, + + /// Maximum rate at which to repaint. This can be used to artificially reduce the repaint rate below + /// vsync in order to save resources. + pub max_fps: Option, } #[cfg(target_arch = "wasm32")] @@ -527,6 +531,8 @@ impl Default for WebOptions { should_stop_propagation: Box::new(|_| true), should_prevent_default: Box::new(|_| true), + + max_fps: None, } } } diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 9f2d2beba..9421f9b44 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -96,7 +96,8 @@ impl AppRunner { wgpu_render_state: None, }; - let needs_repaint: std::sync::Arc = Default::default(); + let needs_repaint: std::sync::Arc = + std::sync::Arc::new(NeedRepaint::new(web_options.max_fps)); { let needs_repaint = needs_repaint.clone(); egui_ctx.set_request_repaint_callback(move |info| { diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index 5877f424c..e81e5487d 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -50,11 +50,20 @@ impl WebInput { // ---------------------------------------------------------------------------- /// Stores when to do the next repaint. -pub(crate) struct NeedRepaint(Mutex); +pub(crate) struct NeedRepaint { + /// Time in seconds when the next repaint should happen. + next_repaint: Mutex, -impl Default for NeedRepaint { - fn default() -> Self { - Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint + /// Rate limit for repaint. 0 means "unlimited". The rate may still be limited by vsync. + max_fps: u32, +} + +impl NeedRepaint { + pub fn new(max_fps: Option) -> Self { + Self { + next_repaint: Mutex::new(f64::NEG_INFINITY), // start with a repaint + max_fps: max_fps.unwrap_or(0), + } } } @@ -62,25 +71,43 @@ impl NeedRepaint { /// Returns the time (in [`now_sec`] scale) when /// we should next repaint. pub fn when_to_repaint(&self) -> f64 { - *self.0.lock() + *self.next_repaint.lock() } /// Unschedule repainting. pub fn clear(&self) { - *self.0.lock() = f64::INFINITY; + *self.next_repaint.lock() = f64::INFINITY; } pub fn repaint_after(&self, num_seconds: f64) { - let mut repaint_time = self.0.lock(); - *repaint_time = repaint_time.min(super::now_sec() + num_seconds); + let mut time = super::now_sec() + num_seconds; + time = self.round_repaint_time_to_rate(time); + let mut repaint_time = self.next_repaint.lock(); + *repaint_time = repaint_time.min(time); + } + + /// Request a repaint. Depending on the presence of rate limiting, this may not be instant. + pub fn repaint(&self) { + let time = self.round_repaint_time_to_rate(super::now_sec()); + let mut repaint_time = self.next_repaint.lock(); + *repaint_time = repaint_time.min(time); + } + + pub fn repaint_asap(&self) { + *self.next_repaint.lock() = f64::NEG_INFINITY; } pub fn needs_repaint(&self) -> bool { self.when_to_repaint() <= super::now_sec() } - pub fn repaint_asap(&self) { - *self.0.lock() = f64::NEG_INFINITY; + fn round_repaint_time_to_rate(&self, time: f64) -> f64 { + if self.max_fps == 0 { + time + } else { + let interval = 1.0 / self.max_fps as f64; + (time / interval).ceil() * interval + } } } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 1a79937b0..a5bb4c0eb 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -638,7 +638,7 @@ fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); let should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); - runner.needs_repaint.repaint_asap(); + runner.needs_repaint.repaint(); // Use web options to tell if the web event should be propagated to parent elements based on the egui event. if should_stop_propagation { @@ -721,7 +721,7 @@ fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), runner.input.raw.events.push(egui_event); push_touches(runner, egui::TouchPhase::Move, &event); - runner.needs_repaint.repaint_asap(); + runner.needs_repaint.repaint(); // Use web options to tell if the web event should be propagated to parent elements based on the egui event. if should_stop_propagation { @@ -834,7 +834,7 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV let should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event); runner.input.raw.events.push(egui_event); - runner.needs_repaint.repaint_asap(); + runner.needs_repaint.repaint(); // Use web options to tell if the web event should be propagated to parent elements based on the egui event. if should_stop_propagation { From 72b9b9d750f74063dc3dec2e15681e73fdb5fd51 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 9 Sep 2025 15:47:24 +0200 Subject: [PATCH 222/388] Increase default text size from 12.5 to 13.0 (#7521) --- crates/egui/src/style.rs | 10 +++++----- crates/egui_demo_app/tests/snapshots/clock.png | 4 ++-- crates/egui_demo_app/tests/snapshots/custom3d.png | 4 ++-- .../egui_demo_app/tests/snapshots/easymarkeditor.png | 4 ++-- crates/egui_demo_app/tests/snapshots/imageviewer.png | 4 ++-- .../tests/snapshots/demos/Bézier Curve.png | 4 ++-- .../tests/snapshots/demos/Clipboard Test.png | 4 ++-- .../tests/snapshots/demos/Code Editor.png | 4 ++-- .../tests/snapshots/demos/Code Example.png | 4 ++-- .../tests/snapshots/demos/Cursor Test.png | 4 ++-- .../tests/snapshots/demos/Dancing Strings.png | 4 ++-- .../tests/snapshots/demos/Drag and Drop.png | 4 ++-- .../tests/snapshots/demos/Extra Viewport.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Font Book.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/Frame.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Grid Test.png | 4 ++-- .../tests/snapshots/demos/Highlighting.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/ID Test.png | 4 ++-- .../tests/snapshots/demos/Input Event History.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Input Test.png | 4 ++-- .../tests/snapshots/demos/Interactive Container.png | 4 ++-- .../tests/snapshots/demos/Layout Test.png | 4 ++-- .../tests/snapshots/demos/Manual Layout Test.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Misc Demos.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/Modals.png | 4 ++-- .../tests/snapshots/demos/Multi Touch.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Painting.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/Popups.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/Scene.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Screenshot.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Scrolling.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/Sliders.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/Strip.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/demos/Table.png | 4 ++-- .../tests/snapshots/demos/Tessellation Test.png | 4 ++-- .../tests/snapshots/demos/Text Layout.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/TextEdit.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Tooltips.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Undo Redo.png | 4 ++-- .../tests/snapshots/demos/Window Options.png | 4 ++-- .../tests/snapshots/demos/Window Resize Test.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/modals_1.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/modals_2.png | 4 ++-- crates/egui_demo_lib/tests/snapshots/modals_3.png | 4 ++-- ...als_backdrop_should_prevent_focusing_lower_area.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 ++-- .../snapshots/tessellation_test/Additive rectangle.png | 4 ++-- .../snapshots/tessellation_test/Blurred stroke.png | 4 ++-- .../tests/snapshots/tessellation_test/Blurred.png | 4 ++-- .../snapshots/tessellation_test/Minimal rounding.png | 4 ++-- .../tests/snapshots/tessellation_test/Normal.png | 4 ++-- .../Thick stroke, minimal rounding.png | 4 ++-- .../tests/snapshots/tessellation_test/Thin filled.png | 4 ++-- .../tests/snapshots/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 ++-- .../egui_kittest/tests/snapshots/combobox_closed.png | 4 ++-- .../egui_kittest/tests/snapshots/combobox_opened.png | 4 ++-- .../egui_kittest/tests/snapshots/image_snapshots.png | 4 ++-- .../tests/snapshots/menu/closed_hovered.png | 4 ++-- crates/egui_kittest/tests/snapshots/menu/opened.png | 4 ++-- crates/egui_kittest/tests/snapshots/menu/submenu.png | 4 ++-- .../egui_kittest/tests/snapshots/menu/subsubmenu.png | 4 ++-- .../snapshots/override_text_color_interactive.png | 4 ++-- crates/egui_kittest/tests/snapshots/readme_example.png | 4 ++-- .../tests/snapshots/should_wait_for_images.png | 4 ++-- crates/egui_kittest/tests/snapshots/test_masking.png | 4 ++-- .../tests/snapshots/test_scroll_initial.png | 4 ++-- .../tests/snapshots/test_scroll_scrolled.png | 4 ++-- crates/egui_kittest/tests/snapshots/test_shrink.png | 4 ++-- tests/egui_tests/tests/snapshots/grow_all.png | 4 ++-- .../snapshots/hovering_should_preserve_text_format.png | 4 ++-- .../egui_tests/tests/snapshots/layout/atoms_image.png | 4 ++-- .../tests/snapshots/layout/atoms_minimal.png | 4 ++-- .../tests/snapshots/layout/atoms_multi_grow.png | 4 ++-- tests/egui_tests/tests/snapshots/layout/button.png | 4 ++-- .../egui_tests/tests/snapshots/layout/button_image.png | 4 ++-- .../tests/snapshots/layout/button_image_shortcut.png | 4 ++-- tests/egui_tests/tests/snapshots/layout/checkbox.png | 4 ++-- .../tests/snapshots/layout/checkbox_checked.png | 4 ++-- tests/egui_tests/tests/snapshots/layout/drag_value.png | 4 ++-- tests/egui_tests/tests/snapshots/layout/radio.png | 4 ++-- .../tests/snapshots/layout/radio_checked.png | 4 ++-- .../tests/snapshots/layout/selectable_value.png | 4 ++-- .../snapshots/layout/selectable_value_selected.png | 4 ++-- tests/egui_tests/tests/snapshots/layout/slider.png | 4 ++-- tests/egui_tests/tests/snapshots/layout/text_edit.png | 4 ++-- tests/egui_tests/tests/snapshots/max_width.png | 4 ++-- .../egui_tests/tests/snapshots/max_width_and_grow.png | 4 ++-- tests/egui_tests/tests/snapshots/shrink_first_text.png | 4 ++-- tests/egui_tests/tests/snapshots/shrink_last_text.png | 4 ++-- .../egui_tests/tests/snapshots/sides/default_long.png | 4 ++-- .../snapshots/sides/default_long_fit_contents.png | 4 ++-- .../egui_tests/tests/snapshots/sides/default_short.png | 4 ++-- .../snapshots/sides/default_short_fit_contents.png | 4 ++-- .../tests/snapshots/sides/shrink_left_long.png | 4 ++-- .../snapshots/sides/shrink_left_long_fit_contents.png | 4 ++-- .../tests/snapshots/sides/shrink_left_short.png | 4 ++-- .../snapshots/sides/shrink_left_short_fit_contents.png | 4 ++-- .../tests/snapshots/sides/shrink_right_long.png | 4 ++-- .../snapshots/sides/shrink_right_long_fit_contents.png | 4 ++-- .../tests/snapshots/sides/shrink_right_short.png | 4 ++-- .../sides/shrink_right_short_fit_contents.png | 4 ++-- .../tests/snapshots/sides/wrap_left_long.png | 4 ++-- .../snapshots/sides/wrap_left_long_fit_contents.png | 4 ++-- .../tests/snapshots/sides/wrap_left_short.png | 4 ++-- .../snapshots/sides/wrap_left_short_fit_contents.png | 4 ++-- .../tests/snapshots/sides/wrap_right_long.png | 4 ++-- .../snapshots/sides/wrap_right_long_fit_contents.png | 4 ++-- .../tests/snapshots/sides/wrap_right_short.png | 4 ++-- .../snapshots/sides/wrap_right_short_fit_contents.png | 4 ++-- tests/egui_tests/tests/snapshots/size_max_size.png | 4 ++-- tests/egui_tests/tests/snapshots/visuals/button.png | 4 ++-- .../tests/snapshots/visuals/button_image.png | 4 ++-- .../tests/snapshots/visuals/button_image_shortcut.png | 4 ++-- .../visuals/button_image_shortcut_selected.png | 4 ++-- tests/egui_tests/tests/snapshots/visuals/checkbox.png | 4 ++-- .../tests/snapshots/visuals/checkbox_checked.png | 4 ++-- .../egui_tests/tests/snapshots/visuals/drag_value.png | 4 ++-- tests/egui_tests/tests/snapshots/visuals/radio.png | 4 ++-- .../tests/snapshots/visuals/radio_checked.png | 4 ++-- .../tests/snapshots/visuals/selectable_value.png | 4 ++-- .../snapshots/visuals/selectable_value_selected.png | 4 ++-- tests/egui_tests/tests/snapshots/visuals/slider.png | 4 ++-- tests/egui_tests/tests/snapshots/visuals/text_edit.png | 4 ++-- 132 files changed, 267 insertions(+), 267 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index f43a549a3..454fc6d89 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1307,10 +1307,10 @@ pub fn default_text_styles() -> BTreeMap { [ (TextStyle::Small, FontId::new(9.0, Proportional)), - (TextStyle::Body, FontId::new(12.5, Proportional)), - (TextStyle::Button, FontId::new(12.5, Proportional)), + (TextStyle::Body, FontId::new(13.0, Proportional)), + (TextStyle::Button, FontId::new(13.0, Proportional)), (TextStyle::Heading, FontId::new(18.0, Proportional)), - (TextStyle::Monospace, FontId::new(12.0, Monospace)), + (TextStyle::Monospace, FontId::new(13.0, Monospace)), ] .into() } @@ -1330,7 +1330,7 @@ impl Default for Style { spacing: Spacing::default(), interaction: Interaction::default(), visuals: Visuals::default(), - animation_time: 1.0 / 12.0, + animation_time: 6.0 / 60.0, // If we make this too slow, it will be too obvious that our panel animations look like shit :( #[cfg(debug_assertions)] debug: Default::default(), explanation_tooltips: false, @@ -1437,7 +1437,7 @@ impl Visuals { striped: false, slider_trailing_fill: false, - handle_shape: HandleShape::Circle, + handle_shape: HandleShape::Rect { aspect_ratio: 0.75 }, interact_cursor: None, diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 3ccc458fc..39d9bb5ce 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:c9a4ac7d3f959100ae760e32b1fa35ba5bbaa0dd3b3d25472cd9c311430e766e -size 335270 +oid sha256:44a68dc4d3aeebeb2d296c5c8e03aac330e1e4552364084347b710326c88f70c +size 335794 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index 7d9c5dfd8..deed497b1 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:2c4f0263b6ab61b9b589f6cb7b83ed020e0131bea88a7d2db24a8b2dff026f69 -size 93090 +oid sha256:e9a760fe4a695e6321f00e40bfa76fd0195bee7157a1217572765e3f146ea2cc +size 93640 diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index c44c6d3a1..a039b8c24 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:3ee4ef83971dc7df940326444723bc9ebd57378476686085d68574c35d3d6513 -size 182173 +oid sha256:a1670bbfc1f0a71e20cbbeb73625c148b680963bc503d9b48e9cc43e704d7c54 +size 181671 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index 629dbb24c..d16d7a2e9 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:47792205570445b5fc3a64ffbc6a4804c7b23b074af90ed1baca691f73f96963 -size 102366 +oid sha256:6b09dbb2e4038c57e28e946604b56123a01f63aa8aa029245448ed77c83ee910 +size 100070 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 39223555c..fcab88179 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:514f2d25acec42f1a7baf40716549fe233f61a19cca675165d53f99b2433bb76 -size 31669 +oid sha256:30929184fab7e7d5975243d86bcab79cd9f7a0c5d57dd9ae827464ff6570be7b +size 31795 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 54fd62562..86e0a3237 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:693d26c8195ef8062d81127290a1e2e252b317f6430aba8cabf1f1c8b25a3213 -size 26895 +oid sha256:b418b7f9d94244fe355fd81866ab9dc9750ff4e0cb7db39d902dd9892eae5246 +size 27049 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png index e5e08d8f7..a62936ff2 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d55b12672145f5a1b3fd7d8f063735dcce06d1e6dc6fa06af93d7d04c0460ab9 -size 26928 +oid sha256:5e4a6476a2bb8980a9207868b77a253c65c0ba8433f843bb17e622856695b720 +size 27686 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index e225a8dde..22341d7b3 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1054c1cb66803f36119e8e698f69dfaf1583167a8eba6316915a21c7303dc22c -size 78324 +oid sha256:5c1951b99908326b3f05ebb72aa4d02d0f297bdd925f38ded09041fae45400c1 +size 85217 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png index 06832b754..9afd1794a 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d43b44d4ae5c168762c74b3c34f9ae5a57b63ff3470dfc272a69625b57c8710 -size 62921 +oid sha256:4c303fa620a2c7bc491a0ac1f9afdf9601b352e0e5163526c5f8732edf6bd6b3 +size 63404 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png index 53a03ba14..05a412548 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6a7ac851b248bfe69f486ccaf12063ccc7e73fb01b4e7d07902677b5db6118b -size 25946 +oid sha256:0df751bac5947c9bf6f82d075cf5670a562742b80d6c512bcd642da5ed449d26 +size 25975 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png index 2aedac9ae..9b1b65636 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e37b5c785cd011dbbade5975147702fe7d458c22699c83e307033339f614c26 -size 20797 +oid sha256:0e26e87f2909414b614278a1cf0b485cda425aceb5419906426615dccdcbef2b +size 20877 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png index 6f8feb5ef..69d55cd27 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24e31eeaf1b7c07fb1ddb3aa92a876595436c20fa54937cf35c44fb7a7a6d890 -size 10774 +oid sha256:20e3050bd41c7b9d225feb71f3bea3fdd1b8f749f77c4d140b5e560f53eb32b7 +size 10731 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png index 1ea554b02..2a12a3152 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1c130d0a2cacc4fcc55d9c6947ce239a68cada8c207a03a04b08e822adb9cd5 -size 127038 +oid sha256:1227636b03a7d35db3482b19f6059ec7aaf03ca795edadd5338056be6f6a8f7f +size 126724 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index e54845856..0385ab120 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:294f03e8961a96705ca219da37555a4c856849ac2e8cea73bca29948e68526c6 -size 24505 +oid sha256:c01e96bf0aab24dbcfc05f2a6dcb0ffcddff69ec2474797de4fbce0a0670a8cd +size 24964 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 a19dafc5e..e14a4ecb5 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:d5bb6d587903f3bd06ed8e3410ba8f31aa88fe355adb36023e4949bf89c009b4 -size 101649 +oid sha256:ec357eafd145194f99c36a53a149a8b331fd691c5088df43ee96282b84bc81a4 +size 99439 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index 5a623f8a8..99cfc8f5d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12aa0ce4cd746cffc9fafc1daa43342df7b251f92a7e330be44b1b73e671c9f7 -size 18142 +oid sha256:b5c95085e3c78b3fa1cc39ebd032834bd5ab5a80c3a2cad482d8a5bcbad004b9 +size 18064 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png index 2274ecb7c..f2d914cc2 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc2575366c79922f95a7b40766b6a5a91fdac7bce49c1eb057d992679d11030f -size 115934 +oid sha256:28a69a52c07344576f2b5335497151e4e923b838dfaec9791402949ffb099c12 +size 116116 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png index 80f75d67d..c100d9209 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7b1f709a5e8be577dbdd6122935dc1969fbe355155b78b00b862be2bdb268d1 -size 24876 +oid sha256:e1378d865af3df02e12a0c4bc087620a4e9ef0029221db3180cdd2fd34f69d7f +size 24832 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 dac41f75d..58cc9f94b 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:b3201768ee61eb87aba5fac21b3382b160296acd3a0d7caab62360efe63cd94d -size 51714 +oid sha256:aff927596be5db77349ec0bbdcc852a0b1467e94c2a553a740a383ae318bad18 +size 51670 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png index e354d67c5..1ef6bf0db 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28112d906a73833fb6052402df4717a17fd374ab7c3a5f84348463e025689b58 -size 22255 +oid sha256:70b00222e6c63f97bfd8c7a179c15cfaba93f8f2566702d4b03997f4714fe6cb +size 22609 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png index 2d1948615..ed84d7428 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b485f4dd8b72fa6b8c689b4091a0660afd4f1301287382c44c16c3bb4fc5e0c -size 47315 +oid sha256:ad26106a86a6236f0db1c51bed754b370530813e9bb6e36c1be2948820fbef25 +size 47827 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 11e7bc7d0..9f42483fe 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:90d78b13159965cdaac9c67aa54a11390c0a18a0fdcee7f818edd06f87fa865f -size 24313 +oid sha256:afa66ba8daca5c00f9c49b1d9173ad8f5e826247d3a9369d7e7c360cbdfcb72e +size 22928 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index d2151b1bd..396d83508 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c672c2ad3c33863a09b35e00fcd761ba91488d194273788c9790ac475d20e33c -size 66212 +oid sha256:9d6f055247034fa13ab55c9ec1fca275e6c23999c9a7e01c87af1fcc930faac6 +size 66777 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png index c2f998766..47718fdd1 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d12e4f2fbfebc8ba3050f317e98f2a485ec7961db9190104143257499d206ac -size 32700 +oid sha256:90ab689d8a5034f5cab2ae2b44a8054d6dd815b3d295bee040c5bfcdf4564dee +size 33063 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index 7fc83efa7..e9f8a0e08 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a80efaa91120ab8b830037073b5c177045261da2a8f146c859bc76317a6a3b1a -size 36328 +oid sha256:9ad5938299b6fefb688ace5775f7906f5992cc119eedb487999cf963a5dcf813 +size 36275 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index 6f56df920..e53f4f7af 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a654b9ebd75fb1fea733378ffcd50876dae406610ef070c3aa487db0a5f48eb1 -size 17625 +oid sha256:2d2370972781f15a1d602deca28bca38f1c077152801870edf2112650b8b1349 +size 17708 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png index e926f466e..d00256a7e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87df1de1677b993bdaaf0c0f13db1e0c9a52bdf91cbdc8c4487357e09cbfc453 -size 56985 +oid sha256:f8b937a8a63de6fedcd0f9748b1d04cd863331a297bec78906885a0107def32a +size 61242 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index 0f8ca667e..9bf999143 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:b17c6044a9975a871da3062a5e1f55e500ebd4b0c325ee8c1d9277d8f7ba24e3 -size 35158 +oid sha256:17c8a6bc3ea09fe5940011e6d96ad26e47aee6ee672b92bd4fa23b40e4a0f790 +size 34493 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 32dd79e99..7e49a8613 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:165b7029b921d3c4a2ad7164eaf288efc3e5f022341dc89ea214f50890072c49 -size 23385 +oid sha256:be599ae66323140bba4a7d63546acbf84340b57e2d82d4736bf3fe590040319d +size 23623 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 16912ea51..6de002ee4 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:45a5e9542b928cf1624d84f6bf8e339dc78e02adb4d90ae83fad914941319631 -size 186697 +oid sha256:01c3cb5e8972e0cab5325328f93af8f51b35a0d61016e74969eab0f7ddea1e02 +size 176973 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 4c18fae80..417b183b5 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:d96d6f381aa5e2ac87ee8a7a3fcf1d07a98532642ba5d45e3c24a5c331bafd96 -size 119551 +oid sha256:f2f6cedc262259d52c1fbf4283d99b4b62ec732e8688b1e2799a2581425e0564 +size 120342 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index cadb7ea81..be51e5062 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b97435e619f0655d6332ed1f779af6b93c65b5264b09e7f05242db0b196c8c27 -size 26019 +oid sha256:142f65cee971f82a4917734c4f49ae233aa9a873028dca8c807d2825672bf2b2 +size 26657 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index dd8ef6869..cf728bf3f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a834ebd9c0e66b4e4e60d5dd02274624005e368ae4c9b6f0a34feef51ecd6648 -size 76635 +oid sha256:4fbcca2b13c94769a62b44853b19f7e841bbb60c9197b3d0bf6e83ef9f8f76d1 +size 77815 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 75cd7ad69..cd8524cef 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:a77520c0176ff60c506174998fa6e0cc9473103e93efb05858f52c82fe1b3852 -size 69473 +oid sha256:cc24f146adf0282cfb51723b56c76eceb92f2988fc67bbeefd16b93950505922 +size 70110 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index 90875e3e5..28c9f57ac 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff51d191c5883bf68c0961aef70842543285e6513598dc57893edf5b4ccc5856 -size 65490 +oid sha256:77aeaa1dcd391a571cb38732686e0b85b2d727975c02507a114d4e932f2c351b +size 65562 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 96b998fe2..3ee3adf6e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:806b43782ee228900ecb9c7b1fb7b57a8894669db1475a13852215f64d18b2c5 -size 21352 +oid sha256:5f964939ed1b3904706592915ca4fbbb951855ac88b466c51b835cd1c7467fb0 +size 21501 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index c790b5999..265dfdc1e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b70081c1423731d387df294f5685c56d265b6b66dfd95e606624665d45d3766 -size 64463 +oid sha256:415b1ce17dd6df7ca7a86fed92750c2ef811ff64720a447ae3ca6be10090666e +size 64624 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 fce774dc2..fb2bd06aa 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:1a837402657274b1a31e757005cb1fb0f5b345f9d3427be700357b982455baec -size 13081 +oid sha256:0eaf717bf0083737c4186ac39e7baf98f42fffb36b49434a6658eff1430a0ac6 +size 13187 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 b61e004c9..97fa6cebc 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:fda993429a2454cf84ae9cc73ae4242aa50a80a5fd8920dc35601bf8442e9669 -size 36020 +oid sha256:a31f0c12bb70449136443f9086103bd5b46356eedc2bb93ae1b6b10684ab69ca +size 36285 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png index 41115407b..7a042bdba 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8ee6b7e3e4c4d3dd96b1b7f6111a9e032e2ee96c157ff7d669332d15b23129d -size 43557 +oid sha256:b85a2af24c3361a0008fd0996e8d7244dc3e289646ec7233e8bad39a586c871c +size 44512 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index e5e0ddc0b..a184b67dc 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_1.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:702842a12c11f131bf41ad49542369b0aa520bf2ec89e460c069177c48e630b4 -size 48301 +oid sha256:60a44771c6bc9236593717236f9eca40bcb4723bac7567493cab4326f003eba3 +size 48693 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index 119ff2e5d..c8e7cb55a 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab58d458abb369d4925e6ca614cdfd5f87171de5def5daa23f0fa002af8c7b18 -size 48229 +oid sha256:53c1509f7be264ed2947cd4ec0f10b555e9f710e949ed6fd8a73ca8ade53abd4 +size 48570 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index a83010763..777b700c2 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a89afce6dbbc27ebac9989be3219d96aac4f51f09dea0c10c9a13e3e6e5b7d54 -size 44194 +oid sha256:20eecafb998f69c2384afabc27eec1f97f413d603ece944adae9a99139be0b58 +size 44689 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index 7e0bac996..700eaf46b 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:331d86036fea0271cda3969de4c59650df90c1d6442d61f99200fa699d5b9d74 -size 44318 +oid sha256:9a7f8282946761e6ab40193267e47a9421f5642bae67458a9aadb71ac1231c8f +size 44581 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 d04c406a7..5824e7a8e 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:89326fda609fdd4b6ce6aa5cd0881d2a720ee498c7283670085fb4116738117d -size 619548 +oid sha256:69442b230ab0eb5291b47290c6ec08eff21bb2032f164592b1b0205065a8b035 +size 621404 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 bda95de70..20777b41a 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:fde14e4bb03251a7ff4ba508e3c78eb33204781dc67dec440f93a146d74f6691 -size 813974 +oid sha256:d38bfa32246e60a408495101004c7346220e43e430440aba82737131206e5053 +size 826006 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 7d389ceb5..80435c851 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:d5c95515a85e1bb827ff5507843c30d39864fdf0578890ce788889578ae65724 -size 992333 +oid sha256:2fc6621f8a548991b198db4e1510f2aab04716c814fd5d7c4642c354dfb060f3 +size 1039325 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 ea6dc57ca..1866afd60 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:53423e41be4856056639d14aca6695966465d8ed0379a97deeb101aa41ff560b -size 1129280 +oid sha256:0f541e682b229cefc00220c07bb8fd929de98041403e3cfdbc1f45000fa96e32 +size 1209860 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 ef72be63c..a678c7233 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:56ec1367a030b816a01b5580f454d1a2138cc4dc1fd2534526743b33fd4d1be9 -size 1239055 +oid sha256:f3c9ede7cf36cb5704f80ac4544953db22c84c6b69b24d94c072f7d35ee79698 +size 1236290 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 4c0ac30ea..e6a694435 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:3d9ab25243e4850888e43b268801963d8fafb68aa27e0a7cfc1f4b563c8326e8 -size 1410062 +oid sha256:c36f92a5ced3be65e7b9b6364670f1f50702fcc330a0fab794d20efbc6367148 +size 1466940 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 50bf1982b..6a9f5b77e 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:ec6720a0b4d1db5ddcd08e4ee9834b56b9d0bded7d7e7194865f804d74bb76b2 -size 46251 +oid sha256:07443de8d14d8179e10b82b82be76531e68b1367fdbfe06993bb2e068ca92b0d +size 46794 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 5d1ececad..e98af25c9 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:234f2b50e096f1f1545531edeb5a7865e0a73e2035967ce3270602512c3a9a18 -size 88006 +oid sha256:096baabe30a44b013a893eb6e529bc24cb7b6a205edd22c1319cf1f137ac83da +size 88561 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 26a97fe62..19320ca47 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:d4611a5844cb85a000ec1fb7b51b6a75ba8ada24da108d962b16b0317e55fa7f -size 120119 +oid sha256:0d7fa14c81618b24b316df464d3a95954f94149e21cf2086acbaef7912ea2920 +size 120651 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 50bae8acb..e21e75fdf 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:e6fcca95ef83f8fd57359ac3bb52aa2bb338a726845fbe0370c41e59b99f15d3 -size 52535 +oid sha256:3dfced2f1f04816366b7688614339d2e5aee4b655d399f02692125ecfb1b17df +size 53066 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 cf996488b..95d683c9e 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:c15f24d4b9a969b7dc275a3402e2fc36a1b86f3bfaf4f43b8c3e7ae36634e70e -size 55729 +oid sha256:2314aefa3f38b20600c918413a146c9d31e90b379b44cf53b378da845b8a1199 +size 56280 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 73c70b4ee..6de0d75d4 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:58bbd42732e48c1792765dc0c48d57ef0e4441a8b4e97eed936d564bef661116 -size 56210 +oid sha256:b258e241726f19d9b0842fbd0fe5c7b39ea61ecae9ee925c1c1f7b18be0bdea1 +size 56737 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 69aa782b5..d4a3c94b0 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:8546adf43506733e48e3c8988f7e869bffb2a674629dc9d66e52a5aae0123653 -size 37132 +oid sha256:a8c7a56c6f29756cf49edbefe38ec2a6bd164b400dd458686405655e5e7f1c77 +size 37596 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 cd8804dc9..c329ebf32 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:aff779555f23d9360f58f1b7dc2686c72b848cc5d0a889ba4d5446fc4b252d65 -size 37112 +oid sha256:c0c4a3625ba10777e0878bea1b26c8ac06d0558f777f9b82c9bbe987195c6d60 +size 37623 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 daabdb90b..c672710c6 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:7ad3e3de6e1c12eccd12750c44f52e863f28c3234cbc4f3091674e8c03a9d503 -size 66162 +oid sha256:dae36303c3b75ae100a43511436a9598190f556af2ce7f48b97f02ec09a7d81c +size 66858 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 586bf57e3..b0cf0e18e 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:e9208c3b89665e8aab19ab5e7b427ed8251381c92c3b7c51a708f80c61be28b2 -size 152614 +oid sha256:82a2c649f039a7b6549391253bb20fbbe4dcb404fec98850488155a7297f4353 +size 158896 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 62c85f873..ac962e05e 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:b55a25a1ccff076036b574c82a923d13ff353c2f7a4927f2d911d04121782c05 -size 60492 +oid sha256:c1fc06217640ee574a1cebd6c2351d7b8fb52ad263144175d0eed64ea46109d2 +size 61119 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 7e5f3baf8..63c5e8b74 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:ae7992752e2fce96d0083143f07f216d27472ee057995ac2630cecccfeb44bcc -size 146455 +oid sha256:cb4408cd1654af081e9ffff1af2d6132dce9b395af3657cd2dc97cbcd3919310 +size 152219 diff --git a/crates/egui_kittest/tests/snapshots/combobox_closed.png b/crates/egui_kittest/tests/snapshots/combobox_closed.png index 01d7d4d03..bb574d356 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_closed.png +++ b/crates/egui_kittest/tests/snapshots/combobox_closed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e42d26fa8188790c1dfed8e6a1a9f36a01b066a798dd1c878eeb3f0d1fb7e52 -size 4475 +oid sha256:f297609dd69fd479377eaea7cd304b717e0523a650dbf76e19c6d1f1c5af1343 +size 4518 diff --git a/crates/egui_kittest/tests/snapshots/combobox_opened.png b/crates/egui_kittest/tests/snapshots/combobox_opened.png index 7c392a7c7..aaa7198ce 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_opened.png +++ b/crates/egui_kittest/tests/snapshots/combobox_opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03564030d1ce4d1698b2e831df405d88d3930de2894ff2b601adf1723c160383 -size 7443 +oid sha256:8f70ef032c241cd63675a246de07886c5c822e6fe21525b3a6d3fee106a589c9 +size 7501 diff --git a/crates/egui_kittest/tests/snapshots/image_snapshots.png b/crates/egui_kittest/tests/snapshots/image_snapshots.png index 7df240eb1..5036d662c 100644 --- a/crates/egui_kittest/tests/snapshots/image_snapshots.png +++ b/crates/egui_kittest/tests/snapshots/image_snapshots.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:668999dda2ce857c5a43dd7bdaf88d2062105683f2628b7f41b8885c423693df -size 2136 +oid sha256:de4a197f82befde31b6966f902567c35cef96c7d541fd65b4207c693ab12bace +size 2036 diff --git a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png index 3d8bfd207..d2969adee 100644 --- a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png +++ b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d62d6e8eb73acaaa4851268f33ca6a9411f5c96eeaae5795bb5f71af4f8e31ba -size 10819 +oid sha256:dd6e159a462dde10240c4ca51da5ca5badfb7fc170bad97a59106babb72f8ae3 +size 10795 diff --git a/crates/egui_kittest/tests/snapshots/menu/opened.png b/crates/egui_kittest/tests/snapshots/menu/opened.png index c52ecaf09..30f26b446 100644 --- a/crates/egui_kittest/tests/snapshots/menu/opened.png +++ b/crates/egui_kittest/tests/snapshots/menu/opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:670b2c2887f3be0d808faca41195caebf227420034a725494bb5a07122f4cabe -size 21881 +oid sha256:8f2a5873350f85457d599c1fd165ac756ed69758e7647e160c64f44d2f35c804 +size 21812 diff --git a/crates/egui_kittest/tests/snapshots/menu/submenu.png b/crates/egui_kittest/tests/snapshots/menu/submenu.png index 8dde92fe0..96ffaf97c 100644 --- a/crates/egui_kittest/tests/snapshots/menu/submenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/submenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52318e0106cc60d9442b2caa9d4cfa6b0831e9a85e2ed5415be67e9cdb113303 -size 29122 +oid sha256:facc05c499745594ac286f15645e40447633a176058337cad9edcb850ad578c7 +size 29379 diff --git a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png index ceb49b1ca..d1d0b4cd3 100644 --- a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10ac5ab3174646c2ec943c43ee954d7450f2d435f14b184bc824b858f1f036c2 -size 33901 +oid sha256:9f23ff8c6782befdbe7bd5f076dcdda15c38555f8e505282369bf52e43938c1b +size 34194 diff --git a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png index 7f3a5599f..6e92f9f03 100644 --- a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png +++ b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89d172c28662826c8b0897a703730c629ca438755a14f040f029944d55f24353 -size 19966 +oid sha256:2fa5cb5b96232d729f89be8cc92263715fe7197e72343b71e57e53a359afe181 +size 19881 diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index eed33654c..2c8565718 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:b17893705837c90f589b23b1e5b418e5ce93c5601c4abf348b8fc6ef416e4278 -size 1947 +oid sha256:bb8d702361987803995c0f557ce94552a87b97dcd25bed5ee39af4c0e6090700 +size 1904 diff --git a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png index 4a3f79eb4..7e33877fc 100644 --- a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png +++ b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0b3a6fdd415cfae89d08fe0d10b0dab2327bba5f0fdb2032d3db271811313e6 -size 2182 +oid sha256:f038c7fdf31f35107ec6e29edc0895049160ccbe98d1577c16ae082605b58d52 +size 2207 diff --git a/crates/egui_kittest/tests/snapshots/test_masking.png b/crates/egui_kittest/tests/snapshots/test_masking.png index def54053d..a5fb50908 100644 --- a/crates/egui_kittest/tests/snapshots/test_masking.png +++ b/crates/egui_kittest/tests/snapshots/test_masking.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc85c7d6a9fe285ad27693169fd9a9fead0286637e3f77a349e573e660a13c47 -size 5697 +oid sha256:4d94d4c3300d406fd1d93ddd90a9a46f99eac81a98a84f4d97a20fe4ef492e5d +size 5674 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png index 00ff45847..586dc00b7 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd0968233f5145a3708fe5ff771473abd824952e71182c2311670417c6a92f04 -size 7623 +oid sha256:460cb2e9c91d139334c797829fd82fa77d593e9e58531e57a6b649c7e8fad228 +size 7405 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png index 8ac89d1aa..64feb6323 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edc4149d67817420db2a2877a1eb83ad7569fd7aa8a4bf75a6dd93b2b59e9615 -size 8305 +oid sha256:996985b155bd579cc4769d8cde5aa7e87c20ed909176da6b52dddeeb78a1cfba +size 8290 diff --git a/crates/egui_kittest/tests/snapshots/test_shrink.png b/crates/egui_kittest/tests/snapshots/test_shrink.png index dc83dbda3..c25ae0367 100644 --- a/crates/egui_kittest/tests/snapshots/test_shrink.png +++ b/crates/egui_kittest/tests/snapshots/test_shrink.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99ec3f7a81bbe351d0a894005fad95129f36d5c8cc29b4648ddfb9ddce201913 -size 2983 +oid sha256:025942c144891b8862bf931385824e0484e60f4e7766f5d4401511c72ff20756 +size 2975 diff --git a/tests/egui_tests/tests/snapshots/grow_all.png b/tests/egui_tests/tests/snapshots/grow_all.png index e065c70f0..373889987 100644 --- a/tests/egui_tests/tests/snapshots/grow_all.png +++ b/tests/egui_tests/tests/snapshots/grow_all.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d815e6dd4f35df988cd8f52d19e607aa51f56ba0fdb7391a1c010ea769200aa -size 14237 +oid sha256:469a1b1faa71da472c07bcc7103933db9964440a4c49dc596220f070e9b483f5 +size 14375 diff --git a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png index 46492a251..7db5ec76b 100644 --- a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png +++ b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:880b50269a3b7a544901655f199dd2fe0bd7f004e180c6d3a1375000f6d138a5 -size 10271 +oid sha256:0fdc04ac6f24c20688c846af8759f91db2b3f8a93abcd0c5669c9050b98451ea +size 10152 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_image.png b/tests/egui_tests/tests/snapshots/layout/atoms_image.png index d15676184..bf98d3d2d 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_image.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bedaa1bc229b0135f39ab79db1e0e62ff9c86b057aa4a2c6d1f9e6c051f12eaf -size 404014 +oid sha256:070638cd5c174200161498123a612e4a58d58517b539e91e289d1e3dd38670bb +size 388531 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png index 91a7d0d33..ae17dff6a 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6028b96a4d209b654f5a376e594ac4f680bb4cd8796d96d9acafb2f8b74fff6f -size 396750 +oid sha256:ac8885d6e5325b5f1f0ccb11f185df5c4937b66c80159aa8cf53930f5c8045e2 +size 395599 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png index 7bdfdc46b..97c181c70 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:963e4a024d6bea8e1554305286c59a28e8fbf09e3bbfb57cb93c85b7868b0fe9 -size 303826 +oid sha256:05b891dd999050150f4cd4226a1c68673b352f04d08d405751d66c698c7711c5 +size 309736 diff --git a/tests/egui_tests/tests/snapshots/layout/button.png b/tests/egui_tests/tests/snapshots/layout/button.png index ef67e84e2..e53754f51 100644 --- a/tests/egui_tests/tests/snapshots/layout/button.png +++ b/tests/egui_tests/tests/snapshots/layout/button.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:455888286bea147481f797edf31e4d119118d3d38933b7e5f41a6c3d82146751 -size 323220 +oid sha256:6e8a7835e3fd22aafc22b6c536051367e2c27c8607fbe5d8b6b6cf6d0ba3d54f +size 332771 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png index 4e6908277..ece6568e9 100644 --- a/tests/egui_tests/tests/snapshots/layout/button_image.png +++ b/tests/egui_tests/tests/snapshots/layout/button_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6811a7d4d041fdb2155d32d0e6ebfe4425a94bc5e3fff2f0380f65003487854b -size 347791 +oid sha256:59cbb416865f8bede12b449d8e65baddb6949df017face2782a3492de7a058e3 +size 355276 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 05e9e4306..13f860b83 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:1a481554267ad3e4c65f3552c64181d7cf0602193795ec4560e511a33586e879 -size 430746 +oid sha256:cad07a16b6f0eb8c7647c7d0500ea99c68dddf2eef892b4df48aa20f581e8a85 +size 438837 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox.png b/tests/egui_tests/tests/snapshots/layout/checkbox.png index c60b61189..18ebbdb7c 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08d455bd1b3bc259ab051ad6fcea713271a00ac11cb4a97399655457249e8abe -size 398447 +oid sha256:4747efdf758e7e8e2d7f3954d9595dfd45d3b4b86923b8ff39c8a96002bb4825 +size 408726 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png index 8a84f35f2..127aa7f30 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9df1bcba7179ef9a6e1a7478049918c5019b21e93f623fb01c6c5c26225632b4 -size 428446 +oid sha256:4a3ca4b3a47ff516f9b05799cdf5f92845ae1e058728d635986cc61b7317f110 +size 437102 diff --git a/tests/egui_tests/tests/snapshots/layout/drag_value.png b/tests/egui_tests/tests/snapshots/layout/drag_value.png index 495712a80..471d3b867 100644 --- a/tests/egui_tests/tests/snapshots/layout/drag_value.png +++ b/tests/egui_tests/tests/snapshots/layout/drag_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e544781b4d83dca5d938657bf488db7df37a9bda5334b48c34cd6a15642b0ecc -size 244477 +oid sha256:c168f197bec3bc780553db0a47f464f8d76afc606e28e2545ecd91f174abe551 +size 249781 diff --git a/tests/egui_tests/tests/snapshots/layout/radio.png b/tests/egui_tests/tests/snapshots/layout/radio.png index 7642aa187..07e20176d 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio.png +++ b/tests/egui_tests/tests/snapshots/layout/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33fe0d203e53c9adab6317f8fccfc742e68a7e12efcb972825c4ec5b3f34753d -size 339179 +oid sha256:39f1985a3a975b1b9a179d3b1bd5832e0b4c30d10232babf2e4736f55b43989f +size 350051 diff --git a/tests/egui_tests/tests/snapshots/layout/radio_checked.png b/tests/egui_tests/tests/snapshots/layout/radio_checked.png index 19faf9b40..2163011e6 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/radio_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:523d529c9c12af56f8e1de2a325287ca7c8f97421d7fa20464e49fbfd08a17e6 -size 359389 +oid sha256:18a38fe66d5ac8f2cf5c109f1cd9c29951e5bf8428b6bf0d4587dfc8f8c5c890 +size 370205 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value.png b/tests/egui_tests/tests/snapshots/layout/selectable_value.png index f6825f605..2ee7f7d0e 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c10baa7eb1d6e4fbf7503f1bc15de623f7bab104cb1af9f4d7912de612cb45a -size 404088 +oid sha256:bfc900ea84b408564652df487e705311b164d9bd3ff5631c3cebb83b06497a7b +size 410131 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png index 0b46f297d..7e3dd6319 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b84eede8fae61c6417ecb59908efa13170cce843e5ed20f810360bd7c1acb8b4 -size 416638 +oid sha256:c9d08ce85c9210a7d9046480ab208040e5ba399c40acaecf5cb43f807534bce9 +size 423523 diff --git a/tests/egui_tests/tests/snapshots/layout/slider.png b/tests/egui_tests/tests/snapshots/layout/slider.png index d1a39e2fe..b8cc394c4 100644 --- a/tests/egui_tests/tests/snapshots/layout/slider.png +++ b/tests/egui_tests/tests/snapshots/layout/slider.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7db0cec954ff4eccd1e814107034b205ee0af6ba7d4ee523bd5f22d6631dd871 -size 341575 +oid sha256:b402195e54bdbd09985e4b30a025083298f29ab747b809fbb864c8dfef0975eb +size 289714 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit.png b/tests/egui_tests/tests/snapshots/layout/text_edit.png index dfe942fcb..8b53899dd 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a2fb352590e99ea3593ad1b9be8ae4de422892567468c979dd35bd675390730 -size 239449 +oid sha256:5ed6af3a92790e07b71e71637b5d6bb45d55a7d26738d438714aca64d7f4534c +size 245394 diff --git a/tests/egui_tests/tests/snapshots/max_width.png b/tests/egui_tests/tests/snapshots/max_width.png index 5fc8690ac..a10284911 100644 --- a/tests/egui_tests/tests/snapshots/max_width.png +++ b/tests/egui_tests/tests/snapshots/max_width.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e014a3d42234c9fe2d7f78ad79cd290e53e387688347b91991a4ec557f809f82 -size 9041 +oid sha256:9e2dc33b2d4caddac86dd8649d09ff3d57187a0152240f824a6aa170a35dd719 +size 8570 diff --git a/tests/egui_tests/tests/snapshots/max_width_and_grow.png b/tests/egui_tests/tests/snapshots/max_width_and_grow.png index 6f61d9c49..b0d5c134f 100644 --- a/tests/egui_tests/tests/snapshots/max_width_and_grow.png +++ b/tests/egui_tests/tests/snapshots/max_width_and_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8301a3ea55b19269bada62cb7dd4ac296b9c2f7f3d11954f3eb9478641e3597b -size 9043 +oid sha256:36ab2c05eade94dcfe524651a0954c122aea754976af94a73a7efd950055c9eb +size 8574 diff --git a/tests/egui_tests/tests/snapshots/shrink_first_text.png b/tests/egui_tests/tests/snapshots/shrink_first_text.png index 36210b716..7bae217bb 100644 --- a/tests/egui_tests/tests/snapshots/shrink_first_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_first_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:904f5fad4c9548bdd3df1e0350066d6ceac36fd8573573f6201e326e867eb029 -size 12032 +oid sha256:c2ba53264abcaa2ee79858ddbd475ed35aa28d146b846cbc080c26911d373ea6 +size 11881 diff --git a/tests/egui_tests/tests/snapshots/shrink_last_text.png b/tests/egui_tests/tests/snapshots/shrink_last_text.png index e9294bacf..821490e52 100644 --- a/tests/egui_tests/tests/snapshots/shrink_last_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_last_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d0b548a375ca69f4427ec7008d7bf3f452272d33b7d18d5b63a587f63041c61 -size 12623 +oid sha256:082d80ae144338dce45169c02038b6dc5b75d7b6d93c2a8213ddbb2a8784cc92 +size 12435 diff --git a/tests/egui_tests/tests/snapshots/sides/default_long.png b/tests/egui_tests/tests/snapshots/sides/default_long.png index 4c1ba903e..452ed723a 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_long.png +++ b/tests/egui_tests/tests/snapshots/sides/default_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8b7ee31e8c1b8ec2f46cc5e9d61d7925d68a5e0bfa8a6fe1cf511111c8129e7 -size 8191 +oid sha256:0e455b08f4674a9326682771f12456a71cc22dfd733ee965fdbbb5582cba0380 +size 8176 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 index cb1aa5acf..ce5996d2f 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png +++ b/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 -size 8971 +oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc +size 8963 diff --git a/tests/egui_tests/tests/snapshots/sides/default_short.png b/tests/egui_tests/tests/snapshots/sides/default_short.png index 8783d2a44..2d7ccae52 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_short.png +++ b/tests/egui_tests/tests/snapshots/sides/default_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 -size 1718 +oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 +size 1700 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 index e137eb319..9c5635e19 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png +++ b/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 -size 1318 +oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 +size 1305 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png index ec941bf8f..cc17a1c48 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ee1cb1948c5d070c79248fe12727626872ff57ff3ce027d957f33eeb1d309ac -size 7450 +oid sha256:d36fb3c42b7f74e0b6dd8e73b9f8455e7fac035f5979cb93769e6a3f5453fdbc +size 7239 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 index cb1aa5acf..ce5996d2f 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 -size 8971 +oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc +size 8963 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png index 8783d2a44..2d7ccae52 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 -size 1718 +oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 +size 1700 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 index e137eb319..9c5635e19 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 -size 1318 +oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 +size 1305 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png index 435225195..03ca0a66e 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1f7df988359773b96ac7645eb919260076b090eb980fe900a46ad765c69bc4f -size 7353 +oid sha256:89d9445d7aaff34ace5322af6efc308f261cf5becfdd11aa7fec016236ecfa84 +size 7064 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 index cb1aa5acf..ce5996d2f 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 -size 8971 +oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc +size 8963 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png index 8783d2a44..2d7ccae52 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 -size 1718 +oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 +size 1700 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 index e137eb319..9c5635e19 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 -size 1318 +oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 +size 1305 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png index f6cfcde28..48332d65b 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:510dde01a46ce7fdf1142bc88e7873ba83c5d52a6a93a6f375ff565c1ad5d11f -size 9468 +oid sha256:b7487a1b77fd2db2493bca7d42128aad7a0049962599c8b5add2b7b25376a37d +size 9381 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 index cb1aa5acf..ce5996d2f 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 -size 8971 +oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc +size 8963 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png index 8783d2a44..2d7ccae52 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 -size 1718 +oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 +size 1700 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 index e137eb319..9c5635e19 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 -size 1318 +oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 +size 1305 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png index 76de1f273..fa65ff9db 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25c0c51f39b515609a51a07514606c474c4bd8516e5597569b58a31dfedfb790 -size 9448 +oid sha256:1f614fb9b2aba8cc5a6997a519ca92083fb4378a36f335570d9e870159267f40 +size 9495 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 index cb1aa5acf..ce5996d2f 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f89796ebda793d34c8e2a14e67b704bc59d3c2538070f28a544dc1be17bb7f88 -size 8971 +oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc +size 8963 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png index 8783d2a44..2d7ccae52 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e851957d66bb7b15ad558e3a67a12155146545a3db308f964a5088784d36810 -size 1718 +oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 +size 1700 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 index e137eb319..9c5635e19 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ab59052273826057aca96159596ee4333357ffdd22ad1310eaddce7c025b042 -size 1318 +oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 +size 1305 diff --git a/tests/egui_tests/tests/snapshots/size_max_size.png b/tests/egui_tests/tests/snapshots/size_max_size.png index 4a221a5b8..9c5ac8be3 100644 --- a/tests/egui_tests/tests/snapshots/size_max_size.png +++ b/tests/egui_tests/tests/snapshots/size_max_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08c69a55622468c906581534fe874f1b51515922ec4e92b2d219feec56dbf941 -size 8992 +oid sha256:5230bd00a43990b30e523528b609e04916dab3cf87a59d56e39dcd5926f47aa5 +size 8838 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png index b874abe60..4c81f62fc 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:db667120ae1f7909c8f1a9f956ec435d415ea5759c5ea40b8af4bc37cd477da1 -size 10004 +oid sha256:1c05992e16c1abf6d174fed73d19cad6bb2266e0adb87b8232e765d75fcf3f14 +size 10310 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png index 1a2870238..00582f3ae 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:24105bf3867e453ebe6457d30ba3c6e7356f6522137bab46bca01b28ac19c372 -size 11205 +oid sha256:7681d33a5a764187c084c966a4e47063136e2832094c44f62718447b1b3027ec +size 11292 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 5cc894260..1429bfd2d 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:773f4bb9e40340bd07e18ff98f95a34c5ddb3daae797625383c79bc54d406837 -size 13897 +oid sha256:b99a82e9f3dfa24c079545272d680b55c4285c276befa0efc492fe273422f541 +size 14195 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 c750d4d61..c73effead 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:6ad507f427d9cf10778aa9289f499a7364f08c55c8d8d8fec565d9d0363467ee -size 13553 +oid sha256:cdf079228b762949dbc67308103f8fe1328b6c0175f312ccc492d4e86d42127b +size 13868 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png index 116086858..16d88e546 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4bdfc4468b385f3b83ed7bfa72f0cd7cf07e6d6a60c07db876c9c55c009fc721 -size 12723 +oid sha256:0bafe4c157696bfb52940b69501416d4da0b4eab52f34f52220d2e9ed01357cf +size 12901 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index 4ebe6b427..fd85297ac 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:289f3202b84d5e17cdda65edd8ed9c4201278e1e9a8422eab1579972e2d8baf2 -size 13807 +oid sha256:72175bf108135b422d978b701d29e6d9a5348c536e25abc924234bc11b6b7f21 +size 14016 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png index be8981733..5411009f0 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:100ae526549f0d85466fa41c3f8cb60dc0d2501729444fff7ed566b09fa9d457 -size 7493 +oid sha256:129121534b5f1a2a668898ebb3560820fe50aa4d3546ef46cc764d5513787e9e +size 7529 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png index 0af777736..e00b42d8c 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f78fc2de7c20d08c98909a5f53517e91d0ac46f98c0c76ff350d858e2867907 -size 11170 +oid sha256:e9999c7921f8b277f456189ce0f1185120b4cde7c9a01485a5a7d83f12e95527 +size 11710 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index 1efaa705e..0021e7d0a 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:1ae4d4a7d079b2557f1b7b505d91cc7438f4052db6f6a149990bd11754b1ca49 -size 11845 +oid sha256:8fec1fd9f80e5fa17b1cda690c0856e7e5fd674d113a10b1d60b14f5a6c6dd6b +size 12401 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png index cdec6780a..a0b480be4 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52babe11fc4af5eb9d9137107594a59b7987cc9d3116e444cc8d739408f35cba -size 12871 +oid sha256:ac18e2eef000a80858b2d0811f9ee31304c6ff96f7a91dc60cc1a404ae28ce38 +size 13246 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 7405322ad..291263c44 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:130704c3320a88309e7ad7a3ca441741727fe891c080e0593377c083f5b548c8 -size 13104 +oid sha256:c11fe0399c85db5a618580ec4c1f2fe76176c6ea0ead3710a430d9a2bf8acc5d +size 13352 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index 8ad98c155..67b0b365b 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:af0764e450a544af4eab1c9c123fe4c46f313b84f070c0e0b756c273db2d2fed -size 11204 +oid sha256:a41bf44780feefa108a230ae617830445791bde16d712ac35530350d5d009481 +size 9045 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png index bf87660fb..d8e56eb2a 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:078a3d58c6e2cc2e9189d257b09f3c2c19721c09666bfe48103884005fb4eb6c -size 7228 +oid sha256:a103b51df184d5480438e8b537106432205a6d86f2927ab1bd507fe8ed3bb29b +size 7656 From 802d307e4a2835cf4cf184d1cc99bea525b0c959 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 9 Sep 2025 16:07:39 +0200 Subject: [PATCH 223/388] Remove deprecated fields from `PlatformOutput` (#7523) --- crates/eframe/src/web/app_runner.rs | 12 ------------ crates/egui-winit/src/lib.rs | 11 ----------- crates/egui/src/context.rs | 4 ++-- crates/egui/src/data/output.rs | 28 ---------------------------- 4 files changed, 2 insertions(+), 53 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 9421f9b44..bd245a1fe 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -305,8 +305,6 @@ impl AppRunner { } fn handle_platform_output(&self, platform_output: egui::PlatformOutput) { - #![allow(deprecated)] - #[cfg(feature = "web_screen_reader")] if self.egui_ctx.options(|o| o.screen_reader) { super::screen_reader::speak(&platform_output.events_description()); @@ -315,8 +313,6 @@ impl AppRunner { let egui::PlatformOutput { commands, cursor_icon, - open_url, - copied_text, events: _, // already handled mutable_text_under_cursor: _, // TODO(#4569): https://github.com/emilk/egui/issues/4569 ime, @@ -342,14 +338,6 @@ impl AppRunner { super::set_cursor_icon(cursor_icon); - if let Some(open) = open_url { - super::open_url(&open.url, open.new_tab); - } - - if !copied_text.is_empty() { - super::set_clipboard_text(&copied_text); - } - if self.has_focus() { // The eframe app has focus. if ime.is_some() { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 6d0604cfd..206b6bd3a 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -827,14 +827,11 @@ impl State { window: &Window, platform_output: egui::PlatformOutput, ) { - #![allow(deprecated)] profiling::function_scope!(); let egui::PlatformOutput { commands, cursor_icon, - open_url, - copied_text, events: _, // handled elsewhere mutable_text_under_cursor: _, // only used in eframe web ime, @@ -860,14 +857,6 @@ impl State { self.set_cursor_icon(window, cursor_icon); - if let Some(open_url) = open_url { - open_url_in_browser(&open_url.url); - } - - if !copied_text.is_empty() { - self.clipboard.set_text(copied_text); - } - let allow_ime = ime.is_some(); if self.allow_ime != allow_ime { self.allow_ime = allow_ime; diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 20c9ab25e..cdc0199ed 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -698,7 +698,7 @@ impl ContextImpl { /// ``` /// # let ctx = egui::Context::default(); /// if ctx.input(|i| i.key_pressed(egui::Key::A)) { -/// ctx.output_mut(|o| o.copied_text = "Hello!".to_string()); +/// ctx.copy_text("Hello!".to_owned()); /// } /// ``` /// @@ -1534,7 +1534,7 @@ impl Context { /// ``` /// # let ctx = egui::Context::default(); /// # let open_url = egui::OpenUrl::same_tab("http://www.example.com"); - /// ctx.output_mut(|o| o.open_url = Some(open_url)); + /// ctx.send_cmd(egui::OutputCommand::OpenUrl(open_url)); /// ``` pub fn open_url(&self, open_url: crate::OpenUrl) { self.send_cmd(crate::OutputCommand::OpenUrl(open_url)); diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 58153c8f7..bd7f62913 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -113,24 +113,6 @@ pub struct PlatformOutput { /// Set the cursor to this icon. pub cursor_icon: CursorIcon, - /// If set, open this url. - #[deprecated = "Use `Context::open_url` or `PlatformOutput::commands` instead"] - pub open_url: Option, - - /// If set, put this text in the system clipboard. Ignore if empty. - /// - /// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`]. - /// - /// ``` - /// # egui::__run_test_ui(|ui| { - /// if ui.button("📋").clicked() { - /// ui.output_mut(|o| o.copied_text = "some_text".to_string()); - /// } - /// # }); - /// ``` - #[deprecated = "Use `Context::copy_text` or `PlatformOutput::commands` instead"] - pub copied_text: String, - /// Events that may be useful to e.g. a screen reader. pub events: Vec, @@ -187,13 +169,9 @@ impl PlatformOutput { /// Add on new output. pub fn append(&mut self, newer: Self) { - #![allow(deprecated)] - let Self { mut commands, cursor_icon, - open_url, - copied_text, mut events, mutable_text_under_cursor, ime, @@ -205,12 +183,6 @@ impl PlatformOutput { self.commands.append(&mut commands); self.cursor_icon = cursor_icon; - if open_url.is_some() { - self.open_url = open_url; - } - if !copied_text.is_empty() { - self.copied_text = copied_text; - } self.events.append(&mut events); self.mutable_text_under_cursor = mutable_text_under_cursor; self.ime = ime.or(self.ime); From b0c568a78e9ac943e97ba63e64dcd2ceba76a087 Mon Sep 17 00:00:00 2001 From: Alan Everett Date: Thu, 11 Sep 2025 06:44:17 -0400 Subject: [PATCH 224/388] Add rotation gesture support for trackpad sources (#7453) Co-authored-by: Emil Ernerfeldt Co-authored-by: Lucas Meurer --- crates/eframe/src/web/backend.rs | 8 ++- crates/eframe/src/web/events.rs | 71 ++++++++++++++++++- crates/egui-winit/src/lib.rs | 31 +++++++- crates/egui/src/data/input.rs | 3 + crates/egui/src/input_state/mod.rs | 31 ++++++++ crates/egui_demo_lib/src/demo/multi_touch.rs | 48 +++++++++---- .../tests/snapshots/demos/Multi Touch.png | 4 +- 7 files changed, 174 insertions(+), 22 deletions(-) diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index e81e5487d..4814fa99b 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -11,9 +11,15 @@ use super::percent_decode; /// Data gathered between frames. #[derive(Default)] pub(crate) struct WebInput { - /// Required because we don't get a position on touched + /// Required because we don't get a position on touchend pub primary_touch: Option, + /// Helps to track the delta scale from gesture events + pub accumulated_scale: f32, + + /// Helps to track the delta rotation from gesture events + pub accumulated_rotation: f32, + /// The raw input to `egui`. pub raw: egui::RawInput, } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index a5bb4c0eb..dff1463c4 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -7,6 +7,7 @@ use super::{ push_touches, text_from_keyboard_event, translate_key, }; +use js_sys::Reflect; use web_sys::{Document, EventTarget, ShadowRoot}; // TODO(emilk): there are more calls to `prevent_default` and `stop_propagation` @@ -101,6 +102,7 @@ pub(crate) fn install_event_handlers(runner_ref: &WebRunner) -> Result<(), JsVal install_touchcancel(runner_ref, &canvas)?; install_wheel(runner_ref, &canvas)?; + install_gesture(runner_ref, &canvas)?; install_drag_and_drop(runner_ref, &canvas)?; install_window_events(runner_ref, &window)?; install_color_scheme_change_event(runner_ref, &window)?; @@ -816,7 +818,7 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV let egui_event = if modifiers.ctrl && !runner.input.raw.modifiers.ctrl { // The browser is saying the ctrl key is down, but it isn't _really_. - // This happens on pinch-to-zoom on a Mac trackpad. + // This happens on pinch-to-zoom on multitouch trackpads // egui will treat ctrl+scroll as zoom, so it all works. // However, we explicitly handle it here in order to better match the pinch-to-zoom // speed of a native app, without being sensitive to egui's `scroll_zoom_speed` setting. @@ -847,6 +849,73 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV }) } +fn install_gesture(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { + runner_ref.add_event_listener(target, "gesturestart", |event: web_sys::Event, runner| { + runner.input.accumulated_scale = 1.0; + runner.input.accumulated_rotation = 0.0; + handle_gesture(event, runner); + })?; + runner_ref.add_event_listener(target, "gesturechange", handle_gesture)?; + runner_ref.add_event_listener(target, "gestureend", |event: web_sys::Event, runner| { + handle_gesture(event, runner); + runner.input.accumulated_scale = 1.0; + runner.input.accumulated_rotation = 0.0; + })?; + + Ok(()) +} + +#[expect(clippy::needless_pass_by_value)] // So that we can pass it directly to `add_event_listener` +fn handle_gesture(event: web_sys::Event, runner: &mut AppRunner) { + // GestureEvent is a non-standard API, so this attempts to get the relevant fields if they exist. + let new_scale = Reflect::get(&event, &JsValue::from_str("scale")) + .ok() + .and_then(|scale| scale.as_f64()) + .map_or(1.0, |scale| scale as f32); + let new_rotation = Reflect::get(&event, &JsValue::from_str("rotation")) + .ok() + .and_then(|rotation| rotation.as_f64()) + .map_or(0.0, |rotation| rotation.to_radians() as f32); + + let scale_delta = new_scale / runner.input.accumulated_scale; + let rotation_delta = new_rotation - runner.input.accumulated_rotation; + runner.input.accumulated_scale *= scale_delta; + runner.input.accumulated_rotation += rotation_delta; + + let mut should_stop_propagation = true; + let mut should_prevent_default = true; + + if scale_delta != 1.0 { + let zoom_event = egui::Event::Zoom(scale_delta); + + should_stop_propagation &= (runner.web_options.should_stop_propagation)(&zoom_event); + should_prevent_default &= (runner.web_options.should_prevent_default)(&zoom_event); + runner.input.raw.events.push(zoom_event); + } + + if rotation_delta != 0.0 { + let rotate_event = egui::Event::Rotate(rotation_delta); + + should_stop_propagation &= (runner.web_options.should_stop_propagation)(&rotate_event); + should_prevent_default &= (runner.web_options.should_prevent_default)(&rotate_event); + runner.input.raw.events.push(rotate_event); + } + + if scale_delta != 1.0 || rotation_delta != 0.0 { + 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 should_stop_propagation { + event.stop_propagation(); + } + + if should_prevent_default { + // Prevents a simulated ctrl-scroll event for zoom + event.prevent_default(); + } + } +} + fn install_drag_and_drop(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { runner_ref.add_event_listener(target, "dragover", |event: web_sys::DragEvent, runner| { if let Some(data_transfer) = event.data_transfer() { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 206b6bd3a..99e72c820 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -491,9 +491,7 @@ impl State { // Things we completely ignore: WindowEvent::ActivationTokenDone { .. } | WindowEvent::AxisMotion { .. } - | WindowEvent::DoubleTapGesture { .. } - | WindowEvent::RotationGesture { .. } - | WindowEvent::PanGesture { .. } => EventResponse { + | WindowEvent::DoubleTapGesture { .. } => EventResponse { repaint: false, consumed: false, }, @@ -508,6 +506,33 @@ impl State { consumed: self.egui_ctx.wants_pointer_input(), } } + + WindowEvent::RotationGesture { delta, .. } => { + // Positive delta values indicate counterclockwise rotation + // Negative delta values indicate clockwise rotation + // This is opposite of egui's sign convention for angles + self.egui_input + .events + .push(egui::Event::Rotate(-delta.to_radians())); + EventResponse { + repaint: true, + consumed: self.egui_ctx.wants_pointer_input(), + } + } + + WindowEvent::PanGesture { delta, .. } => { + let pixels_per_point = pixels_per_point(&self.egui_ctx, window); + + self.egui_input.events.push(egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Point, + delta: Vec2::new(delta.x, delta.y) / pixels_per_point, + modifiers: self.egui_input.modifiers, + }); + EventResponse { + repaint: true, + consumed: self.egui_ctx.wants_pointer_input(), + } + } } } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 6e6a3deb8..7c23a13bd 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -479,6 +479,9 @@ pub enum Event { /// As a user, check [`crate::InputState::smooth_scroll_delta`] to see if the user did any zooming this frame. Zoom(f32), + /// Rotation in radians this frame, measuring clockwise (e.g. from a rotation gesture). + Rotate(f32), + /// IME Event Ime(ImeEvent), diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index bfa20d6c8..33a08c79a 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -267,6 +267,9 @@ pub struct InputState { /// * `zoom > 1`: pinch spread zoom_factor_delta: f32, + /// Rotation in radians this frame, measuring clockwise (e.g. from a rotation gesture). + rotation_radians: f32, + // ---------------------------------------------- /// Position and size of the egui area. pub screen_rect: Rect, @@ -354,6 +357,7 @@ impl Default for InputState { raw_scroll_delta: Vec2::ZERO, smooth_scroll_delta: Vec2::ZERO, zoom_factor_delta: 1.0, + rotation_radians: 0.0, screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)), pixels_per_point: 1.0, @@ -402,6 +406,7 @@ impl InputState { let mut keys_down = self.keys_down; let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor + let mut rotation_radians = 0.0; let mut raw_scroll_delta = Vec2::ZERO; let mut unprocessed_scroll_delta = self.unprocessed_scroll_delta; @@ -480,6 +485,9 @@ impl InputState { Event::Zoom(factor) => { zoom_factor_delta *= *factor; } + Event::Rotate(radians) => { + rotation_radians += *radians; + } Event::WindowFocused(false) => { // Example: pressing `Cmd+S` brings up a save-dialog (e.g. using rfd), // but we get no key-up event for the `S` key (in winit). @@ -542,6 +550,7 @@ impl InputState { raw_scroll_delta, smooth_scroll_delta, zoom_factor_delta, + rotation_radians, screen_rect, pixels_per_point, @@ -631,6 +640,26 @@ impl InputState { } } + /// Rotation in radians this frame, measuring clockwise (e.g. from a rotation gesture). + #[inline(always)] + pub fn rotation_delta(&self) -> f32 { + self.multi_touch() + .map_or(self.rotation_radians, |touch| touch.rotation_delta) + } + + /// Panning translation in pixels this frame (e.g. from scrolling or a pan gesture) + /// + /// The delta indicates how the **content** should move. + /// + /// A positive X-value indicates the content is being moved right, as when swiping right on a touch-screen or track-pad with natural scrolling. + /// + /// A positive Y-value indicates the content is being moved down, as when swiping down on a touch-screen or track-pad with natural scrolling. + #[inline(always)] + pub fn translation_delta(&self) -> Vec2 { + self.multi_touch() + .map_or(self.smooth_scroll_delta, |touch| touch.translation_delta) + } + /// How long has it been (in seconds) since the use last scrolled? #[inline(always)] pub fn time_since_last_scroll(&self) -> f32 { @@ -1526,6 +1555,7 @@ impl InputState { unprocessed_scroll_delta_for_zoom, raw_scroll_delta, smooth_scroll_delta, + rotation_radians, zoom_factor_delta, screen_rect, @@ -1579,6 +1609,7 @@ impl InputState { "smooth_scroll_delta: {smooth_scroll_delta:?} points" )); ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x")); + ui.label(format!("rotation_radians: {rotation_radians:.3} radians")); ui.label(format!("screen_rect: {screen_rect:?} points")); ui.label(format!( diff --git a/crates/egui_demo_lib/src/demo/multi_touch.rs b/crates/egui_demo_lib/src/demo/multi_touch.rs index 0c2d98202..d83e548bd 100644 --- a/crates/egui_demo_lib/src/demo/multi_touch.rs +++ b/crates/egui_demo_lib/src/demo/multi_touch.rs @@ -1,5 +1,5 @@ use egui::{ - Color32, Frame, Pos2, Rect, Sense, Stroke, Vec2, + Color32, Event, Frame, Pos2, Rect, Sense, Stroke, Vec2, emath::{RectTransform, Rot2}, vec2, }; @@ -30,7 +30,7 @@ impl crate::Demo for MultiTouch { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()) .open(open) - .default_size(vec2(512.0, 512.0)) + .default_size(vec2(544.0, 512.0)) .resizable(true) .show(ctx, |ui| { use crate::View as _; @@ -45,13 +45,31 @@ impl crate::View for MultiTouch { ui.add(crate::egui_github_link_file!()); }); ui.strong( - "This demo only works on devices with multitouch support (e.g. mobiles and tablets).", + "This demo only works on devices with multitouch support (e.g. mobiles, tablets, and trackpads).", ); ui.separator(); ui.label("Try touch gestures Pinch/Stretch, Rotation, and Pressure with 2+ fingers."); + let relative_pointer_gesture = ui.input(|i| { + i.events.iter().any(|event| { + matches!( + event, + Event::MouseWheel { .. } | Event::Zoom { .. } | Event::Rotate { .. } + ) + }) + }); let num_touches = ui.input(|i| i.multi_touch().map_or(0, |mt| mt.num_touches)); - ui.label(format!("Current touches: {num_touches}")); + let num_touches_str = format!("{num_touches}-finger touch"); + ui.label(format!( + "Input source: {}", + if ui.input(|i| i.multi_touch().is_some()) { + num_touches_str.as_str() + } else if relative_pointer_gesture { + "cursor" + } else { + "none" + } + )); let color = if ui.visuals().dark_mode { Color32::WHITE @@ -83,18 +101,18 @@ impl crate::View for MultiTouch { // check for touch input (or the lack thereof) and update zoom and scale factors, plus // color and width: let mut stroke_width = 1.; - if let Some(multi_touch) = ui.ctx().multi_touch() { - // This adjusts the current zoom factor and rotation angle according to the dynamic - // change (for the current frame) of the touch gesture: - self.zoom *= multi_touch.zoom_delta; - self.rotation += multi_touch.rotation_delta; - // the translation we get from `multi_touch` needs to be scaled down to the - // normalized coordinates we use as the basis for painting: - self.translation += to_screen.inverse().scale() * multi_touch.translation_delta; - // touch pressure will make the arrow thicker (not all touch devices support this): - stroke_width += 10. * multi_touch.force; + if ui.input(|i| i.multi_touch().is_some()) || relative_pointer_gesture { + ui.input(|input| { + // This adjusts the current zoom factor, rotation angle, and translation according + // to the dynamic change (for the current frame) of the touch gesture: + self.zoom *= input.zoom_delta(); + self.rotation += input.rotation_delta(); + self.translation += to_screen.inverse().scale() * input.translation_delta(); + // touch pressure will make the arrow thicker (not all touch devices support this): + stroke_width += 10. * input.multi_touch().map_or(0.0, |touch| touch.force); - self.last_touch_time = ui.input(|i| i.time); + self.last_touch_time = input.time; + }); } else { self.slowly_reset(ui); } diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index e9f8a0e08..daa9da35a 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ad5938299b6fefb688ace5775f7906f5992cc119eedb487999cf963a5dcf813 -size 36275 +oid sha256:bf7f0a76424a959ede7afbb0eaf777638038cc6fe208ef710d9d82638d68b4d0 +size 37848 From 2d3ecd349498d9a45a23c41f971b371d27e05ee5 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 11 Sep 2025 17:18:53 +0200 Subject: [PATCH 225/388] Reset wrapping in label tooltip (#7535) * follow up to #7514 That PR changed the tooltip to preserve the wrapping, which made the tooltip kind of useless. With this PR the wrapping is reset for the tooltip. --- crates/egui/src/widgets/label.rs | 8 +++++++- tests/egui_tests/tests/regression_tests.rs | 2 +- .../snapshots/hovering_should_preserve_text_format.png | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index e0f21fd11..86259ab2a 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -281,8 +281,14 @@ impl Widget for Label { if ui.is_rect_visible(response.rect) { if show_tooltip_when_elided && galley.elided { + // Keep the sections and text, but reset everything else (especially wrapping): + let job = crate::text::LayoutJob { + sections: galley.job.sections.clone(), + text: galley.job.text.clone(), + ..crate::text::LayoutJob::default() + }; // Show the full (non-elided) text on hover: - response = response.on_hover_text(galley.job.clone()); + response = response.on_hover_text(job); } let response_color = if interactive { diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs index babfb2320..d02d04aa2 100644 --- a/tests/egui_tests/tests/regression_tests.rs +++ b/tests/egui_tests/tests/regression_tests.rs @@ -18,7 +18,7 @@ fn hovering_should_preserve_text_format() { let mut harness = Harness::builder().with_size((200.0, 70.0)).build_ui(|ui| { ui.add( Label::new( - RichText::new("Long text that should be elided and has lots of styling") + RichText::new("Long text that should be elided and has lots of styling and is long enough to have multiple lines.") .italics() .underline() .color(Color32::LIGHT_BLUE), diff --git a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png index 7db5ec76b..2b3ac7a50 100644 --- a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png +++ b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fdc04ac6f24c20688c846af8759f91db2b3f8a93abcd0c5669c9050b98451ea -size 10152 +oid sha256:cac533a01c65c8eef093efcd4c9036da50f898ea2436612990f4c2365c98ad83 +size 12126 From 226bdc4c5bbb2230fb829e01b3fcb0460e741b34 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 12 Sep 2025 08:18:19 +0200 Subject: [PATCH 226/388] 0.32.3 release: Bump version numbers and update changelog (#7536) --- CHANGELOG.md | 5 ++++ Cargo.lock | 32 ++++++++++++------------ Cargo.toml | 26 +++++++++---------- crates/ecolor/CHANGELOG.md | 4 +++ crates/eframe/CHANGELOG.md | 4 +++ crates/egui-wgpu/CHANGELOG.md | 4 +++ crates/egui-winit/CHANGELOG.md | 4 +++ crates/egui_extras/CHANGELOG.md | 4 +++ crates/egui_glow/CHANGELOG.md | 4 +++ crates/egui_kittest/CHANGELOG.md | 4 +++ crates/epaint/CHANGELOG.md | 4 +++ crates/epaint_default_fonts/CHANGELOG.md | 4 +++ 12 files changed, 70 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8bf57b19..579121cda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ 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.3 - 2025-09-12 +* Preserve text format in truncated label tooltip [#7514](https://github.com/emilk/egui/pull/7514) [#7535](https://github.com/emilk/egui/pull/7535) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix `TextEdit`'s in RTL layouts [#5547](https://github.com/emilk/egui/pull/5547) by [@zakarumych](https://github.com/zakarumych) + + ## 0.32.2 - 2025-09-04 * Fix: `SubMenu` should not display when ui is disabled [#7428](https://github.com/emilk/egui/pull/7428) by [@ozwaldorf](https://github.com/ozwaldorf) * Remove line breaks when pasting into single line TextEdit [#7441](https://github.com/emilk/egui/pull/7441) by [@YgorSouza](https://github.com/YgorSouza) diff --git a/Cargo.lock b/Cargo.lock index eec56131f..38e7364b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1196,7 +1196,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.32.2" +version = "0.32.3" dependencies = [ "bytemuck", "cint", @@ -1208,7 +1208,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.32.2" +version = "0.32.3" dependencies = [ "ahash", "bytemuck", @@ -1247,7 +1247,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.32.2" +version = "0.32.3" dependencies = [ "accesskit", "ahash", @@ -1267,7 +1267,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.32.2" +version = "0.32.3" dependencies = [ "ahash", "bytemuck", @@ -1285,7 +1285,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.32.2" +version = "0.32.3" dependencies = [ "accesskit_winit", "arboard", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.32.2" +version = "0.32.3" dependencies = [ "bytemuck", "chrono", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.32.2" +version = "0.32.3" dependencies = [ "chrono", "criterion", @@ -1350,7 +1350,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.32.2" +version = "0.32.3" dependencies = [ "ahash", "chrono", @@ -1369,7 +1369,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.32.2" +version = "0.32.3" dependencies = [ "bytemuck", "document-features", @@ -1388,7 +1388,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.32.2" +version = "0.32.3" dependencies = [ "dify", "document-features", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.32.2" +version = "0.32.3" dependencies = [ "egui", "egui_extras", @@ -1434,7 +1434,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.32.2" +version = "0.32.3" dependencies = [ "bytemuck", "document-features", @@ -1531,7 +1531,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.32.2" +version = "0.32.3" dependencies = [ "ab_glyph", "ahash", @@ -1553,7 +1553,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.32.2" +version = "0.32.3" [[package]] name = "equivalent" @@ -3239,7 +3239,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.32.2" +version = "0.32.3" dependencies = [ "eframe", "env_logger", @@ -5487,7 +5487,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.32.2" +version = "0.32.3" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index f9e808989..72d2b2a97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.86" -version = "0.32.2" +version = "0.32.3" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.32.2", path = "crates/emath", default-features = false } -ecolor = { version = "0.32.2", path = "crates/ecolor", default-features = false } -epaint = { version = "0.32.2", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.32.2", path = "crates/epaint_default_fonts" } -egui = { version = "0.32.2", path = "crates/egui", default-features = false } -egui-winit = { version = "0.32.2", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.32.2", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.32.2", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.32.2", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.32.2", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.32.2", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.32.2", path = "crates/eframe", default-features = false } +emath = { version = "0.32.3", path = "crates/emath", default-features = false } +ecolor = { version = "0.32.3", path = "crates/ecolor", default-features = false } +epaint = { version = "0.32.3", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.32.3", path = "crates/epaint_default_fonts" } +egui = { version = "0.32.3", path = "crates/egui", default-features = false } +egui-winit = { version = "0.32.3", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.32.3", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.32.3", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.32.3", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.32.3", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.32.3", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.32.3", path = "crates/eframe", default-features = false } accesskit = "0.19.0" accesskit_winit = "0.27" diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index b4c8d2ea1..1c83db637 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,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.3 - 2025-09-12 +Nothing new + + ## 0.32.2 - 2025-09-04 Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 88aea16e6..fec24aa79 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,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.3 - 2025-09-12 +Nothing new + + ## 0.32.2 - 2025-09-04 Nothing new diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 7ecf20997..2b50ce27d 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,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.3 - 2025-09-12 +Nothing new + + ## 0.32.2 - 2025-09-04 Nothing new diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 4a4a26c72..5e06ddedf 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/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.3 - 2025-09-12 +Nothing new + + ## 0.32.2 - 2025-09-04 Nothing new diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index f8693f21d..cdabaffd4 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/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.3 - 2025-09-12 +* Fix deadlock in `FileLoader` and `EhttpLoader` [#7515](https://github.com/emilk/egui/pull/7515) by [@emilk](https://github.com/emilk) + + ## 0.32.2 - 2025-09-04 * Fix memory leak when `forget_image` is called while loading [#7380](https://github.com/emilk/egui/pull/7380) by [@Vanadiae](https://github.com/Vanadiae) * Fix deadlock in `ImageLoader`, `FileLoader`, `EhttpLoader` [#7494](https://github.com/emilk/egui/pull/7494) by [@lucasmerlin](https://github.com/lucasmerlin) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 12fd2f42d..1cd8dee7d 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.32.3 - 2025-09-12 +Nothing new + + ## 0.32.2 - 2025-09-04 * Allow masking widgets in kittest snapshots [#7467](https://github.com/emilk/egui/pull/7467) by [@lucasmerlin](https://github.com/lucasmerlin) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 8d2c1521a..3949eb59d 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/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.3 - 2025-09-12 +* Optimize `Mesh::add_rect_with_uv` [#7511](https://github.com/emilk/egui/pull/7511) by [@valadaptive](https://github.com/valadaptive) + + ## 0.32.2 - 2025-09-04 * Panic mutexes that can't lock for 30 seconds, in debug builds [#7468](https://github.com/emilk/egui/pull/7468) by [@emilk](https://github.com/emilk) * Skip zero-length layout job sections [#7430](https://github.com/emilk/egui/pull/7430) by [@HactarCE](https://github.com/HactarCE) diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index 54c1cca5f..8fe164f66 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.3 - 2025-09-12 +Nothing new + + ## 0.32.2 - 2025-09-04 Nothing new From f2f00ef62aa4ad572623539970cf29bf7d3ff226 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 16 Sep 2025 10:55:58 +0200 Subject: [PATCH 227/388] New Plugin trait (#7385) This adds a new `Plugin` trait and new `input_hook` and `output_hook` plugin fns. Having a `Plugin` trait should make it easier to store state in the plugin and improve discoverability of possible plugin hooks. The old `on_begin_pass` and `on_end_pass` have been ported to use the new plugin trait, should we deprecate them? --- crates/egui/src/context.rs | 160 ++++++------ crates/egui/src/debug_text.rs | 57 ++--- crates/egui/src/drag_and_drop.rs | 67 +++-- crates/egui/src/lib.rs | 4 +- crates/egui/src/plugin.rs | 232 ++++++++++++++++++ .../text_selection/label_text_selection.rs | 75 ++---- 6 files changed, 404 insertions(+), 191 deletions(-) create mode 100644 crates/egui/src/plugin.rs diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index cdc0199ed..4ac8668d4 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -17,31 +17,32 @@ use epaint::{ use crate::{ Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, - ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, + ModifierNames, Modifiers, NumExt as _, Order, Painter, Plugin, 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, - epaint, hit_test, - input_state::{InputState, MultiTouchInfo, PointerEvent}, - interaction, + epaint, + hit_test::WidgetHits, + input_state::{InputState, MultiTouchInfo, PointerEvent, SurrenderFocusOn}, + interaction::InteractionSnapshot, layers::GraphicLayers, load::{self, Bytes, Loaders, SizedTexture}, memory::{Options, Theme}, os::OperatingSystem, output::FullOutput, pass_state::PassState, + plugin, + plugin::TypedPluginHandle, resize, response, scroll_area, util::IdTypeMap, viewport::ViewportClass, }; -use self::{hit_test::WidgetHits, interaction::InteractionSnapshot}; #[cfg(feature = "accesskit")] use crate::IdMap; -use crate::input_state::SurrenderFocusOn; /// Information given to the backend about when it is time to repaint the ui. /// @@ -93,46 +94,6 @@ impl Default for WrappedTextureManager { // ---------------------------------------------------------------------------- -/// Generic event callback. -pub type ContextCallback = Arc; - -#[derive(Clone)] -struct NamedContextCallback { - debug_name: &'static str, - callback: ContextCallback, -} - -/// Callbacks that users can register -#[derive(Clone, Default)] -struct Plugins { - pub on_begin_pass: Vec, - pub on_end_pass: Vec, -} - -impl Plugins { - fn call(ctx: &Context, _cb_name: &str, callbacks: &[NamedContextCallback]) { - profiling::scope!("plugins", _cb_name); - for NamedContextCallback { - debug_name: _name, - callback, - } in callbacks - { - profiling::scope!("plugin", _name); - (callback)(ctx); - } - } - - fn on_begin_pass(&self, ctx: &Context) { - Self::call(ctx, "on_begin_pass", &self.on_begin_pass); - } - - fn on_end_pass(&self, ctx: &Context) { - Self::call(ctx, "on_end_pass", &self.on_end_pass); - } -} - -// ---------------------------------------------------------------------------- - /// Repaint-logic impl ContextImpl { /// This is where we update the repaint logic. @@ -412,7 +373,7 @@ struct ContextImpl { memory: Memory, animation_manager: AnimationManager, - plugins: Plugins, + plugins: plugin::Plugins, /// All viewports share the same texture manager and texture namespace. /// @@ -756,10 +717,12 @@ impl Default for Context { }; let ctx = Self(Arc::new(RwLock::new(ctx_impl))); + ctx.add_plugin(plugin::CallbackPlugin::default()); + // Register built-in plugins: - crate::debug_text::register(&ctx); - crate::text_selection::LabelSelectionState::register(&ctx); - crate::DragAndDrop::register(&ctx); + ctx.add_plugin(crate::debug_text::DebugTextPlugin::default()); + ctx.add_plugin(crate::text_selection::LabelSelectionState::default()); + ctx.add_plugin(crate::DragAndDrop::default()); ctx } @@ -889,13 +852,16 @@ impl Context { /// let full_output = ctx.end_pass(); /// // handle full_output /// ``` - pub fn begin_pass(&self, new_input: RawInput) { + pub fn begin_pass(&self, mut new_input: RawInput) { profiling::function_scope!(); + let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); + plugins.on_input(&mut new_input); + self.write(|ctx| ctx.begin_pass(new_input)); // Plugins run just after the pass starts: - self.read(|ctx| ctx.plugins.clone()).on_begin_pass(self); + plugins.on_begin_pass(self); } /// See [`Self::begin_pass`]. @@ -1885,26 +1851,73 @@ impl Context { impl Context { /// Call the given callback at the start of each pass of each viewport. /// - /// This can be used for egui _plugins_. - /// See [`crate::debug_text`] for an example. - pub fn on_begin_pass(&self, debug_name: &'static str, cb: ContextCallback) { - let named_cb = NamedContextCallback { - debug_name, - callback: cb, - }; - self.write(|ctx| ctx.plugins.on_begin_pass.push(named_cb)); + /// This is a convenience wrapper around [`Self::add_plugin`]. + pub fn on_begin_pass(&self, debug_name: &'static str, cb: plugin::ContextCallback) { + self.with_plugin(|p: &mut crate::plugin::CallbackPlugin| { + p.on_begin_plugins.push((debug_name, cb)); + }); } /// Call the given callback at the end of each pass of each viewport. /// - /// This can be used for egui _plugins_. - /// See [`crate::debug_text`] for an example. - pub fn on_end_pass(&self, debug_name: &'static str, cb: ContextCallback) { - let named_cb = NamedContextCallback { - debug_name, - callback: cb, - }; - self.write(|ctx| ctx.plugins.on_end_pass.push(named_cb)); + /// This is a convenience wrapper around [`Self::add_plugin`]. + pub fn on_end_pass(&self, debug_name: &'static str, cb: plugin::ContextCallback) { + self.with_plugin(|p: &mut crate::plugin::CallbackPlugin| { + p.on_end_plugins.push((debug_name, cb)); + }); + } + + /// Register a [`Plugin`] + /// + /// Plugins are called in the order they are added. + /// + /// A plugin of the same type can only be added once (further calls with the same type will be ignored). + /// This way it's convenient to add plugins in `eframe::run_simple_native`. + pub fn add_plugin(&self, plugin: impl Plugin + 'static) { + let handle = plugin::PluginHandle::new(plugin); + + let added = self.write(|ctx| ctx.plugins.add(handle.clone())); + + if added { + handle.lock().dyn_plugin_mut().setup(self); + } + } + + /// Call the provided closure with the plugin of type `T`, if it was registered. + /// + /// Returns `None` if the plugin was not registered. + pub fn with_plugin(&self, f: impl FnOnce(&mut T) -> R) -> Option { + let plugin = self.read(|ctx| ctx.plugins.get(std::any::TypeId::of::())); + plugin.map(|plugin| f(plugin.lock().typed_plugin_mut())) + } + + /// Get a handle to the plugin of type `T`. + /// + /// ## Panics + /// If the plugin of type `T` was not registered, this will panic. + pub fn plugin(&self) -> TypedPluginHandle { + if let Some(plugin) = self.plugin_opt() { + plugin + } else { + panic!("Plugin of type {:?} not found", std::any::type_name::()); + } + } + + /// Get a handle to the plugin of type `T`, if it was registered. + pub fn plugin_opt(&self) -> Option> { + let plugin = self.read(|ctx| ctx.plugins.get(std::any::TypeId::of::())); + plugin.map(TypedPluginHandle::new) + } + + /// Get a handle to the plugin of type `T`, or insert its default. + pub fn plugin_or_default(&self) -> TypedPluginHandle { + if let Some(plugin) = self.plugin_opt() { + plugin + } else { + let default_plugin = T::default(); + self.add_plugin(default_plugin); + self.plugin() + } } } @@ -2265,12 +2278,15 @@ impl Context { } // Plugins run just before the pass ends. - self.read(|ctx| ctx.plugins.clone()).on_end_pass(self); + let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); + plugins.on_end_pass(self); #[cfg(debug_assertions)] self.debug_painting(); - self.write(|ctx| ctx.end_pass()) + let mut output = self.write(|ctx| ctx.end_pass()); + plugins.on_output(&mut output); + output } /// Call at the end of each frame if you called [`Context::begin_pass`]. @@ -3170,7 +3186,9 @@ impl Context { .show(ui, |ui| { ui.label(format!( "{:#?}", - crate::text_selection::LabelSelectionState::load(ui.ctx()) + *ui.ctx() + .plugin::() + .lock() )); }); diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index c3f938711..ed0bea724 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -1,24 +1,14 @@ //! This is an example of how to create a plugin for egui. //! -//! A plugin usually consist of a struct that holds some state, -//! which is stored using [`Context::data_mut`]. -//! The plugin registers itself onto a specific [`Context`] -//! to get callbacks on certain events ([`Context::on_begin_pass`], [`Context::on_end_pass`]). +//! A plugin is a struct that implements the [`Plugin`] trait and holds some state. +//! The plugin is registered with the [`Context`] using [`Context::add_plugin`] +//! to get callbacks on certain events ([`Plugin::on_begin_pass`], [`Plugin::on_end_pass`]). use crate::{ - Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText, text, + Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Vec2, WidgetText, + text, }; -/// Register this plugin on the given egui context, -/// so that it will be called every pass. -/// -/// This is a built-in plugin in egui, -/// meaning [`Context`] calls this from its `Default` implementation, -/// so this is marked as `pub(crate)`. -pub(crate) fn register(ctx: &Context) { - ctx.on_end_pass("debug_text", std::sync::Arc::new(State::end_pass)); -} - /// Print this text next to the cursor at the end of the pass. /// /// If you call this multiple times, the text will be appended. @@ -38,15 +28,12 @@ pub fn print(ctx: &Context, text: impl Into) { let location = std::panic::Location::caller(); let location = format!("{}:{}", location.file(), location.line()); - ctx.data_mut(|data| { - // We use `Id::NULL` as the id, since we only have one instance of this plugin. - // We use the `temp` version instead of `persisted` since we don't want to - // persist state on disk when the egui app is closed. - let state = data.get_temp_mut_or_default::(Id::NULL); - state.entries.push(Entry { - location, - text: text.into(), - }); + + let plugin = ctx.plugin::(); + let mut state = plugin.lock(); + state.entries.push(Entry { + location, + text: text.into(), }); } @@ -58,24 +45,26 @@ struct Entry { /// A plugin for easily showing debug-text on-screen. /// -/// This is a built-in plugin in egui. +/// This is a built-in plugin in egui, automatically registered during [`Context`] creation. #[derive(Clone, Default)] -struct State { +pub struct DebugTextPlugin { // This gets re-filled every pass. entries: Vec, } -impl State { - fn end_pass(ctx: &Context) { - let state = ctx.data_mut(|data| data.remove_temp::(Id::NULL)); - if let Some(state) = state { - state.paint(ctx); - } +impl Plugin for DebugTextPlugin { + fn debug_name(&self) -> &'static str { + "DebugTextPlugin" } - fn paint(self, ctx: &Context) { - let Self { entries } = self; + fn on_end_pass(&mut self, ctx: &Context) { + let entries = std::mem::take(&mut self.entries); + Self::paint_entries(ctx, entries); + } +} +impl DebugTextPlugin { + fn paint_entries(ctx: &Context, entries: Vec) { if entries.is_empty() { return; } diff --git a/crates/egui/src/drag_and_drop.rs b/crates/egui/src/drag_and_drop.rs index 6caecc24b..21468ed46 100644 --- a/crates/egui/src/drag_and_drop.rs +++ b/crates/egui/src/drag_and_drop.rs @@ -1,44 +1,46 @@ use std::{any::Any, sync::Arc}; -use crate::{Context, CursorIcon, Id}; +use crate::{Context, CursorIcon, Plugin}; -/// Tracking of drag-and-drop payload. +/// Plugin for tracking drag-and-drop payload. /// -/// This is a low-level API. +/// This plugin stores the current drag-and-drop payload internally and handles +/// automatic cleanup when the drag operation ends (via Escape key or mouse release). /// -/// For a higher-level API, see: +/// This is a low-level API. For a higher-level API, see: /// - [`crate::Ui::dnd_drag_source`] /// - [`crate::Ui::dnd_drop_zone`] /// - [`crate::Response::dnd_set_drag_payload`] /// - [`crate::Response::dnd_hover_payload`] /// - [`crate::Response::dnd_release_payload`] /// +/// This is a built-in plugin in egui, automatically registered during [`Context`] creation. +/// /// See [this example](https://github.com/emilk/egui/blob/main/crates/egui_demo_lib/src/demo/drag_and_drop.rs). #[doc(alias = "drag and drop")] #[derive(Clone, Default)] pub struct DragAndDrop { - /// If set, something is currently being dragged + /// The current drag-and-drop payload, if any. Automatically cleared when drag ends. payload: Option>, } -impl DragAndDrop { - pub(crate) fn register(ctx: &Context) { - ctx.on_begin_pass("drag_and_drop_begin_pass", Arc::new(Self::begin_pass)); - ctx.on_end_pass("drag_and_drop_end_pass", Arc::new(Self::end_pass)); +impl Plugin for DragAndDrop { + fn debug_name(&self) -> &'static str { + "DragAndDrop" } /// Interrupt drag-and-drop if the user presses the escape key. /// /// This needs to happen at frame start so we can properly capture the escape key. - fn begin_pass(ctx: &Context) { - let has_any_payload = Self::has_any_payload(ctx); + fn on_begin_pass(&mut self, ctx: &Context) { + let has_any_payload = self.payload.is_some(); if has_any_payload { let abort_dnd_due_to_escape_key = ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape)); if abort_dnd_due_to_escape_key { - Self::clear_payload(ctx); + self.payload = None; } } } @@ -48,14 +50,14 @@ impl DragAndDrop { /// This is a catch-all safety net in case user code doesn't capture the drag payload itself. /// This must happen at end-of-frame such that we don't shadow the mouse release event from user /// code. - fn end_pass(ctx: &Context) { - let has_any_payload = Self::has_any_payload(ctx); + fn on_end_pass(&mut self, ctx: &Context) { + let has_any_payload = self.payload.is_some(); if has_any_payload { let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released()); if abort_dnd_due_to_mouse_release { - Self::clear_payload(ctx); + self.payload = None; } else { // We set the cursor icon only if its default, as the user code might have // explicitly set it already. @@ -67,7 +69,9 @@ impl DragAndDrop { } } } +} +impl DragAndDrop { /// Set a drag-and-drop payload. /// /// This can be read by [`Self::payload`] until the pointer is released. @@ -75,18 +79,12 @@ impl DragAndDrop { where Payload: Any + Send + Sync, { - ctx.data_mut(|data| { - let state = data.get_temp_mut_or_default::(Id::NULL); - state.payload = Some(Arc::new(payload)); - }); + ctx.plugin::().lock().payload = Some(Arc::new(payload)); } /// Clears the payload, setting it to `None`. pub fn clear_payload(ctx: &Context) { - ctx.data_mut(|data| { - let state = data.get_temp_mut_or_default::(Id::NULL); - state.payload = None; - }); + ctx.plugin::().lock().payload = None; } /// Retrieve the payload, if any. @@ -99,11 +97,13 @@ impl DragAndDrop { where Payload: Any + Send + Sync, { - ctx.data(|data| { - let state = data.get_temp::(Id::NULL)?; - let payload = state.payload?; - payload.downcast().ok() - }) + ctx.plugin::() + .lock() + .payload + .as_ref()? + .clone() + .downcast() + .ok() } /// Retrieve and clear the payload, if any. @@ -116,11 +116,7 @@ impl DragAndDrop { where Payload: Any + Send + Sync, { - ctx.data_mut(|data| { - let state = data.get_temp_mut_or_default::(Id::NULL); - let payload = state.payload.take()?; - payload.downcast().ok() - }) + ctx.plugin::().lock().payload.take()?.downcast().ok() } /// Are we carrying a payload of the given type? @@ -139,9 +135,6 @@ impl DragAndDrop { /// Returns `true` both during a drag and on the frame the pointer is released /// (if there is a payload). pub fn has_any_payload(ctx: &Context) -> bool { - ctx.data(|data| { - let state = data.get_temp::(Id::NULL); - state.is_some_and(|state| state.payload.is_some()) - }) + ctx.plugin::().lock().payload.is_some() } } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index f8e9308a5..8b87866d0 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -406,6 +406,7 @@ #![allow(clippy::manual_range_contains)] mod animation_manager; +mod atomics; pub mod cache; pub mod containers; mod context; @@ -429,6 +430,7 @@ pub mod os; mod painter; mod pass_state; pub(crate) mod placer; +mod plugin; pub mod response; mod sense; pub mod style; @@ -442,7 +444,6 @@ mod widget_rect; pub mod widget_text; pub mod widgets; -mod atomics; #[cfg(feature = "callstack")] #[cfg(debug_assertions)] mod callstack; @@ -501,6 +502,7 @@ pub use self::{ load::SizeHint, memory::{FocusDirection, Memory, Options, Theme, ThemePreference}, painter::Painter, + plugin::Plugin, response::{InnerResponse, Response}, sense::Sense, style::{FontSelection, Spacing, Style, TextStyle, Visuals}, diff --git a/crates/egui/src/plugin.rs b/crates/egui/src/plugin.rs new file mode 100644 index 000000000..bebcf892e --- /dev/null +++ b/crates/egui/src/plugin.rs @@ -0,0 +1,232 @@ +use crate::{Context, FullOutput, RawInput}; +use ahash::HashMap; +use epaint::mutex::{Mutex, MutexGuard}; +use std::sync::Arc; + +/// A plugin to extend egui. +/// +/// Add plugins via [`Context::add_plugin`]. +/// +/// Plugins should not hold a reference to the [`Context`], since this would create a cycle +/// (which would prevent the [`Context`] from being dropped). +#[expect(unused_variables)] +pub trait Plugin: Send + Sync + std::any::Any + 'static { + /// Plugin name. + /// + /// Used when profiling. + fn debug_name(&self) -> &'static str; + + /// Called once, when the plugin is registered. + /// + /// Useful to e.g. register image loaders. + fn setup(&mut self, ctx: &Context) {} + + /// Called at the start of each pass. + /// + /// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::SidePanel`]. + fn on_begin_pass(&mut self, ctx: &Context) {} + + /// Called at the end of each pass. + /// + /// Can be used to show ui, e.g. a [`crate::Window`]. + fn on_end_pass(&mut self, ctx: &Context) {} + + /// Called just before the input is processed. + /// + /// Useful to inspect or modify the input. + /// Since this is called outside a pass, don't show ui here. + fn input_hook(&mut self, input: &mut RawInput) {} + + /// Called just before the output is passed to the backend. + /// + /// Useful to inspect or modify the output. + /// Since this is called outside a pass, don't show ui here. + fn output_hook(&mut self, output: &mut FullOutput) {} +} + +pub(crate) struct PluginHandle { + plugin: Box, +} + +pub struct TypedPluginHandle { + handle: Arc>, + _type: std::marker::PhantomData

, +} + +impl TypedPluginHandle

{ + pub(crate) fn new(handle: Arc>) -> Self { + Self { + handle, + _type: std::marker::PhantomData, + } + } + + pub fn lock(&self) -> TypedPluginGuard<'_, P> { + TypedPluginGuard { + guard: self.handle.lock(), + _type: std::marker::PhantomData, + } + } +} + +pub struct TypedPluginGuard<'a, P: Plugin> { + guard: MutexGuard<'a, PluginHandle>, + _type: std::marker::PhantomData

, +} + +impl TypedPluginGuard<'_, P> {} + +impl std::ops::Deref for TypedPluginGuard<'_, P> { + type Target = P; + + fn deref(&self) -> &Self::Target { + self.guard.typed_plugin() + } +} + +impl std::ops::DerefMut for TypedPluginGuard<'_, P> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.guard.typed_plugin_mut() + } +} + +impl PluginHandle { + pub fn new(plugin: P) -> Arc> { + Arc::new(Mutex::new(Self { + plugin: Box::new(plugin), + })) + } + + fn plugin_type_id(&self) -> std::any::TypeId { + (*self.plugin).type_id() + } + + pub fn dyn_plugin_mut(&mut self) -> &mut dyn Plugin { + &mut *self.plugin + } + + fn typed_plugin(&self) -> &P { + (&*self.plugin as &dyn std::any::Any) + .downcast_ref::

() + .expect("PluginHandle: plugin is not of the expected type") + } + + pub fn typed_plugin_mut(&mut self) -> &mut P { + (&mut *self.plugin as &mut dyn std::any::Any) + .downcast_mut::

() + .expect("PluginHandle: plugin is not of the expected type") + } +} + +/// User-registered plugins. +#[derive(Clone, Default)] +pub(crate) struct Plugins { + plugins: HashMap>>, + plugins_ordered: PluginsOrdered, +} + +#[derive(Clone, Default)] +pub(crate) struct PluginsOrdered(Vec>>); + +impl PluginsOrdered { + fn for_each_dyn(&self, mut f: F) + where + F: FnMut(&mut dyn Plugin), + { + for plugin in &self.0 { + let mut plugin = plugin.lock(); + profiling::scope!("plugin", plugin.dyn_plugin_mut().debug_name()); + f(plugin.dyn_plugin_mut()); + } + } + + pub fn on_begin_pass(&self, ctx: &Context) { + profiling::scope!("plugins", "on_begin_pass"); + self.for_each_dyn(|p| { + p.on_begin_pass(ctx); + }); + } + + pub fn on_end_pass(&self, ctx: &Context) { + profiling::scope!("plugins", "on_end_pass"); + self.for_each_dyn(|p| { + p.on_end_pass(ctx); + }); + } + + pub fn on_input(&self, input: &mut RawInput) { + profiling::scope!("plugins", "on_input"); + self.for_each_dyn(|plugin| { + plugin.input_hook(input); + }); + } + + pub fn on_output(&self, output: &mut FullOutput) { + profiling::scope!("plugins", "on_output"); + self.for_each_dyn(|plugin| { + plugin.output_hook(output); + }); + } +} + +impl Plugins { + pub fn ordered_plugins(&self) -> PluginsOrdered { + self.plugins_ordered.clone() + } + + /// Remember to call [`Plugin::setup`] on the plugin after adding it. + /// + /// Will not add the plugin if a plugin of the same type already exists. + /// Returns `false` if the plugin was not added, `true` if it was added. + pub fn add(&mut self, handle: Arc>) -> bool { + profiling::scope!("plugins", "add"); + + let type_id = handle.lock().plugin_type_id(); + + if self.plugins.contains_key(&type_id) { + return false; + } + + self.plugins.insert(type_id, handle.clone()); + self.plugins_ordered.0.push(handle); + + true + } + + pub fn get(&self, type_id: std::any::TypeId) -> Option>> { + self.plugins.get(&type_id).cloned() + } +} + +/// Generic event callback. +pub type ContextCallback = Arc; + +#[derive(Default)] +pub(crate) struct CallbackPlugin { + pub on_begin_plugins: Vec<(&'static str, ContextCallback)>, + pub on_end_plugins: Vec<(&'static str, ContextCallback)>, +} + +impl Plugin for CallbackPlugin { + fn debug_name(&self) -> &'static str { + "CallbackPlugins" + } + + fn on_begin_pass(&mut self, ctx: &Context) { + profiling::function_scope!(); + + for (_debug_name, cb) in &self.on_begin_plugins { + profiling::scope!(*_debug_name); + (cb)(ctx); + } + } + + fn on_end_pass(&mut self, ctx: &Context) { + profiling::function_scope!(); + + for (_debug_name, cb) in &self.on_end_plugins { + profiling::scope!(*_debug_name); + (cb)(ctx); + } + } +} diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index 8297bc429..da248a0f5 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use emath::TSTransform; use crate::{ - Context, CursorIcon, Event, Galley, Id, LayerId, Pos2, Rect, Response, Ui, layers::ShapeIdx, - text::CCursor, text_selection::CCursorRange, + Context, CursorIcon, Event, Galley, Id, LayerId, Plugin, Pos2, Rect, Response, Ui, + layers::ShapeIdx, text::CCursor, text_selection::CCursorRange, }; use super::{ @@ -123,65 +123,45 @@ impl Default for LabelSelectionState { } } -impl LabelSelectionState { - pub(crate) fn register(ctx: &Context) { - ctx.on_begin_pass("LabelSelectionState", std::sync::Arc::new(Self::begin_pass)); - ctx.on_end_pass("LabelSelectionState", std::sync::Arc::new(Self::end_pass)); +impl Plugin for LabelSelectionState { + fn debug_name(&self) -> &'static str { + "LabelSelectionState" } - pub fn load(ctx: &Context) -> Self { - let id = Id::new(ctx.viewport_id()); - ctx.data(|data| data.get_temp::(id)) - .unwrap_or_default() - } - - pub fn store(self, ctx: &Context) { - let id = Id::new(ctx.viewport_id()); - ctx.data_mut(|data| { - data.insert_temp(id, self); - }); - } - - fn begin_pass(ctx: &Context) { - let mut state = Self::load(ctx); - + fn on_begin_pass(&mut self, ctx: &Context) { if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) { // Maybe a new selection is about to begin, but the old one is over: // state.selection = None; // TODO(emilk): this makes sense, but doesn't work as expected. } - state.selection_bbox_last_frame = state.selection_bbox_this_frame; - state.selection_bbox_this_frame = Rect::NOTHING; + self.selection_bbox_last_frame = self.selection_bbox_this_frame; + self.selection_bbox_this_frame = Rect::NOTHING; - state.any_hovered = false; - state.has_reached_primary = false; - state.has_reached_secondary = false; - state.text_to_copy.clear(); - state.last_copied_galley_rect = None; - state.painted_selections.clear(); - - state.store(ctx); + self.any_hovered = false; + self.has_reached_primary = false; + self.has_reached_secondary = false; + self.text_to_copy.clear(); + self.last_copied_galley_rect = None; + self.painted_selections.clear(); } - fn end_pass(ctx: &Context) { - let mut state = Self::load(ctx); - - if state.is_dragging { + fn on_end_pass(&mut self, ctx: &Context) { + if self.is_dragging { ctx.set_cursor_icon(CursorIcon::Text); } - if !state.has_reached_primary || !state.has_reached_secondary { + if !self.has_reached_primary || !self.has_reached_secondary { // We didn't see both cursors this frame, // maybe because they are outside the visible area (scrolling), // or one disappeared. In either case we will have horrible glitches, so let's just deselect. - let prev_selection = state.selection.take(); + let prev_selection = self.selection.take(); if let Some(selection) = prev_selection { // This was the first frame of glitch, so hide the // glitching by removing all painted selections: ctx.graphics_mut(|layers| { if let Some(list) = layers.get_mut(selection.layer_id) { - for (shape_idx, row_selections) in state.painted_selections.drain(..) { + for (shape_idx, row_selections) in self.painted_selections.drain(..) { list.mutate_shape(shape_idx, |shape| { if let epaint::Shape::Text(text_shape) = &mut shape.shape { let galley = Arc::make_mut(&mut text_shape.galley); @@ -211,25 +191,25 @@ impl LabelSelectionState { } let pressed_escape = ctx.input(|i| i.key_pressed(crate::Key::Escape)); - let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !state.any_hovered; + let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !self.any_hovered; let delected_everything = pressed_escape || clicked_something_else; if delected_everything { - state.selection = None; + self.selection = None; } if ctx.input(|i| i.pointer.any_released()) { - state.is_dragging = false; + self.is_dragging = false; } - let text_to_copy = std::mem::take(&mut state.text_to_copy); + let text_to_copy = std::mem::take(&mut self.text_to_copy); if !text_to_copy.is_empty() { ctx.copy_text(text_to_copy); } - - state.store(ctx); } +} +impl LabelSelectionState { pub fn has_selection(&self) -> bool { self.selection.is_some() } @@ -297,7 +277,8 @@ impl LabelSelectionState { fallback_color: epaint::Color32, underline: epaint::Stroke, ) { - let mut state = Self::load(ui.ctx()); + let plugin = ui.ctx().plugin::(); + let mut state = plugin.lock(); let new_vertex_indices = state.on_label(ui, response, galley_pos, &mut galley); let shape_idx = ui.painter().add( @@ -309,8 +290,6 @@ impl LabelSelectionState { .painted_selections .push((shape_idx, new_vertex_indices)); } - - state.store(ui.ctx()); } fn cursor_for( From 603dba29e610542187179342f8effe536e6189d7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 16 Sep 2025 13:30:28 +0200 Subject: [PATCH 228/388] Add snapshot test for text spacing/kerning (#7545) --- crates/egui_demo_lib/tests/misc.rs | 26 +++++++++++++++++++ .../image_blending/image_dark_x1.png | 3 +++ .../image_blending/image_dark_x2.png | 3 +++ .../image_blending/image_light_x1.png | 3 +++ .../image_blending/image_light_x2.png | 3 +++ crates/epaint/src/text/font.rs | 2 +- 6 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 crates/egui_demo_lib/tests/misc.rs create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png diff --git a/crates/egui_demo_lib/tests/misc.rs b/crates/egui_demo_lib/tests/misc.rs new file mode 100644 index 000000000..5a8ae2c29 --- /dev/null +++ b/crates/egui_demo_lib/tests/misc.rs @@ -0,0 +1,26 @@ +use egui_kittest::Harness; + +#[test] +fn test_kerning() { + for pixels_per_point in [1.0, 2.0] { + for theme in [egui::Theme::Dark, egui::Theme::Light] { + let mut harness = Harness::builder() + .with_pixels_per_point(pixels_per_point) + .with_theme(theme) + .build_ui(|ui| { + ui.label("Thin spaces: −123 456 789"); + ui.label("Ligature: fi :)"); + ui.label("\ttabbed"); + }); + harness.run(); + harness.fit_contents(); + harness.snapshot(format!( + "image_blending/image_{theme}_x{pixels_per_point}", + theme = match theme { + egui::Theme::Dark => "dark", + egui::Theme::Light => "light", + } + )); + } + } +} diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png new file mode 100644 index 000000000..069d48198 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b619e8dbedbfc017513111dc26144d795ce97352631ae561c1c336c3e9e0fd4 +size 5557 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png new file mode 100644 index 000000000..020f9e370 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3827cbd75a015ab9d03d9f47ba40fcadb71a2ba3a312d0892ae22a8e379103bc +size 12539 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png new file mode 100644 index 000000000..f248a753a --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afd019fc23aa4b8a899e2df92138b2b3e69b7cb1d20e038a5e841c84e9095fe1 +size 5740 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png new file mode 100644 index 000000000..f5024f7a5 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6129567eaf7d77c6656d9fe6984f1667b0817099492a24f6622da0f1d636e0e8 +size 13646 diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 372389188..c452eb4bc 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -259,7 +259,7 @@ impl FontImpl { if let Some(space) = self.glyph_info(' ') { let em = self.ab_glyph_font.units_per_em().unwrap_or(1.0); - let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); + let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); // TODO(emilk): make configurable let glyph_info = GlyphInfo { advance_width_unscaled: advance_width.into(), ..space From 6ac155c5cd3ee9d194579edc964c5659dfe70ab0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 17 Sep 2025 08:12:50 +0200 Subject: [PATCH 229/388] eframe web: prevent default action on command-comma (#7547) --- crates/eframe/src/web/events.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index dff1463c4..4f09958dc 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -227,9 +227,10 @@ fn should_prevent_default_for_key( // Prevent cmd/ctrl plus these keys from triggering the default browser action: let keys = [ - egui::Key::O, // open - egui::Key::P, // print (cmd-P is common for command palette) - egui::Key::S, // save + egui::Key::Comma, // cmd-, opens options on macOS, which egui apps may wanna "steal" + egui::Key::O, // open + egui::Key::P, // print (cmd-P is common for command palette) + egui::Key::S, // save ]; for key in keys { if egui_key == key && (modifiers.ctrl || modifiers.command || modifiers.mac_cmd) { From c97c065a575ec6e657bb42872890a00d0fb391c1 Mon Sep 17 00:00:00 2001 From: Kumpelinus Date: Sun, 21 Sep 2025 20:42:00 +0200 Subject: [PATCH 230/388] Update wgpu to 26 and wasm-bindgen to 0.2.100 (#7540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: TÖRÖK Attila Co-authored-by: Andreas Reich --- .github/workflows/rust.yml | 2 +- Cargo.lock | 191 +++++++++++----------- Cargo.toml | 2 +- crates/eframe/src/web/web_painter_wgpu.rs | 1 + crates/egui-wgpu/src/capture.rs | 1 + crates/egui-wgpu/src/setup.rs | 1 + crates/egui-wgpu/src/winit.rs | 1 + crates/egui_demo_app/Cargo.toml | 2 +- crates/egui_kittest/src/wgpu.rs | 3 +- deny.toml | 23 +-- scripts/setup_web.sh | 4 +- 11 files changed, 117 insertions(+), 114 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d6abc2d45..2c456b60b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -105,7 +105,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.97" + version: "0.2.100" - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.lock b/Cargo.lock index 38e7364b1..b755441cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,7 +134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "once_cell", "serde", "version_check", @@ -166,7 +166,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "thiserror 1.0.66", ] @@ -909,6 +909,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -922,8 +932,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "foreign-types", "libc", ] @@ -935,7 +945,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.1", "libc", ] @@ -1826,9 +1847,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1895,7 +1916,7 @@ dependencies = [ "bitflags 2.9.0", "cfg_aliases", "cgl", - "core-foundation", + "core-foundation 0.9.4", "dispatch", "glutin_egl_sys", "glutin_glx_sys", @@ -1985,9 +2006,9 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.0", "gpu-descriptor-types", @@ -2023,12 +2044,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hello_android" version = "0.1.0" @@ -2359,9 +2374,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -2549,13 +2564,13 @@ dependencies = [ [[package]] name = "metal" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ "bitflags 2.9.0", "block", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types", "log", "objc", @@ -2624,25 +2639,26 @@ dependencies = [ [[package]] name = "naga" -version = "25.0.1" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" dependencies = [ "arrayvec", "bit-set 0.8.0", "bitflags 2.9.0", + "cfg-if", "cfg_aliases", "codespan-reporting", "half", "hashbrown", "hexf-parse", "indexmap", + "libm", "log", "num-traits", "once_cell", "rustc-hash", "spirv", - "strum", "thiserror 2.0.11", "unicode-ident", ] @@ -2656,7 +2672,7 @@ dependencies = [ "bitflags 2.9.0", "jni-sys", "log", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "raw-window-handle", "thiserror 1.0.66", @@ -2668,15 +2684,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -3251,6 +3258,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.3" @@ -3489,7 +3505,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.11", ] @@ -3584,7 +3600,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -3969,28 +3985,6 @@ dependencies = [ "float-cmp", ] -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" @@ -4528,24 +4522,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -4566,9 +4560,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4576,9 +4570,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -4589,9 +4583,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wayland-backend" @@ -4704,9 +4701,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -4724,18 +4721,16 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e" +checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" dependencies = [ - "block2 0.5.1", - "core-foundation", - "home", + "core-foundation 0.10.1", "jni", "log", "ndk-context", - "objc2 0.5.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-foundation 0.3.0", "url", "web-sys", ] @@ -4757,12 +4752,13 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "25.0.0" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6049eb2014a0e0d8689f9b787605dd71d5bbfdc74095ead499f3cff705c229" +checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" dependencies = [ "arrayvec", "bitflags 2.9.0", + "cfg-if", "cfg_aliases", "document-features", "hashbrown", @@ -4785,9 +4781,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "25.0.1" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19813e647da7aa3cdaa84f5846e2c64114970ea7c86b1e6aae8be08091f4bdc" +checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9" dependencies = [ "arrayvec", "bit-set 0.8.0", @@ -4817,45 +4813,45 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-wasm" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca8809ad123f6c7f2c5e01a2c7117c4fdfd02f70bd422ee2533f69dfa98756c" +checksum = "c03b9f9e1a50686d315fc6debe4980cc45cd37b0e919351917df494e8fdc8885" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "25.0.1" +version = "26.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7c4a1dc42ff14c23c9b11ebf1ee85cde661a9b1cf0392f79c1faca5bc559fb" +checksum = "7df2c64ac282a91ad7662c90bc4a77d4a2135bc0b2a2da5a4d4e267afc034b9e" dependencies = [ "android_system_properties", "arrayvec", @@ -4866,7 +4862,7 @@ dependencies = [ "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types", + "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -4880,11 +4876,12 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys 0.5.0+25.2.9519653", + "ndk-sys", "objc", "ordered-float", "parking_lot", "portable-atomic", + "portable-atomic-util", "profiling", "range-alloc", "raw-window-handle", @@ -4900,9 +4897,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" dependencies = [ "bitflags 2.9.0", "bytemuck", @@ -5345,7 +5342,7 @@ dependencies = [ "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", diff --git a/Cargo.toml b/Cargo.toml index 72d2b2a97..d99430f72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = "0.3.73" web-time = "1.1.0" # Timekeeping for native and web -wgpu = { version = "25.0.0", default-features = false } +wgpu = { version = "26.0.1", default-features = false } windows-sys = "0.60" winit = { version = "0.30.12", default-features = false } diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 7b83faa0b..645f62507 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -236,6 +236,7 @@ impl WebPainter for WebPainterWgpu { }), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { wgpu::RenderPassDepthStencilAttachment { diff --git a/crates/egui-wgpu/src/capture.rs b/crates/egui-wgpu/src/capture.rs index d47a828b4..917740061 100644 --- a/crates/egui-wgpu/src/capture.rs +++ b/crates/egui-wgpu/src/capture.rs @@ -160,6 +160,7 @@ impl CaptureState { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, occlusion_query_set: None, diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index cae8c4807..9e0adb9a6 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -162,6 +162,7 @@ impl Default for WgpuSetupCreateNew { .unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL), flags: wgpu::InstanceFlags::from_build_config().with_env(), backend_options: wgpu::BackendOptions::from_env_or_default(), + memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(), }, power_preference: wgpu::PowerPreference::from_env() diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 800d67c31..02f1851f2 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -471,6 +471,7 @@ impl Painter { }), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| { wgpu::RenderPassDepthStencilAttachment { diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index f4c6e2864..ff441af8b 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -92,7 +92,7 @@ rfd = { version = "0.15.3", optional = true } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "=0.2.97" +wasm-bindgen = "=0.2.100" wasm-bindgen-futures.workspace = true web-sys.workspace = true diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index e0fef2901..85ccf79bd 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -190,6 +190,7 @@ impl crate::TestRenderer for WgpuTestRenderer { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], ..Default::default() }) @@ -205,7 +206,7 @@ impl crate::TestRenderer for WgpuTestRenderer { self.render_state .device .poll(wgpu::PollType::Wait) - .map_err(|e| format!("{e}"))?; + .map_err(|e| format!("{e:?}"))?; Ok(texture_to_image( &self.render_state.device, diff --git a/deny.toml b/deny.toml index 4f973ed60..8bf0644ad 100644 --- a/deny.toml +++ b/deny.toml @@ -45,19 +45,20 @@ deny = [ ] skip = [ - { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 - { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 - { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … - { name = "ndk-sys" }, # old version via wgpu, winit uses newer version - { name = "quick-xml" }, # old version via wayland-scanner - { name = "redox_syscall" }, # old version via winit - { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x - { name = "thiserror-impl" }, # same as above - { name = "windows-sys" }, # mostly hopeless to avoid + { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 + { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 + { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "quick-xml" }, # old version via wayland-scanner + { name = "redox_syscall" }, # old version via winit + { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x + { name = "thiserror-impl" }, # same as above + { name = "windows-sys" }, # mostly hopeless to avoid + { name = "core-foundation" }, # version conflict between winit and wgpu ecosystems + { name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems ] skip-tree = [ - { name = "rfd" }, # example dependency - { name = "windows" }, # the ecosystem is currently transitioning from 0.58 to 0.61 + { name = "rfd" }, # example dependency + { name = "windows" }, # the ecosystem is currently transitioning from 0.58 to 0.61 ] diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index a49f82059..0193bf3e5 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -9,6 +9,6 @@ set -x rustup target add wasm32-unknown-unknown # For generating JS bindings: -if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.97'; then - cargo install --force --quiet wasm-bindgen-cli --version 0.2.97 +if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.100'; then + cargo install --force --quiet wasm-bindgen-cli --version 0.2.100 fi From 9150b9342d5ced3d5fe7cdd5db415c836dc4a866 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 23 Sep 2025 10:07:18 +0200 Subject: [PATCH 231/388] Make individual egui_extras image loaders public (#7551) This was initially a PR to add kitdiff, but this now lives in it's own crate: https://github.com/rerun-io/kitdiff I needed to make the image loaders public, this way it's possible to compose image loaders together (which allowed me to create a image diff loader that uses two other image loaders). But you can't use the `ctx.try_load_image` since that would deadlock, so you have to store a reference to the other loader in the wrapping loader. --- crates/egui/src/load.rs | 12 +++++++++++- crates/egui_extras/src/lib.rs | 2 +- crates/egui_extras/src/loaders.rs | 16 ++++++++-------- .../loaders/{ehttp_loader.rs => http_loader.rs} | 0 4 files changed, 20 insertions(+), 10 deletions(-) rename crates/egui_extras/src/loaders/{ehttp_loader.rs => http_loader.rs} (100%) diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 43aa1785e..1c74e5041 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -387,7 +387,7 @@ pub type ImageLoadResult = Result; /// An `ImageLoader` decodes raw bytes into a [`ColorImage`]. /// /// Implementations are expected to cache at least each `URI`. -pub trait ImageLoader { +pub trait ImageLoader: std::any::Any { /// Unique ID of this loader. /// /// To reduce the chance of collisions, include `module_path!()` as part of this ID. @@ -517,6 +517,16 @@ impl TexturePoll { Self::Ready { texture } => Some(texture.id), } } + + #[inline] + pub fn is_pending(&self) -> bool { + matches!(self, Self::Pending { .. }) + } + + #[inline] + pub fn is_ready(&self) -> bool { + matches!(self, Self::Ready { .. }) + } } pub type TextureLoadResult = Result; diff --git a/crates/egui_extras/src/lib.rs b/crates/egui_extras/src/lib.rs index f70ff9ff1..16354c440 100644 --- a/crates/egui_extras/src/lib.rs +++ b/crates/egui_extras/src/lib.rs @@ -17,7 +17,7 @@ pub mod syntax_highlighting; #[doc(hidden)] pub mod image; mod layout; -mod loaders; +pub mod loaders; mod sizing; mod strip; mod table; diff --git a/crates/egui_extras/src/loaders.rs b/crates/egui_extras/src/loaders.rs index 03b1abfc9..f73604896 100644 --- a/crates/egui_extras/src/loaders.rs +++ b/crates/egui_extras/src/loaders.rs @@ -63,9 +63,9 @@ pub fn install_image_loaders(ctx: &egui::Context) { } #[cfg(feature = "http")] - if !ctx.is_loader_installed(self::ehttp_loader::EhttpLoader::ID) { + if !ctx.is_loader_installed(self::http_loader::EhttpLoader::ID) { ctx.add_bytes_loader(std::sync::Arc::new( - self::ehttp_loader::EhttpLoader::default(), + self::http_loader::EhttpLoader::default(), )); log::trace!("installed EhttpLoader"); } @@ -108,16 +108,16 @@ pub fn install_image_loaders(ctx: &egui::Context) { } #[cfg(not(target_arch = "wasm32"))] -mod file_loader; +pub mod file_loader; #[cfg(feature = "http")] -mod ehttp_loader; +pub mod http_loader; #[cfg(feature = "gif")] -mod gif_loader; +pub mod gif_loader; #[cfg(feature = "image")] -mod image_loader; +pub mod image_loader; #[cfg(feature = "svg")] -mod svg_loader; +pub mod svg_loader; #[cfg(feature = "webp")] -mod webp_loader; +pub mod webp_loader; diff --git a/crates/egui_extras/src/loaders/ehttp_loader.rs b/crates/egui_extras/src/loaders/http_loader.rs similarity index 100% rename from crates/egui_extras/src/loaders/ehttp_loader.rs rename to crates/egui_extras/src/loaders/http_loader.rs From 48d903d8797d0869b5d2b43a22346dede7d471d2 Mon Sep 17 00:00:00 2001 From: Gijs de Jong <14833076+oxkitsune@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:03:30 +0200 Subject: [PATCH 232/388] Include popups and tooltips in `Harness::fit_contents` (#7556) This makes `Harness::fit_contents` also use popups and tooltips to compute the size of the contents. --- crates/egui_kittest/src/lib.rs | 29 +++++++++++++++++-- .../tests/snapshots/test_tooltip_hidden.png | 3 ++ .../tests/snapshots/test_tooltip_shown.png | 3 ++ crates/egui_kittest/tests/tests.rs | 22 ++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png create mode 100644 crates/egui_kittest/tests/snapshots/test_tooltip_shown.png diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 0f99da170..60d689c18 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -276,14 +276,39 @@ impl<'a, State> Harness<'a, State> { self.output = output; } + /// Calculate the rect that includes all popups and tooltips. + fn compute_total_rect_with_popups(&self) -> Option { + // Start with the standard response rect + let mut used = if let Some(response) = self.response.as_ref() { + response.rect + } else { + return None; + }; + + // Add all visible areas from other orders (popups, tooltips, etc.) + self.ctx.memory(|mem| { + mem.areas() + .visible_layer_ids() + .into_iter() + .filter(|layer_id| layer_id.order != egui::Order::Background) + .filter_map(|layer_id| mem.area_rect(layer_id.id)) + .for_each(|area_rect| used |= area_rect); + }); + + Some(used) + } + /// Resize the test harness to fit the contents. This only works when creating the Harness via /// [`Harness::new_ui`] / [`Harness::new_ui_state`] or /// [`HarnessBuilder::build_ui`] / [`HarnessBuilder::build_ui_state`]. pub fn fit_contents(&mut self) { self._step(true); - if let Some(response) = &self.response { - self.set_size(response.rect.size()); + + // Calculate size including all content (main UI + popups + tooltips) + if let Some(rect) = self.compute_total_rect_with_popups() { + self.set_size(rect.size()); } + self.run_ok(); } diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png b/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png new file mode 100644 index 000000000..c25ae0367 --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:025942c144891b8862bf931385824e0484e60f4e7766f5d4401511c72ff20756 +size 2975 diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png new file mode 100644 index 000000000..8ff6bba67 --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c267530452adb4f1ed1440df476d576ef4c2d96e6c58068bb57fed4615f5e113 +size 4453 diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index e18e21761..04f02ba87 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -16,6 +16,28 @@ fn test_shrink() { harness.snapshot("test_shrink"); } +#[test] +fn test_tooltip() { + let mut harness = Harness::new_ui(|ui| { + ui.label("Hello, world!"); + ui.separator(); + ui.label("This is a test") + .on_hover_text("This\nis\na\nvery\ntall\ntooltip!"); + }); + + harness.fit_contents(); + + #[cfg(all(feature = "snapshot", feature = "wgpu"))] + harness.snapshot("test_tooltip_hidden"); + + harness.get_by_label("This is a test").hover(); + harness.run_ok(); + harness.fit_contents(); + + #[cfg(all(feature = "snapshot", feature = "wgpu"))] + harness.snapshot("test_tooltip_shown"); +} + #[test] fn test_modifiers() { #[derive(Default)] From b36a259d47f56f0303d0944c4e2e8af41f0f9dd1 Mon Sep 17 00:00:00 2001 From: Fangdun Tsai Date: Tue, 23 Sep 2025 17:42:59 +0800 Subject: [PATCH 233/388] Update accesskit to 0.21.0 (#7550) --- Cargo.lock | 31 +++++++++++++++---------------- Cargo.toml | 6 +++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b755441cc..90690bf9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25ae84c0260bdf5df07796d7cc4882460de26a2b406ec0e6c42461a723b271b" +checksum = "9c0690ad6e6f9597b8439bd3c95e8c6df5cd043afd950c6d68f3b37df641e27c" dependencies = [ "enumn", "serde", @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "accesskit_atspi_common" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bd41de2e54451a8ca0dd95ebf45b54d349d29ebceb7f20be264eee14e3d477" +checksum = "8fb511e093896d3cae0efba40322087dff59ea322308a3e6edf70f28d22f2607" dependencies = [ "accesskit", "accesskit_consumer", @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bfae7c152994a31dc7d99b8eeac7784a919f71d1b306f4b83217e110fd3824c" +checksum = "ec27574c1baeb7747c802a194566b46b602461e81dc4957949580ea8da695038" dependencies = [ "accesskit", "hashbrown", @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "accesskit_macos" -version = "0.20.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692dd318ff8a7a0ffda67271c4bd10cf32249656f4e49390db0b26ca92b095f2" +checksum = "bf962bfd305aed21133d06128ab3f4a6412031a5b8505534d55af869788af272" dependencies = [ "accesskit", "accesskit_consumer", @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "accesskit_unix" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f7474c36606d0fe4f438291d667bae7042ea2760f506650ad2366926358fc8" +checksum = "2abbfb16144cca5bb2ea6acad5865b7c1e70d4fa171ceba1a52ea8e78a7515f4" dependencies = [ "accesskit", "accesskit_atspi_common", @@ -86,9 +86,9 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a042b62c9c05bf7b616f015515c17d2813f3ba89978d6f4fc369735d60700a" +checksum = "e4cd727229c389e32c1a78fe9f74dc62d7c9fb6eac98cfa1a17efde254fb2d98" dependencies = [ "accesskit", "accesskit_consumer", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "accesskit_winit" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1f0d3d13113d8857542a4f8d1a1c24d1dc1527b77aee8426127f4901588708" +checksum = "822493d0e54e6793da77525bb7235a19e4fef8418194aaf25a988bc93740d683" dependencies = [ "accesskit", "accesskit_macos", @@ -2410,8 +2410,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kittest" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c1bfc4cb16136b6f00fb85a281e4b53d026401cf5dff9a427c466bde5891f0b" +source = "git+https://github.com/rerun-io/kittest.git#028d5311f475e64839bcbc04f259a0d20532d2c1" dependencies = [ "accesskit", "accesskit_consumer", diff --git a/Cargo.toml b/Cargo.toml index d99430f72..2202a3f13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,8 +68,8 @@ egui_glow = { version = "0.32.3", path = "crates/egui_glow", default-features = egui_kittest = { version = "0.32.3", path = "crates/egui_kittest", default-features = false } eframe = { version = "0.32.3", path = "crates/eframe", default-features = false } -accesskit = "0.19.0" -accesskit_winit = "0.27" +accesskit = "0.21.0" +accesskit_winit = "0.29" ahash = { version = "0.8.11", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", @@ -87,7 +87,7 @@ glutin-winit = { version = "0.5.0", default-features = false } home = "0.5.9" image = { version = "0.25", default-features = false } js-sys = "0.3" -kittest = { version = "0.2.0" } +kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } log = { version = "0.4", features = ["std"] } mimalloc = "0.1.46" nohash-hasher = "0.2" From a73f2c356f1a1023cb0163d96a22e0d9ce4e1179 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 23 Sep 2025 11:56:32 +0200 Subject: [PATCH 234/388] Add text edit rtl regression test (#7524) --- tests/egui_tests/tests/regression_tests.rs | 30 ++++++++++++++++++- .../tests/snapshots/text_edit_rtl_0.png | 3 ++ .../tests/snapshots/text_edit_rtl_1.png | 3 ++ .../tests/snapshots/text_edit_rtl_2.png | 3 ++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/egui_tests/tests/snapshots/text_edit_rtl_0.png create mode 100644 tests/egui_tests/tests/snapshots/text_edit_rtl_1.png create mode 100644 tests/egui_tests/tests/snapshots/text_edit_rtl_2.png diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs index d02d04aa2..a407864e7 100644 --- a/tests/egui_tests/tests/regression_tests.rs +++ b/tests/egui_tests/tests/regression_tests.rs @@ -1,4 +1,5 @@ -use egui::{Color32, Image, Label, RichText, TextWrapMode, include_image}; +use egui::accesskit::Role; +use egui::{Align, Color32, Image, Label, Layout, RichText, TextWrapMode, include_image}; use egui_kittest::Harness; use egui_kittest::kittest::Queryable as _; @@ -33,3 +34,30 @@ fn hovering_should_preserve_text_format() { harness.snapshot("hovering_should_preserve_text_format"); } + +#[test] +fn text_edit_rtl() { + let mut text = "hello ".to_owned(); + let mut harness = Harness::builder().with_size((200.0, 50.0)).build_ui(|ui| { + ui.with_layout(Layout::right_to_left(Align::Min), |ui| { + _ = ui.button("right"); + ui.add( + egui::TextEdit::singleline(&mut text) + .desired_width(10.0) + .clip_text(false), + ); + _ = ui.button("left"); + }); + }); + + harness.get_by_role(Role::TextInput).focus(); + harness.step(); + harness.snapshot("text_edit_rtl_0"); + + harness.get_by_role(Role::TextInput).type_text("world"); + + for i in 1..3 { + harness.step(); + harness.snapshot(format!("text_edit_rtl_{i}")); + } +} diff --git a/tests/egui_tests/tests/snapshots/text_edit_rtl_0.png b/tests/egui_tests/tests/snapshots/text_edit_rtl_0.png new file mode 100644 index 000000000..d93540222 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/text_edit_rtl_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:336581facb1ec989a43291ed76bd8ddb552c46137a75601f466e6dc4dae77278 +size 2395 diff --git a/tests/egui_tests/tests/snapshots/text_edit_rtl_1.png b/tests/egui_tests/tests/snapshots/text_edit_rtl_1.png new file mode 100644 index 000000000..2ae957da9 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/text_edit_rtl_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b0684c53a20eaa90a9dccef8ac3eaa2a6eede7c770e7bbbba6d995f43584d99 +size 2353 diff --git a/tests/egui_tests/tests/snapshots/text_edit_rtl_2.png b/tests/egui_tests/tests/snapshots/text_edit_rtl_2.png new file mode 100644 index 000000000..b9235740d --- /dev/null +++ b/tests/egui_tests/tests/snapshots/text_edit_rtl_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38f325f2e741f18f897502c176f9a7efe276e9adab41a144511121dd8b8a3073 +size 3079 From b69bab73e14ff9376c27080d585bcd99f14a5c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 23 Sep 2025 14:57:43 +0200 Subject: [PATCH 235/388] Fix build error in egui-winit with profiling enabled (#7557) --- crates/egui-winit/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 99e72c820..1a7bc1be6 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1361,7 +1361,7 @@ fn process_viewport_command( info: &mut ViewportInfo, actions_requested: &mut Vec, ) { - profiling::function_scope!(format!("{command:?}")); + profiling::function_scope!(&format!("{command:?}")); use winit::window::ResizeDirection; From 472437a510552846d180ba0cde0c038712a6076d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 26 Sep 2025 09:39:42 +0200 Subject: [PATCH 236/388] Small CI improvements (#7563) --- .github/workflows/cargo_machete.yml | 4 ++-- .github/workflows/preview_build.yml | 2 +- .github/workflows/rust.yml | 14 +++++++------- .github/workflows/spelling_and_links.yml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml index 0414de063..deb830c81 100644 --- a/.github/workflows/cargo_machete.yml +++ b/.github/workflows/cargo_machete.yml @@ -7,12 +7,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86 - name: Machete install ## The official cargo-machete action - uses: bnjbvr/cargo-machete@main + uses: bnjbvr/cargo-machete@v0.9.1 - name: Checkout uses: actions/checkout@v4 - name: Machete Check diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 05d4118d7..9719b56a1 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -18,7 +18,7 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86.0 targets: wasm32-unknown-unknown diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2c456b60b..fbda11cf4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,7 +17,7 @@ jobs: with: lfs: true - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86.0 @@ -83,7 +83,7 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86.0 targets: wasm32-unknown-unknown @@ -123,7 +123,7 @@ jobs: - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{env.NIGHTLY_VERSION}} targets: wasm32-unknown-unknown @@ -173,7 +173,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86.0 targets: aarch64-linux-android @@ -195,7 +195,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86.0 targets: aarch64-apple-ios @@ -215,7 +215,7 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86.0 @@ -239,7 +239,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: 1.86.0 diff --git a/.github/workflows/spelling_and_links.yml b/.github/workflows/spelling_and_links.yml index e411de5f3..faceada95 100644 --- a/.github/workflows/spelling_and_links.yml +++ b/.github/workflows/spelling_and_links.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Check spelling of entire workspace - uses: crate-ci/typos@master + uses: crate-ci/typos@v1.36.3 lychee: name: lychee From 4683d91653ace7c2428794de0e0f568c6d6be6ff Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 27 Sep 2025 12:14:47 +0200 Subject: [PATCH 237/388] Small clippy fixes (#7566) --- crates/egui/src/menu.rs | 13 +++++++------ crates/egui/src/widget_text.rs | 14 +++++--------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index f9744bd5c..b83aeea63 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -445,12 +445,13 @@ impl MenuRoot { response.ctx.input(|input| { let pointer = &input.pointer; if let Some(pos) = pointer.interact_pos() { - let mut in_old_menu = false; - let mut destroy = false; - if let Some(root) = root { - in_old_menu = root.menu_state.read().area_contains(pos); - destroy = !in_old_menu && pointer.any_pressed() && root.id == response.id; - } + let (in_old_menu, destroy) = if let Some(root) = root { + let in_old_menu = root.menu_state.read().area_contains(pos); + let destroy = !in_old_menu && pointer.any_pressed() && root.id == response.id; + (in_old_menu, destroy) + } else { + (false, false) + }; if !in_old_menu { if hovered && secondary_clicked { return MenuResponse::Create(pos, response.id); diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index c66e38d70..ef54d7969 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -404,15 +404,11 @@ impl RichText { let text_color = text_color.unwrap_or(crate::Color32::PLACEHOLDER); let font_id = { - let mut font_id = text_style - .or_else(|| style.override_text_style.clone()) - .map_or_else( - || fallback_font.resolve(style), - |text_style| text_style.resolve(style), - ); - if let Some(fid) = style.override_font_id.clone() { - font_id = fid; - } + let mut font_id = style.override_font_id.clone().unwrap_or_else(|| { + (text_style.as_ref().or(style.override_text_style.as_ref())) + .map(|text_style| text_style.resolve(style)) + .unwrap_or_else(|| fallback_font.resolve(style)) + }); if let Some(size) = size { font_id.size = size; } From e9898e49329c3364c78dff24a2ff02a2ab1ec3ff Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 29 Sep 2025 11:12:03 +0200 Subject: [PATCH 238/388] Improve error message when failing to run egui_demo_app (#7567) --- crates/egui_demo_app/src/main.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 48825715d..37a235e89 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -8,7 +8,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; // Much faster allocator, can give 20% speedups: https://github.com/emilk/egui/pull/7029 // When compiling natively: -fn main() -> eframe::Result { +fn main() { for arg in std::env::args().skip(1) { match arg.as_str() { "--profile" => { @@ -62,11 +62,27 @@ fn main() -> eframe::Result { ..Default::default() }; - eframe::run_native( + let result = eframe::run_native( "egui demo app", options, Box::new(|cc| Ok(Box::new(egui_demo_app::WrapApp::new(cc)))), - ) + ); + + match result { + Ok(()) => {} + Err(err) => { + // This produces a nicer error message than returning the `Result`: + print_error_and_exit(&err); + } + } +} + +fn print_error_and_exit(err: &eframe::Error) -> ! { + #![expect(clippy::print_stderr)] + #![expect(clippy::exit)] + + eprintln!("Error: {err}"); + std::process::exit(1) } #[cfg(feature = "puffin")] From 5ee88da61cfb783d9d0bbd6960be20f627d9bb02 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 30 Sep 2025 08:24:21 +0200 Subject: [PATCH 239/388] kittest: Format errors with `Display` (#7569) --- crates/egui_kittest/src/snapshot.rs | 8 ++++---- crates/egui_kittest/src/wgpu.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index bc58e7b4c..946984a4b 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -250,7 +250,7 @@ impl Display for SnapshotError { err => { write!( f, - "Error reading snapshot: {err:?}\nAt: {}. {HOW_TO_UPDATE_SCREENSHOTS}", + "Error reading snapshot: {err}\nAt: {}. {HOW_TO_UPDATE_SCREENSHOTS}", path.display() ) } @@ -258,7 +258,7 @@ impl Display for SnapshotError { err => { write!( f, - "Error decoding snapshot: {err:?}\nAt: {}. Make sure git-lfs is setup correctly. Read the instructions here: https://github.com/emilk/egui/blob/main/CONTRIBUTING.md#making-a-pr", + "Error decoding snapshot: {err}\nAt: {}. Make sure git-lfs is setup correctly. Read the instructions here: https://github.com/emilk/egui/blob/main/CONTRIBUTING.md#making-a-pr", path.display() ) } @@ -276,10 +276,10 @@ impl Display for SnapshotError { } Self::WriteSnapshot { path, err } => { let path = std::path::absolute(path).unwrap_or(path.clone()); - write!(f, "Error writing snapshot: {err:?}\nAt: {}", path.display()) + write!(f, "Error writing snapshot: {err}\nAt: {}", path.display()) } Self::RenderError { err } => { - write!(f, "Error rendering image: {err:?}") + write!(f, "Error rendering image: {err}") } } } diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index 85ccf79bd..382ea20b4 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -206,7 +206,7 @@ impl crate::TestRenderer for WgpuTestRenderer { self.render_state .device .poll(wgpu::PollType::Wait) - .map_err(|e| format!("{e:?}"))?; + .map_err(|err| format!("PollError: {err}"))?; Ok(texture_to_image( &self.render_state.device, From 47c5617740a3e86ccafd9d7008fb3afb6ff7fbf8 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 30 Sep 2025 10:12:56 +0200 Subject: [PATCH 240/388] Return `0.0` if font not found in `glyph_width` instead of panic (#7559) --- crates/epaint/src/text/font.rs | 11 +++++------ crates/epaint/src/text/fonts.rs | 11 +++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index c452eb4bc..82fd1aab0 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -478,12 +478,11 @@ impl Font<'_> { /// Width of this character in points. pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 { let (key, glyph_info) = self.glyph_info(c); - let font = &self - .fonts_by_id - .get(&key) - .expect("Nonexistent font ID") - .ab_glyph_font; - glyph_info.advance_width_unscaled.0 * font.px_scale_factor(font_size) + if let Some(font) = &self.fonts_by_id.get(&key) { + glyph_info.advance_width_unscaled.0 * font.ab_glyph_font.px_scale_factor(font_size) + } else { + 0.0 + } } /// Can we display this glyph? diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index a78242fd4..3efcbe8ea 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -651,6 +651,8 @@ impl FontsView<'_> { } /// Width of this character in points. + /// + /// If the font doesn't exist, this will return `0.0`. pub fn glyph_width(&mut self, font_id: &FontId, c: char) -> f32 { self.fonts .font(&font_id.family) @@ -1302,4 +1304,13 @@ mod tests { } } } + + #[test] + fn test_fallback_glyph_width() { + let mut fonts = Fonts::new(1024, AlphaFromCoverage::default(), FontDefinitions::empty()); + let mut view = fonts.with_pixels_per_point(1.0); + + let width = view.glyph_width(&FontId::new(12.0, FontFamily::Proportional), ' '); + assert_eq!(width, 0.0); + } } From a450b1c9895a98a41d03f9995386610f6c5ec941 Mon Sep 17 00:00:00 2001 From: Tye <131195812+tye-exe@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:48:54 +0100 Subject: [PATCH 241/388] Properly end winit event loop (#7565) --- crates/eframe/src/native/glow_integration.rs | 21 ++++--------------- crates/eframe/src/native/run.rs | 5 +++++ crates/eframe/src/native/wgpu_integration.rs | 9 ++++---- crates/eframe/src/native/winit_integration.rs | 19 +++++++++++++++++ 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 4a1df9e1a..9dc8bdfe6 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -470,7 +470,7 @@ impl WinitApp for GlowWinitApp<'_> { if let Some(running) = &mut self.running { Ok(running.on_window_event(window_id, &event)) } else { - Ok(EventResult::Wait) + Ok(EventResult::Exit) } } @@ -747,7 +747,7 @@ impl GlowWinitRunning<'_> { } if integration.should_close() { - Ok(EventResult::Exit) + Ok(EventResult::CloseRequested) } else { Ok(EventResult::Wait) } @@ -798,7 +798,7 @@ impl GlowWinitRunning<'_> { log::debug!( "Received WindowEvent::CloseRequested for main viewport - shutting down." ); - return EventResult::Exit; + return EventResult::CloseRequested; } log::debug!("Received WindowEvent::CloseRequested for viewport {viewport_id:?}"); @@ -818,24 +818,11 @@ impl GlowWinitRunning<'_> { } } } - - winit::event::WindowEvent::Destroyed => { - log::debug!( - "Received WindowEvent::Destroyed for viewport {:?}", - viewport_id - ); - if viewport_id == Some(ViewportId::ROOT) { - return EventResult::Exit; - } else { - return EventResult::Wait; - } - } - _ => {} } if self.integration.should_close() { - return EventResult::Exit; + return EventResult::CloseRequested; } let mut event_response = egui_winit::EventResponse { diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index d21731bd1..03fa7de7d 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -139,6 +139,11 @@ impl WinitAppWrapper { exit = true; event_result } + EventResult::CloseRequested => { + // The windows need to be dropped whilst the event loop is running to allow for proper cleanup. + self.winit_app.save_and_destroy(); + event_result + } }); if let Err(err) = combined_result { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 1d06e6722..550512b7e 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -461,7 +461,8 @@ impl WinitApp for WgpuWinitApp<'_> { if let Some(running) = &mut self.running { Ok(running.on_window_event(window_id, &event)) } else { - Ok(EventResult::Wait) + // running is removed to get ready for exiting + Ok(EventResult::Exit) } } @@ -739,7 +740,7 @@ impl WgpuWinitRunning<'_> { } if integration.should_close() { - Ok(EventResult::Exit) + Ok(EventResult::CloseRequested) } else { Ok(EventResult::Wait) } @@ -799,7 +800,7 @@ impl WgpuWinitRunning<'_> { log::debug!( "Received WindowEvent::CloseRequested for main viewport - shutting down." ); - return EventResult::Exit; + return EventResult::CloseRequested; } log::debug!("Received WindowEvent::CloseRequested for viewport {viewport_id:?}"); @@ -833,7 +834,7 @@ impl WgpuWinitRunning<'_> { .unwrap_or_default(); if integration.should_close() { - EventResult::Exit + EventResult::CloseRequested } else if event_response.repaint { if repaint_asap { EventResult::RepaintNow(window_id) diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index d85443f37..2cf2e9019 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -124,6 +124,25 @@ pub enum EventResult { /// Causes a save of the client state when the persistence feature is enabled. Save, + /// Starts the process of ending eframe execution whilst allowing for proper + /// clean up of resources. + /// + /// # Warning + /// This event **must** occur before [`Exit`] to correctly exit eframe code. + /// If in doubt, return this event. + /// + /// [`Exit`]: [EventResult::Exit] + CloseRequested, + + /// The event loop will exit, now. + /// The correct circumstance to return this event is in response to a winit "Destroyed" event. + /// + /// # Warning + /// The [`CloseRequested`] **must** occur before this event to ensure that winit + /// is able to remove any open windows. Otherwise the window(s) will remain open + /// until the program terminates. + /// + /// [`CloseRequested`]: EventResult::CloseRequested Exit, } From 4fb4072ce811ab4f1328bff93fc6107daf44e1ad Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 30 Sep 2025 15:51:46 +0200 Subject: [PATCH 242/388] Adjust when we write .diff and .new snapshot images (#7571) --- crates/egui_kittest/src/snapshot.rs | 63 +++++++++++++++-------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 946984a4b..da00795f5 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -397,13 +397,6 @@ fn try_image_snapshot_options_impl( Ok(()) }; - // Always write a `.new` file so the user can compare: - new.save(&new_path) - .map_err(|err| SnapshotError::WriteSnapshot { - err, - path: new_path.clone(), - })?; - let previous = match image::open(&snapshot_path) { Ok(image) => image.to_rgba8(), Err(err) => { @@ -433,7 +426,7 @@ fn try_image_snapshot_options_impl( // Compare existing image to the new one: let threshold = if mode == Mode::UpdateAll { - 0.0 + 0.0 // Produce diff for any error, however small } else { *threshold }; @@ -441,39 +434,47 @@ fn try_image_snapshot_options_impl( let result = dify::diff::get_results(previous, new.clone(), threshold, true, None, &None, &None); - if let Some((num_wrong_pixels, diff_image)) = result { + let Some((num_wrong_pixels, diff_image)) = result else { + return Ok(()); // Difference below threshold + }; + + let below_threshold = num_wrong_pixels as i64 <= *failed_pixel_count_threshold as i64; + + if !below_threshold { diff_image .save(diff_path.clone()) .map_err(|err| SnapshotError::WriteSnapshot { path: diff_path.clone(), err, })?; + } - let is_sameish = num_wrong_pixels as i64 <= *failed_pixel_count_threshold as i64; + match mode { + Mode::Test => { + if below_threshold { + Ok(()) + } else { + new.save(&new_path) + .map_err(|err| SnapshotError::WriteSnapshot { + err, + path: new_path.clone(), + })?; - match mode { - Mode::Test => { - if is_sameish { - Ok(()) - } else { - Err(SnapshotError::Diff { - name, - diff: num_wrong_pixels, - diff_path, - }) - } + Err(SnapshotError::Diff { + name, + diff: num_wrong_pixels, + diff_path, + }) } - Mode::UpdateFailing => { - if is_sameish { - Ok(()) - } else { - update_snapshot() - } - } - Mode::UpdateAll => update_snapshot(), } - } else { - Ok(()) + Mode::UpdateFailing => { + if below_threshold { + Ok(()) + } else { + update_snapshot() + } + } + Mode::UpdateAll => update_snapshot(), } } From 18ea9ff0bd3e6e1e6bea4ec1cbcf2756707cc5f4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 30 Sep 2025 15:56:04 +0200 Subject: [PATCH 243/388] Warn if `DYLD_LIBRARY_PATH` is set and we find no wgpu adapter (#7572) Co-authored-by: Andreas Reich --- crates/egui-wgpu/src/lib.rs | 65 ++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 63d20cdfb..2cd2235de 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -92,7 +92,7 @@ async fn request_adapter( instance: &wgpu::Instance, power_preference: wgpu::PowerPreference, compatible_surface: Option<&wgpu::Surface<'_>>, - _available_adapters: &[wgpu::Adapter], + available_adapters: &[wgpu::Adapter], ) -> Result { profiling::function_scope!(); @@ -108,45 +108,57 @@ async fn request_adapter( }) .await .inspect_err(|_err| { - #[cfg(not(target_arch = "wasm32"))] - if _available_adapters.is_empty() { - log::info!("No wgpu adapters found"); - } else if _available_adapters.len() == 1 { + if cfg!(target_arch = "wasm32") { + // Nothing to add here + } else if available_adapters.is_empty() { + if std::env::var("DYLD_LIBRARY_PATH").is_ok() { + // DYLD_LIBRARY_PATH can sometimes lead to loading dylibs that cause + // us to find zero adapters. Very strange. + // I don't want to debug this again. + // See https://github.com/rerun-io/rerun/issues/11351 for more + log::warn!( + "No wgpu adapter found. This could be because DYLD_LIBRARY_PATH causes dylibs to be loaded that interfere with Metal device creation. Try restarting with DYLD_LIBRARY_PATH=''" + ); + } else { + log::info!("No wgpu adapter found"); + } + } else if available_adapters.len() == 1 { log::info!( "The only available wgpu adapter was not suitable: {}", - adapter_info_summary(&_available_adapters[0].get_info()) + adapter_info_summary(&available_adapters[0].get_info()) ); } else { log::info!( "No suitable wgpu adapter found out of the {} available ones: {}", - _available_adapters.len(), - describe_adapters(_available_adapters) + available_adapters.len(), + describe_adapters(available_adapters) ); } })?; - #[cfg(target_arch = "wasm32")] - log::debug!( - "Picked wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) - ); - - #[cfg(not(target_arch = "wasm32"))] - if _available_adapters.len() == 1 { - log::debug!( - "Picked the only available wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) - ); - } else { - log::info!( - "There were {} available wgpu adapters: {}", - _available_adapters.len(), - describe_adapters(_available_adapters) - ); + if cfg!(target_arch = "wasm32") { log::debug!( "Picked wgpu adapter: {}", adapter_info_summary(&adapter.get_info()) ); + } else { + // native: + if available_adapters.len() == 1 { + log::debug!( + "Picked the only available wgpu adapter: {}", + adapter_info_summary(&adapter.get_info()) + ); + } else { + log::info!( + "There were {} available wgpu adapters: {}", + available_adapters.len(), + describe_adapters(available_adapters) + ); + log::debug!( + "Picked wgpu adapter: {}", + adapter_info_summary(&adapter.get_info()) + ); + } } Ok(adapter) @@ -255,7 +267,6 @@ impl RenderState { } } -#[cfg(not(target_arch = "wasm32"))] fn describe_adapters(adapters: &[wgpu::Adapter]) -> String { if adapters.is_empty() { "(none)".to_owned() From 0888e3dc8624642b60f63768de4639d0db2ca0e8 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 1 Oct 2025 14:39:32 +0200 Subject: [PATCH 244/388] Add `Ui::take_available_space()` helper function, which sets the Ui's minimum size to the available space (#7573) A shorthand, and more descriptive version of calling `ui.set_min_size(ui.available_size())`, and mentions this on panel's resizable functions. --- crates/egui/src/containers/panel.rs | 14 ++++++++++---- crates/egui/src/ui.rs | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 2869f598c..917a355cd 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -144,8 +144,11 @@ impl SidePanel { /// /// Default is `true`. /// - /// If you want your panel to be resizable you also need a widget in it that - /// takes up more space as you resize it, such as: + /// If you want your panel to be resizable you also need to make the ui use + /// the available space. + /// + /// This can be done by using [`Ui::take_available_space`], or using a + /// widget in it that takes up more space as you resize it, such as: /// * Wrapping text ([`Ui::horizontal_wrapped`]). /// * A [`crate::ScrollArea`]. /// * A [`crate::Separator`]. @@ -631,8 +634,11 @@ impl TopBottomPanel { /// /// Default is `false`. /// - /// If you want your panel to be resizable you also need a widget in it that - /// takes up more space as you resize it, such as: + /// If you want your panel to be resizable you also need to make the ui use + /// the available space. + /// + /// This can be done by using [`Ui::take_available_space`], or using a + /// widget in it that takes up more space as you resize it, such as: /// * Wrapping text ([`Ui::horizontal_wrapped`]). /// * A [`crate::ScrollArea`]. /// * A [`crate::Separator`]. diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 357e13530..553be6f14 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -944,6 +944,30 @@ impl Ui { self.placer.set_min_height(height); } + /// Makes the ui always fill up the available space. + /// + /// This can be useful to call inside a panel with `resizable == true` + /// to make sure the resized space is used. + pub fn take_available_space(&mut self) { + self.set_min_size(self.available_size()); + } + + /// Makes the ui always fill up the available space in the x axis. + /// + /// This can be useful to call inside a side panel with + /// `resizable == true` to make sure the resized space is used. + pub fn take_available_width(&mut self) { + self.set_min_width(self.available_width()); + } + + /// Makes the ui always fill up the available space in the y axis. + /// + /// This can be useful to call inside a top bottom panel with + /// `resizable == true` to make sure the resized space is used. + pub fn take_available_height(&mut self) { + self.set_min_height(self.available_height()); + } + // ------------------------------------------------------------------------ /// Helper: shrinks the max width to the current width, From 4c1f344ef8d3ce091bde4fff61e984467a9635e6 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Thu, 2 Oct 2025 19:12:29 +0200 Subject: [PATCH 245/388] Update MSRV from 1.86 to 1.88 (#7579) Co-authored-by: Emil Ernerfeldt --- .github/workflows/cargo_machete.yml | 2 +- .github/workflows/deploy_web_demo.yml | 3 +-- .github/workflows/preview_build.yml | 2 +- .github/workflows/rust.yml | 14 +++++++------- Cargo.toml | 17 +++++++---------- clippy.toml | 6 ++---- crates/egui/src/lib.rs | 2 +- crates/emath/src/smart_aim.rs | 2 +- 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 | 4 ++-- 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 +- 37 files changed, 51 insertions(+), 57 deletions(-) diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml index deb830c81..7e5747800 100644 --- a/.github/workflows/cargo_machete.yml +++ b/.github/workflows/cargo_machete.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86 + toolchain: 1.88 - name: Machete install ## The official cargo-machete action uses: bnjbvr/cargo-machete@v0.9.1 diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index f88d89414..21cfb159e 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -11,7 +11,6 @@ on: # release: # types: ["published"] - permissions: contents: write # for committing to gh-pages branch @@ -39,7 +38,7 @@ jobs: with: profile: minimal target: wasm32-unknown-unknown - toolchain: 1.86.0 + toolchain: 1.88.0 override: true - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 9719b56a1..fe37eb8cb 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86.0 + toolchain: 1.88.0 targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fbda11cf4..a8b2c0f7c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86.0 + toolchain: 1.88.0 - name: Install packages (Linux) if: runner.os == 'Linux' @@ -85,7 +85,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86.0 + toolchain: 1.88.0 targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev @@ -159,7 +159,7 @@ jobs: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v2 with: - rust-version: "1.86.0" + rust-version: "1.88.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -175,7 +175,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86.0 + toolchain: 1.88.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -197,7 +197,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86.0 + toolchain: 1.88.0 targets: aarch64-apple-ios - name: Set up cargo cache @@ -217,7 +217,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86.0 + toolchain: 1.88.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 @@ -241,7 +241,7 @@ jobs: lfs: true - uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.86.0 + toolchain: 1.88.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index 2202a3f13..7facc3c23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ [workspace.package] edition = "2024" license = "MIT OR Apache-2.0" -rust-version = "1.86" +rust-version = "1.88" version = "0.32.3" @@ -163,8 +163,10 @@ disallowed_methods = "warn" # See clippy.toml disallowed_names = "warn" # See clippy.toml disallowed_script_idents = "warn" # See clippy.toml disallowed_types = "warn" # See clippy.toml +doc_comment_double_space_linebreaks = "warn" doc_link_with_quotes = "warn" doc_markdown = "warn" +elidable_lifetime_names = "warn" empty_enum = "warn" empty_enum_variants_with_brackets = "warn" empty_line_after_outer_attr = "warn" @@ -184,6 +186,7 @@ fn_to_numeric_cast_any = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" if_let_mutex = "warn" +ignore_without_reason = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" @@ -216,12 +219,12 @@ manual_instant_elapsed = "warn" manual_is_power_of_two = "warn" manual_is_variant_and = "warn" manual_let_else = "warn" +manual_midpoint = "warn" # NOTE `midpoint` is often a lot slower for floats, so we have our own `emath::fast_midpoint` function. manual_ok_or = "warn" manual_string_new = "warn" map_err_ignore = "warn" map_flatten = "warn" match_bool = "warn" -match_on_vec_items = "warn" match_same_arms = "warn" match_wild_err_arm = "warn" match_wildcard_for_single_variants = "warn" @@ -267,6 +270,7 @@ semicolon_if_nothing_returned = "warn" set_contains_or_insert = "warn" single_char_pattern = "warn" single_match_else = "warn" +single_option_map = "warn" str_split_at_newline = "warn" str_to_string = "warn" string_add = "warn" @@ -288,6 +292,7 @@ unimplemented = "warn" uninhabited_references = "warn" uninlined_format_args = "warn" unnecessary_box_returns = "warn" +unnecessary_debug_formatting = "warn" unnecessary_literal_bound = "warn" unnecessary_safety_comment = "warn" unnecessary_safety_doc = "warn" @@ -307,14 +312,6 @@ verbose_file_reads = "warn" wildcard_dependencies = "warn" zero_sized_map_values = "warn" -# Enable these when we update MSRV: -# doc_comment_double_space_linebreaks = "warn" -# elidable_lifetime_names = "warn" -# ignore_without_reason = "warn" -# manual_midpoint = "warn" # NOTE `midpoint` is often a lot slower for floats, so we have our own `emath::fast_midpoint` function. -# single_option_map = "warn" -# unnecessary_debug_formatting = "warn" - # TODO(emilk): maybe enable more of these lints? comparison_chain = "allow" diff --git a/clippy.toml b/clippy.toml index e988cb677..1b39bdae9 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.86" +msrv = "1.88" allow-unwrap-in-tests = true @@ -23,7 +23,7 @@ type-complexity-threshold = 350 # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros disallowed-macros = [ - 'dbg', + 'std::dbg', 'std::unimplemented', # TODO(emilk): consider forbidding these to encourage the use of proper log stream, and then explicitly allow legitimate uses @@ -59,8 +59,6 @@ disallowed-types = [ "std::sync::Condvar", # "std::sync::Once", # enabled for now as the `log_once` macro uses it internally - "ring::digest::SHA1_FOR_LEGACY_USE_ONLY", # SHA1 is cryptographically broken - "winit::dpi::LogicalSize", # We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account "winit::dpi::LogicalPosition", # We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account ] diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 8b87866d0..c62d7c16c 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.86.0 or later to use `egui`. +//! You need to have rust 1.88.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/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index 8d083917a..ebcd68321 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -113,7 +113,7 @@ fn simplest_digit_closed_range(min: i32, max: i32) -> i32 { if min <= 5 && 5 <= max { 5 } else { - (min + max) / 2 + min.midpoint(max) } } diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index a44ab868f..59c7a1e7d 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 7cef1f343..f302b6642 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index 3a2124b18..e1c1a6253 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 4c44d5988..76c16e4a1 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index 76c8b5a65..b78820884 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index a079dfa16..9c82fbbdc 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index ccb6ef73d..99e3eda71 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/external_eventloop/Cargo.toml b/examples/external_eventloop/Cargo.toml index 26eac7210..ea63d9de6 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/external_eventloop_async/Cargo.toml b/examples/external_eventloop_async/Cargo.toml index 418008cca..1b21ddaef 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 17bfb754d..4a3f948d9 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index d5ebd01f0..d563e48bf 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" 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 a3d02778a..d715509de 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index e3976cdff..79b698ec7 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 3256332c5..547290d53 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index 604573d28..e6b8e1dde 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 3639cfe20..be99d0ef0 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 493e1a218..4ec653067 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index b068d4a39..a98573d38 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [package.metadata.cargo-machete] @@ -30,4 +30,4 @@ env_logger = { version = "0.10", default-features = false, features = [ log = { workspace = true } puffin = "0.19" puffin_http = "0.16" -profiling = {workspace = true, features = ["profile-with-puffin"] } +profiling = { workspace = true, features = ["profile-with-puffin"] } diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index ee2ef0bd9..60aa948b8 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] license = "MIT OR Apache-2.0" edition = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index c9dbc7730..a82060f4e 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 6d502dcd8..293f3f0a8 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/rust-toolchain b/rust-toolchain index 2fda45d24..20a74465b 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.86.0" +channel = "1.88.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/check.sh b/scripts/check.sh index 68120b78b..ffee218f6 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.86.0 install --quiet typos-cli +# cargo +1.88.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 fbe946e8c..c138ea5ba 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.86" +msrv = "1.88" allow-unwrap-in-tests = true diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index 1bf772c56..6bfc96e3a 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index 6cb294179..16a42ecbe 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml index 2a6c73be7..3d8b954da 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/tests/test_ui_stack/Cargo.toml b/tests/test_ui_stack/Cargo.toml index 65d808a0d..50b62941d 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] diff --git a/tests/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml index eca0cda80..7f19a9de3 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 = "2024" -rust-version = "1.86" +rust-version = "1.88" publish = false [lints] From bd45406fad35992e7b0c53988859ab86f79d5df9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Oct 2025 19:47:00 +0200 Subject: [PATCH 246/388] Use a lot more let-else (#7582) --- .github/workflows/rust.yml | 2 +- crates/eframe/src/native/app_icon.rs | 21 ++- crates/eframe/src/native/epi_integration.rs | 33 ++-- crates/eframe/src/native/file_storage.rs | 11 +- crates/eframe/src/native/glow_integration.rs | 131 ++++++++-------- crates/eframe/src/native/run.rs | 16 +- crates/eframe/src/native/wgpu_integration.rs | 103 ++++++------ crates/egui-wgpu/src/winit.rs | 34 ++-- crates/egui-winit/src/lib.rs | 95 ++++++------ crates/egui/src/callstack.rs | 8 +- crates/egui/src/containers/area.rs | 29 ++-- crates/egui/src/containers/panel.rs | 21 +-- crates/egui/src/containers/resize.rs | 14 +- crates/egui/src/containers/scene.rs | 60 +++---- crates/egui/src/containers/scroll_area.rs | 8 +- crates/egui/src/containers/window.rs | 18 +-- crates/egui/src/context.rs | 59 ++++--- crates/egui/src/hit_test.rs | 35 ++--- crates/egui/src/input_state/mod.rs | 25 +-- crates/egui/src/interaction.rs | 62 ++++---- crates/egui/src/layers.rs | 14 +- crates/egui/src/memory/mod.rs | 98 ++++++------ crates/egui/src/menu.rs | 35 ++--- .../text_selection/label_text_selection.rs | 146 +++++++++--------- crates/egui/src/viewport.rs | 130 ++++++++-------- crates/egui/src/widgets/text_edit/builder.rs | 135 ++++++++-------- crates/egui_demo_app/src/apps/image_viewer.rs | 10 +- crates/egui_demo_app/src/wrap_app.rs | 8 +- .../src/demo/demo_app_windows.rs | 9 +- crates/egui_demo_lib/src/demo/table_demo.rs | 2 +- .../src/demo/tests/clipboard_test.rs | 5 +- .../src/demo/tests/input_event_history.rs | 10 +- .../src/demo/tests/input_test.rs | 10 +- crates/egui_demo_lib/src/demo/text_edit.rs | 28 ++-- crates/egui_demo_lib/src/demo/undo_redo.rs | 12 +- .../src/easy_mark/easy_mark_editor.rs | 14 +- .../src/easy_mark/easy_mark_parser.rs | 42 ++--- .../egui_extras/src/loaders/image_loader.rs | 12 +- crates/egui_extras/src/sizing.rs | 10 +- crates/egui_extras/src/table.rs | 56 +++---- crates/epaint/src/text/font.rs | 22 +-- crates/epaint/src/text/text_layout.rs | 12 +- crates/epaint/src/text/text_layout_types.rs | 16 +- examples/file_dialog/src/main.rs | 8 +- examples/user_attention/src/main.rs | 30 ++-- 45 files changed, 812 insertions(+), 847 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a8b2c0f7c..b45f027e0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,7 +5,7 @@ name: Rust env: RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings - NIGHTLY_VERSION: nightly-2025-04-22 + NIGHTLY_VERSION: nightly-2025-09-16 jobs: fmt-crank-check-test: diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 7e04f1df7..233bf4c9d 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -14,10 +14,10 @@ pub struct AppTitleIconSetter { impl AppTitleIconSetter { pub fn new(title: String, mut icon_data: Option>) -> Self { - if let Some(icon) = &icon_data { - if **icon == IconData::default() { - icon_data = None; - } + if let Some(icon) = &icon_data + && **icon == IconData::default() + { + icon_data = None; } Self { @@ -275,13 +275,12 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS } // Change the title in the top bar - for python processes this would be again "python" otherwise. - if let Some(main_menu) = app.mainMenu() { - if let Some(item) = main_menu.itemAtIndex(0) { - if let Some(app_menu) = item.submenu() { - profiling::scope!("setTitle_"); - app_menu.setTitle(&NSString::from_str(title)); - } - } + if let Some(main_menu) = app.mainMenu() + && let Some(item) = main_menu.itemAtIndex(0) + && let Some(app_menu) = item.submenu() + { + profiling::scope!("setTitle_"); + app_menu.setTitle(&NSString::from_str(title)); } // The title in the Dock apparently can't be changed. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index aaa8c596a..69c42427f 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -52,14 +52,13 @@ pub fn viewport_builder( viewport_builder = viewport_builder.with_position(pos); } - if clamp_size_to_monitor_size { - if let Some(initial_window_size) = viewport_builder.inner_size { - let initial_window_size = egui::NumExt::at_most( - initial_window_size, - largest_monitor_point_size(egui_zoom_factor, event_loop), - ); - viewport_builder = viewport_builder.with_inner_size(initial_window_size); - } + if clamp_size_to_monitor_size && let Some(initial_window_size) = viewport_builder.inner_size + { + let initial_window_size = egui::NumExt::at_most( + initial_window_size, + largest_monitor_point_size(egui_zoom_factor, event_loop), + ); + viewport_builder = viewport_builder.with_inner_size(initial_window_size); } viewport_builder.inner_size @@ -332,15 +331,15 @@ impl EpiIntegration { if let Some(storage) = self.frame.storage_mut() { profiling::function_scope!(); - if let Some(window) = _window { - if self.persist_window { - profiling::scope!("native_window"); - epi::set_value( - storage, - STORAGE_WINDOW_KEY, - &WindowSettings::from_window(self.egui_ctx.zoom_factor(), window), - ); - } + if let Some(window) = _window + && self.persist_window + { + profiling::scope!("native_window"); + epi::set_value( + storage, + STORAGE_WINDOW_KEY, + &WindowSettings::from_window(self.egui_ctx.zoom_factor(), window), + ); } if _app.persist_egui_memory() { profiling::scope!("egui_memory"); diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index fd502f1a8..13965841b 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -193,12 +193,11 @@ impl crate::Storage for FileStorage { fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { profiling::function_scope!(); - if let Some(parent_dir) = file_path.parent() { - if !parent_dir.exists() { - if let Err(err) = std::fs::create_dir_all(parent_dir) { - log::warn!("Failed to create directory {parent_dir:?}: {err}"); - } - } + if let Some(parent_dir) = file_path.parent() + && !parent_dir.exists() + && let Err(err) = std::fs::create_dir_all(parent_dir) + { + log::warn!("Failed to create directory {parent_dir:?}: {err}"); } match std::fs::File::create(file_path) { diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 9dc8bdfe6..a5924db10 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -281,10 +281,9 @@ impl<'app> GlowWinitApp<'app> { .viewport .mouse_passthrough .unwrap_or(false) + && let Err(err) = glutin.window(ViewportId::ROOT).set_cursor_hittest(false) { - if let Err(err) = glutin.window(ViewportId::ROOT).set_cursor_hittest(false) { - log::warn!("set_cursor_hittest(false) failed: {err}"); - } + log::warn!("set_cursor_hittest(false) failed: {err}"); } let app_creator = std::mem::take(&mut self.app_creator) @@ -440,20 +439,20 @@ impl WinitApp for GlowWinitApp<'_> { _: winit::event::DeviceId, event: winit::event::DeviceEvent, ) -> crate::Result { - if let winit::event::DeviceEvent::MouseMotion { delta } = event { - if let Some(running) = &mut self.running { - let mut glutin = running.glutin.borrow_mut(); - if let Some(viewport) = glutin - .focused_viewport - .and_then(|viewport| glutin.viewports.get_mut(&viewport)) - { - if let Some(egui_winit) = viewport.egui_winit.as_mut() { - egui_winit.on_mouse_motion(delta); - } + if let winit::event::DeviceEvent::MouseMotion { delta } = event + && let Some(running) = &mut self.running + { + let mut glutin = running.glutin.borrow_mut(); + if let Some(viewport) = glutin + .focused_viewport + .and_then(|viewport| glutin.viewports.get_mut(&viewport)) + { + if let Some(egui_winit) = viewport.egui_winit.as_mut() { + egui_winit.on_mouse_motion(delta); + } - if let Some(window) = viewport.window.as_ref() { - return Ok(EventResult::RepaintNext(window.id())); - } + if let Some(window) = viewport.window.as_ref() { + return Ok(EventResult::RepaintNext(window.id())); } } } @@ -480,16 +479,15 @@ impl WinitApp for GlowWinitApp<'_> { if let Some(running) = &self.running { let mut glutin = running.glutin.borrow_mut(); - if let Some(viewport_id) = glutin.viewport_from_window.get(&event.window_id).copied() { - if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) { - if let Some(egui_winit) = &mut viewport.egui_winit { - return Ok(winit_integration::on_accesskit_window_event( - egui_winit, - event.window_id, - &event.window_event, - )); - } - } + if let Some(viewport_id) = glutin.viewport_from_window.get(&event.window_id).copied() + && let Some(viewport) = glutin.viewports.get_mut(&viewport_id) + && let Some(egui_winit) = &mut viewport.egui_winit + { + return Ok(winit_integration::on_accesskit_window_event( + egui_winit, + event.window_id, + &event.window_event, + )); } } @@ -527,10 +525,10 @@ impl GlowWinitRunning<'_> { if is_immediate && viewport_id != ViewportId::ROOT { // This will only happen if this is an immediate viewport. // That means that the viewport cannot be rendered by itself and needs his parent to be rendered. - if let Some(parent_viewport) = glutin.viewports.get(&viewport.ids.parent) { - if let Some(window) = parent_viewport.window.as_ref() { - return Ok(EventResult::RepaintNext(window.id())); - } + if let Some(parent_viewport) = glutin.viewports.get(&viewport.ids.parent) + && let Some(window) = parent_viewport.window.as_ref() + { + return Ok(EventResult::RepaintNext(window.id())); } return Ok(EventResult::Wait); } @@ -727,10 +725,10 @@ impl GlowWinitRunning<'_> { // give it time to settle: #[cfg(feature = "__screenshot")] - if integration.egui_ctx.cumulative_pass_nr() == 2 { - if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") { - save_screenshot_and_exit(&path, &painter, screen_size_in_pixels); - } + if integration.egui_ctx.cumulative_pass_nr() == 2 + && let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") + { + save_screenshot_and_exit(&path, &painter, screen_size_in_pixels); } glutin.handle_viewport_output(event_loop, &integration.egui_ctx, &viewport_output); @@ -785,11 +783,12 @@ impl GlowWinitRunning<'_> { // Resize with 0 width and height is used by winit to signal a minimize event on Windows. // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. - if 0 < physical_size.width && 0 < physical_size.height { - if let Some(viewport_id) = viewport_id { - repaint_asap = true; - glutin.resize(viewport_id, *physical_size); - } + if 0 < physical_size.width + && 0 < physical_size.height + && let Some(viewport_id) = viewport_id + { + repaint_asap = true; + glutin.resize(viewport_id, *physical_size); } } @@ -803,19 +802,19 @@ impl GlowWinitRunning<'_> { log::debug!("Received WindowEvent::CloseRequested for viewport {viewport_id:?}"); - if let Some(viewport_id) = viewport_id { - if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) { - // Tell viewport it should close: - viewport.info.events.push(egui::ViewportEvent::Close); + if let Some(viewport_id) = viewport_id + && let Some(viewport) = glutin.viewports.get_mut(&viewport_id) + { + // Tell viewport it should close: + viewport.info.events.push(egui::ViewportEvent::Close); - // We may need to repaint both us and our parent to close the window, - // and perhaps twice (once to notice the close-event, once again to enforce it). - // `request_repaint_of` does a double-repaint though: - self.integration.egui_ctx.request_repaint_of(viewport_id); - self.integration - .egui_ctx - .request_repaint_of(viewport.ids.parent); - } + // We may need to repaint both us and our parent to close the window, + // and perhaps twice (once to notice the close-event, once again to enforce it). + // `request_repaint_of` does a double-repaint though: + self.integration.egui_ctx.request_repaint_of(viewport_id); + self.integration + .egui_ctx + .request_repaint_of(viewport.ids.parent); } } _ => {} @@ -1232,21 +1231,21 @@ impl GlutinWindowContext { let width_px = NonZeroU32::new(physical_size.width).unwrap_or(NonZeroU32::MIN); let height_px = NonZeroU32::new(physical_size.height).unwrap_or(NonZeroU32::MIN); - if let Some(viewport) = self.viewports.get(&viewport_id) { - if let Some(gl_surface) = &viewport.gl_surface { - change_gl_context( - &mut self.current_gl_context, - &mut self.not_current_gl_context, - gl_surface, - ); - gl_surface.resize( - self.current_gl_context - .as_ref() - .expect("failed to get current context to resize surface"), - width_px, - height_px, - ); - } + if let Some(viewport) = self.viewports.get(&viewport_id) + && let Some(gl_surface) = &viewport.gl_surface + { + change_gl_context( + &mut self.current_gl_context, + &mut self.not_current_gl_context, + gl_surface, + ); + gl_surface.resize( + self.current_gl_context + .as_ref() + .expect("failed to get current context to resize surface"), + width_px, + height_px, + ); } } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 03fa7de7d..6fdae7d3c 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -94,15 +94,15 @@ impl WinitAppWrapper { let mut event_result = event_result; - if cfg!(target_os = "windows") { - if let Ok(EventResult::RepaintNow(window_id)) = event_result { - log::trace!("RepaintNow of {window_id:?}"); - self.windows_next_repaint_times - .insert(window_id, Instant::now()); + if cfg!(target_os = "windows") + && let Ok(EventResult::RepaintNow(window_id)) = event_result + { + log::trace!("RepaintNow of {window_id:?}"); + self.windows_next_repaint_times + .insert(window_id, Instant::now()); - // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 - event_result = self.winit_app.run_ui_and_paint(event_loop, window_id); - } + // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 + event_result = self.winit_app.run_ui_and_paint(event_loop, window_id); } let combined_result = event_result.map(|event_result| match event_result { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 550512b7e..0c06012e7 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -429,20 +429,20 @@ impl WinitApp for WgpuWinitApp<'_> { _: winit::event::DeviceId, event: winit::event::DeviceEvent, ) -> crate::Result { - if let winit::event::DeviceEvent::MouseMotion { delta } = event { - if let Some(running) = &mut self.running { - let mut shared = running.shared.borrow_mut(); - if let Some(viewport) = shared - .focused_viewport - .and_then(|viewport| shared.viewports.get_mut(&viewport)) - { - if let Some(egui_winit) = viewport.egui_winit.as_mut() { - egui_winit.on_mouse_motion(delta); - } + if let winit::event::DeviceEvent::MouseMotion { delta } = event + && let Some(running) = &mut self.running + { + let mut shared = running.shared.borrow_mut(); + if let Some(viewport) = shared + .focused_viewport + .and_then(|viewport| shared.viewports.get_mut(&viewport)) + { + if let Some(egui_winit) = viewport.egui_winit.as_mut() { + egui_winit.on_mouse_motion(delta); + } - if let Some(window) = viewport.window.as_ref() { - return Ok(EventResult::RepaintNext(window.id())); - } + if let Some(window) = viewport.window.as_ref() { + return Ok(EventResult::RepaintNext(window.id())); } } } @@ -478,14 +478,13 @@ impl WinitApp for WgpuWinitApp<'_> { if let Some(viewport) = viewport_from_window .get(&event.window_id) .and_then(|id| viewports.get_mut(id)) + && let Some(egui_winit) = &mut viewport.egui_winit { - if let Some(egui_winit) = &mut viewport.egui_winit { - return Ok(winit_integration::on_accesskit_window_event( - egui_winit, - event.window_id, - &event.window_event, - )); - } + return Ok(winit_integration::on_accesskit_window_event( + egui_winit, + event.window_id, + &event.window_event, + )); } } @@ -563,10 +562,10 @@ impl WgpuWinitRunning<'_> { if viewport.viewport_ui_cb.is_none() { // This will only happen if this is an immediate viewport. // That means that the viewport cannot be rendered by itself and needs his parent to be rendered. - if let Some(viewport) = viewports.get(&viewport.ids.parent) { - if let Some(window) = viewport.window.as_ref() { - return Ok(EventResult::RepaintNext(window.id())); - } + if let Some(viewport) = viewports.get(&viewport.ids.parent) + && let Some(window) = viewport.window.as_ref() + { + return Ok(EventResult::RepaintNext(window.id())); } return Ok(EventResult::Wait); } @@ -730,13 +729,13 @@ impl WgpuWinitRunning<'_> { integration.maybe_autosave(app.as_mut(), window.map(|w| w.as_ref())); - if let Some(window) = window { - if window.is_minimized() == Some(true) { - // On Mac, a minimized Window uses up all CPU: - // https://github.com/emilk/egui/issues/325 - profiling::scope!("minimized_sleep"); - std::thread::sleep(std::time::Duration::from_millis(10)); - } + if let Some(window) = window + && window.is_minimized() == Some(true) + { + // On Mac, a minimized Window uses up all CPU: + // https://github.com/emilk/egui/issues/325 + profiling::scope!("minimized_sleep"); + std::thread::sleep(std::time::Duration::from_millis(10)); } if integration.should_close() { @@ -784,14 +783,14 @@ impl WgpuWinitRunning<'_> { // Resize with 0 width and height is used by winit to signal a minimize event on Windows. // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. - if let Some(viewport_id) = viewport_id { - if let (Some(width), Some(height)) = ( + if let Some(viewport_id) = viewport_id + && let (Some(width), Some(height)) = ( NonZeroU32::new(physical_size.width), NonZeroU32::new(physical_size.height), - ) { - repaint_asap = true; - shared.painter.on_window_resized(viewport_id, width, height); - } + ) + { + repaint_asap = true; + shared.painter.on_window_resized(viewport_id, width, height); } } @@ -805,17 +804,17 @@ impl WgpuWinitRunning<'_> { log::debug!("Received WindowEvent::CloseRequested for viewport {viewport_id:?}"); - if let Some(viewport_id) = viewport_id { - if let Some(viewport) = shared.viewports.get_mut(&viewport_id) { - // Tell viewport it should close: - viewport.info.events.push(egui::ViewportEvent::Close); + if let Some(viewport_id) = viewport_id + && let Some(viewport) = shared.viewports.get_mut(&viewport_id) + { + // Tell viewport it should close: + viewport.info.events.push(egui::ViewportEvent::Close); - // We may need to repaint both us and our parent to close the window, - // and perhaps twice (once to notice the close-event, once again to enforce it). - // `request_repaint_of` does a double-repaint though: - integration.egui_ctx.request_repaint_of(viewport_id); - integration.egui_ctx.request_repaint_of(viewport.ids.parent); - } + // We may need to repaint both us and our parent to close the window, + // and perhaps twice (once to notice the close-event, once again to enforce it). + // `request_repaint_of` does a double-repaint though: + integration.egui_ctx.request_repaint_of(viewport_id); + integration.egui_ctx.request_repaint_of(viewport.ids.parent); } } @@ -1087,13 +1086,13 @@ fn handle_viewport_output( // For Wayland : https://github.com/emilk/egui/issues/4196 if cfg!(target_os = "linux") { let new_inner_size = window.inner_size(); - if new_inner_size != old_inner_size { - if let (Some(width), Some(height)) = ( + if new_inner_size != old_inner_size + && let (Some(width), Some(height)) = ( NonZeroU32::new(new_inner_size.width), NonZeroU32::new(new_inner_size.height), - ) { - painter.on_window_resized(viewport_id, width, height); - } + ) + { + painter.on_window_resized(viewport_id, width, height); } } } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 02f1851f2..917e704fa 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -498,14 +498,12 @@ impl Painter { &screen_descriptor, ); - if capture { - if let Some(capture_state) = &mut self.screen_capture_state { - capture_buffer = Some(capture_state.copy_textures( - &render_state.device, - &output_frame, - &mut encoder, - )); - } + if capture && let Some(capture_state) = &mut self.screen_capture_state { + capture_buffer = Some(capture_state.copy_textures( + &render_state.device, + &output_frame, + &mut encoder, + )); } } @@ -535,16 +533,16 @@ impl Painter { } } - if let Some(capture_buffer) = capture_buffer { - if let Some(screen_capture_state) = &mut self.screen_capture_state { - screen_capture_state.read_screen_rgba( - self.context.clone(), - capture_buffer, - capture_data, - self.capture_tx.clone(), - viewport_id, - ); - } + if let Some(capture_buffer) = capture_buffer + && let Some(screen_capture_state) = &mut self.screen_capture_state + { + screen_capture_state.read_screen_rgba( + self.context.clone(), + capture_buffer, + capture_data, + self.capture_tx.clone(), + viewport_id, + ); } { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 1a7bc1be6..914c17751 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -574,41 +574,41 @@ impl State { state: winit::event::ElementState, button: winit::event::MouseButton, ) { - if let Some(pos) = self.pointer_pos_in_points { - if let Some(button) = translate_mouse_button(button) { - let pressed = state == winit::event::ElementState::Pressed; + if let Some(pos) = self.pointer_pos_in_points + && let Some(button) = translate_mouse_button(button) + { + let pressed = state == winit::event::ElementState::Pressed; - self.egui_input.events.push(egui::Event::PointerButton { - pos, - button, - pressed, - modifiers: self.egui_input.modifiers, - }); + self.egui_input.events.push(egui::Event::PointerButton { + pos, + button, + pressed, + modifiers: self.egui_input.modifiers, + }); - if self.simulate_touch_screen { - if pressed { - self.any_pointer_button_down = true; + if self.simulate_touch_screen { + if pressed { + self.any_pointer_button_down = true; - self.egui_input.events.push(egui::Event::Touch { - device_id: egui::TouchDeviceId(0), - id: egui::TouchId(0), - phase: egui::TouchPhase::Start, - pos, - force: None, - }); - } else { - self.any_pointer_button_down = false; + self.egui_input.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(0), + id: egui::TouchId(0), + phase: egui::TouchPhase::Start, + pos, + force: None, + }); + } else { + self.any_pointer_button_down = false; - self.egui_input.events.push(egui::Event::PointerGone); + self.egui_input.events.push(egui::Event::PointerGone); - self.egui_input.events.push(egui::Event::Touch { - device_id: egui::TouchDeviceId(0), - id: egui::TouchId(0), - phase: egui::TouchPhase::End, - pos, - force: None, - }); - } + self.egui_input.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(0), + id: egui::TouchId(0), + phase: egui::TouchPhase::End, + pos, + force: None, + }); } } } @@ -913,11 +913,11 @@ impl State { } #[cfg(feature = "accesskit")] - if let Some(accesskit) = self.accesskit.as_mut() { - if let Some(update) = accesskit_update { - profiling::scope!("accesskit"); - accesskit.update_if_active(|| update); - } + if let Some(accesskit) = self.accesskit.as_mut() + && let Some(update) = accesskit_update + { + profiling::scope!("accesskit"); + accesskit.update_if_active(|| update); } } @@ -1378,10 +1378,10 @@ fn process_viewport_command( } ViewportCommand::StartDrag => { // If `.has_focus()` is not checked on x11 the input will be permanently taken until the app is killed! - if window.has_focus() { - if let Err(err) = window.drag_window() { - log::warn!("{command:?}: {err}"); - } + if window.has_focus() + && let Err(err) = window.drag_window() + { + log::warn!("{command:?}: {err}"); } } ViewportCommand::InnerSize(size) => { @@ -1795,10 +1795,10 @@ pub fn apply_viewport_builder_to_window( window: &Window, builder: &ViewportBuilder, ) { - if let Some(mouse_passthrough) = builder.mouse_passthrough { - if let Err(err) = window.set_cursor_hittest(!mouse_passthrough) { - log::warn!("set_cursor_hittest failed: {err}"); - } + if let Some(mouse_passthrough) = builder.mouse_passthrough + && let Err(err) = window.set_cursor_hittest(!mouse_passthrough) + { + log::warn!("set_cursor_hittest failed: {err}"); } { @@ -1809,16 +1809,15 @@ pub fn apply_viewport_builder_to_window( let pixels_per_point = pixels_per_point(egui_ctx, window); - if let Some(size) = builder.inner_size { - if window + if let Some(size) = builder.inner_size + && window .request_inner_size(PhysicalSize::new( pixels_per_point * size.x, pixels_per_point * size.y, )) .is_some() - { - log::debug!("Failed to set window size"); - } + { + log::debug!("Failed to set window size"); } if let Some(size) = builder.min_inner_size { window.set_min_inner_size(Some(PhysicalSize::new( diff --git a/crates/egui/src/callstack.rs b/crates/egui/src/callstack.rs index 3fe0a7a59..fef9b2166 100644 --- a/crates/egui/src/callstack.rs +++ b/crates/egui/src/callstack.rs @@ -20,10 +20,10 @@ pub fn capture() -> String { backtrace::resolve_frame(frame, |symbol| { let mut file_and_line = symbol.filename().map(shorten_source_file_path); - if let Some(file_and_line) = &mut file_and_line { - if let Some(line_nr) = symbol.lineno() { - file_and_line.push_str(&format!(":{line_nr}")); - } + if let Some(file_and_line) = &mut file_and_line + && let Some(line_nr) = symbol.lineno() + { + file_and_line.push_str(&format!(":{line_nr}")); } let file_and_line = file_and_line.unwrap_or_default(); diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index d3d2a7228..07f2e0cf9 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -525,10 +525,11 @@ impl Area { true, ); - if movable && move_response.dragged() { - if let Some(pivot_pos) = &mut state.pivot_pos { - *pivot_pos += move_response.drag_delta(); - } + if movable + && move_response.dragged() + && let Some(pivot_pos) = &mut state.pivot_pos + { + *pivot_pos += move_response.drag_delta(); } if (move_response.dragged() || move_response.clicked()) @@ -606,16 +607,16 @@ impl Prepared { let mut ui = Ui::new(ctx.clone(), self.layer_id.id, ui_builder); ui.set_clip_rect(self.constrain_rect); // Don't paint outside our bounds - if self.fade_in { - if let Some(last_became_visible_at) = self.state.last_became_visible_at { - let age = - ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0); - let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0); - let opacity = emath::easing::quadratic_out(opacity); // slow fade-out = quick fade-in - ui.multiply_opacity(opacity); - if opacity < 1.0 { - ctx.request_repaint(); - } + if self.fade_in + && let Some(last_became_visible_at) = self.state.last_became_visible_at + { + let age = + ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0); + let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0); + let opacity = emath::easing::quadratic_out(opacity); // slow fade-out = quick fade-in + ui.multiply_opacity(opacity); + if opacity < 1.0 { + ctx.request_repaint(); } } diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 917a355cd..1cb10269b 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -266,12 +266,10 @@ impl SidePanel { resize_hover = resize_response.hovered(); is_resizing = resize_response.dragged(); - if is_resizing { - if let Some(pointer) = resize_response.interact_pointer_pos() { - width = (pointer.x - side.side_x(panel_rect)).abs(); - width = clamp_to_range(width, width_range).at_most(available_rect.width()); - side.set_rect_width(&mut panel_rect, width); - } + if is_resizing && let Some(pointer) = resize_response.interact_pointer_pos() { + width = (pointer.x - side.side_x(panel_rect)).abs(); + width = clamp_to_range(width, width_range).at_most(available_rect.width()); + side.set_rect_width(&mut panel_rect, width); } } } @@ -765,13 +763,10 @@ impl TopBottomPanel { resize_hover = resize_response.hovered(); is_resizing = resize_response.dragged(); - if is_resizing { - if let Some(pointer) = resize_response.interact_pointer_pos() { - height = (pointer.y - side.side_y(panel_rect)).abs(); - height = - clamp_to_range(height, height_range).at_most(available_rect.height()); - side.set_rect_height(&mut panel_rect, height); - } + if is_resizing && let Some(pointer) = resize_response.interact_pointer_pos() { + height = (pointer.y - side.side_y(panel_rect)).abs(); + height = clamp_to_range(height, height_range).at_most(available_rect.height()); + side.set_rect_height(&mut panel_rect, height); } } } diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index f7522e020..bb001dc67 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -241,14 +241,12 @@ impl Resize { let corner_id = self.resizable.any().then(|| id.with("__resize_corner")); - if let Some(corner_id) = corner_id { - if let Some(corner_response) = ui.ctx().read_response(corner_id) { - if let Some(pointer_pos) = corner_response.interact_pointer_pos() { - // Respond to the interaction early to avoid frame delay. - user_requested_size = - Some(pointer_pos - position + 0.5 * corner_response.rect.size()); - } - } + if let Some(corner_id) = corner_id + && let Some(corner_response) = ui.ctx().read_response(corner_id) + && let Some(pointer_pos) = corner_response.interact_pointer_pos() + { + // Respond to the interaction early to avoid frame delay. + user_requested_size = Some(pointer_pos - position + 0.5 * corner_response.rect.size()); } if let Some(user_requested_size) = user_requested_size { diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index 4c8c9f64b..58739ba2a 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -240,38 +240,38 @@ impl Scene { resp.mark_changed(); } - if let Some(mouse_pos) = ui.input(|i| i.pointer.latest_pos()) { - if resp.contains_pointer() { - let pointer_in_scene = to_global.inverse() * mouse_pos; - let zoom_delta = ui.ctx().input(|i| i.zoom_delta()); - let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta); + if let Some(mouse_pos) = ui.input(|i| i.pointer.latest_pos()) + && resp.contains_pointer() + { + let pointer_in_scene = to_global.inverse() * mouse_pos; + let zoom_delta = ui.ctx().input(|i| i.zoom_delta()); + let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta); - // Most of the time we can return early. This is also important to - // avoid `ui_from_scene` to change slightly due to floating point errors. - if zoom_delta == 1.0 && pan_delta == Vec2::ZERO { - return; - } - - if zoom_delta != 1.0 { - // Zoom in on pointer, but only if we are not zoomed in or out too far. - let zoom_delta = zoom_delta.clamp( - self.zoom_range.min / to_global.scaling, - self.zoom_range.max / to_global.scaling, - ); - - *to_global = *to_global - * TSTransform::from_translation(pointer_in_scene.to_vec2()) - * TSTransform::from_scaling(zoom_delta) - * TSTransform::from_translation(-pointer_in_scene.to_vec2()); - - // Clamp to exact zoom range. - to_global.scaling = self.zoom_range.clamp(to_global.scaling); - } - - // Pan: - *to_global = TSTransform::from_translation(pan_delta) * *to_global; - resp.mark_changed(); + // Most of the time we can return early. This is also important to + // avoid `ui_from_scene` to change slightly due to floating point errors. + if zoom_delta == 1.0 && pan_delta == Vec2::ZERO { + return; } + + if zoom_delta != 1.0 { + // Zoom in on pointer, but only if we are not zoomed in or out too far. + let zoom_delta = zoom_delta.clamp( + self.zoom_range.min / to_global.scaling, + self.zoom_range.max / to_global.scaling, + ); + + *to_global = *to_global + * TSTransform::from_translation(pointer_in_scene.to_vec2()) + * TSTransform::from_scaling(zoom_delta) + * TSTransform::from_translation(-pointer_in_scene.to_vec2()); + + // Clamp to exact zoom range. + to_global.scaling = self.zoom_range.clamp(to_global.scaling); + } + + // Pan: + *to_global = TSTransform::from_translation(pan_delta) * *to_global; + resp.mark_changed(); } } } diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 3d8bf75f1..ff2542d8d 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -829,10 +829,10 @@ impl ScrollArea { if let Some(cursor) = on_drag_cursor { response.on_hover_cursor(cursor); } - } else if response.hovered() { - if let Some(cursor) = on_hover_cursor { - response.on_hover_cursor(cursor); - } + } else if response.hovered() + && let Some(cursor) = on_hover_cursor + { + response.on_hover_cursor(cursor); } } } diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index c3c45dfe4..b5ac1cc40 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -645,10 +645,10 @@ impl Window<'_> { let full_response = area.end(ctx, area_content_ui); - if full_response.should_close() { - if let Some(open) = open { - *open = false; - } + if full_response.should_close() + && let Some(open) = open + { + *open = false; } let inner_response = InnerResponse { @@ -839,11 +839,11 @@ fn resize_response( // TODO(emilk): add this to a Window state instead as a command "move here next frame" area.state_mut().set_left_top_pos(new_rect.left_top()); - if resize_interaction.any_dragged() { - if let Some(mut state) = resize::State::load(ctx, resize_id) { - state.requested_size = Some(new_rect.size() - margins); - state.store(ctx, resize_id); - } + if resize_interaction.any_dragged() + && let Some(mut state) = resize::State::load(ctx, resize_id) + { + state.requested_size = Some(new_rect.size() - margins); + state.store(ctx, resize_id); } ctx.memory_mut(|mem| mem.areas_mut().move_to_top(area_layer_id)); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 4ac8668d4..b496612e8 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -426,20 +426,18 @@ impl ContextImpl { let viewport = self.viewports.entry(viewport_id).or_default(); - if is_outermost_viewport { - if let Some(new_zoom_factor) = self.new_zoom_factor.take() { - let ratio = self.memory.options.zoom_factor / new_zoom_factor; - self.memory.options.zoom_factor = new_zoom_factor; + if is_outermost_viewport && let Some(new_zoom_factor) = self.new_zoom_factor.take() { + let ratio = self.memory.options.zoom_factor / new_zoom_factor; + self.memory.options.zoom_factor = new_zoom_factor; - let input = &viewport.input; - // This is a bit hacky, but is required to avoid jitter: - let mut rect = input.screen_rect; - rect.min = (ratio * rect.min.to_vec2()).to_pos2(); - rect.max = (ratio * rect.max.to_vec2()).to_pos2(); - new_raw_input.screen_rect = Some(rect); - // We should really scale everything else in the input too, - // but the `screen_rect` is the most important part. - } + let input = &viewport.input; + // This is a bit hacky, but is required to avoid jitter: + let mut rect = input.screen_rect; + rect.min = (ratio * rect.min.to_vec2()).to_pos2(); + rect.max = (ratio * rect.max.to_vec2()).to_pos2(); + new_raw_input.screen_rect = Some(rect); + // We should really scale everything else in the input too, + // but the `screen_rect` is the most important part. } let native_pixels_per_point = new_raw_input .viewport() @@ -1105,15 +1103,16 @@ impl Context { ) }; - if let Some(pointer_pos) = self.pointer_hover_pos() { - if text_rect.contains(pointer_pos) { - let tooltip_pos = if below { - text_rect.left_bottom() + vec2(2.0, 4.0) - } else { - text_rect.left_top() + vec2(2.0, -4.0) - }; + if let Some(pointer_pos) = self.pointer_hover_pos() + && text_rect.contains(pointer_pos) + { + let tooltip_pos = if below { + text_rect.left_bottom() + vec2(2.0, 4.0) + } else { + text_rect.left_top() + vec2(2.0, -4.0) + }; - painter.error( + painter.error( tooltip_pos, format!("Widget is {} this text.\n\n\ ID clashes happens when things like Windows or CollapsingHeaders share names,\n\ @@ -1121,7 +1120,6 @@ impl Context { Sometimes the solution is to use ui.push_id.", if below { "above" } else { "below" }), ); - } } }; @@ -1253,10 +1251,10 @@ impl Context { widget_rect.map(|mut rect| { // If the Rect is invalid the Ui hasn't registered its final Rect yet. // We return the Rect from last frame instead. - if !(rect.rect.is_positive() && rect.rect.is_finite()) { - if let Some(prev_rect) = viewport.prev_pass.widgets.get(id) { - rect.rect = prev_rect.rect; - } + if !(rect.rect.is_positive() && rect.rect.is_finite()) + && let Some(prev_rect) = viewport.prev_pass.widgets.get(id) + { + rect.rect = prev_rect.rect; } rect }) @@ -1961,14 +1959,13 @@ impl Context { let mut update_fonts = true; self.read(|ctx| { - if let Some(current_fonts) = ctx.fonts.as_ref() { - if current_fonts + if let Some(current_fonts) = ctx.fonts.as_ref() + && current_fonts .definitions() .font_data .contains_key(&new_font.name) - { - update_fonts = false; // no need to update - } + { + update_fonts = false; // no need to update } }); diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index d1122350e..954ff5d71 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -317,19 +317,18 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { pos, ); - if let Some(closest_drag) = closest_drag { - if hit_drag + if let Some(closest_drag) = closest_drag + && hit_drag .interact_rect .contains_rect(closest_drag.interact_rect) - { - // `hit_drag` is a big background thing and `closest_drag` is something small on top of it. - // Be helpful and return the small things: - return WidgetHits { - click: None, - drag: Some(closest_drag), - ..Default::default() - }; - } + { + // `hit_drag` is a big background thing and `closest_drag` is something small on top of it. + // Be helpful and return the small things: + return WidgetHits { + click: None, + drag: Some(closest_drag), + ..Default::default() + }; } WidgetHits { @@ -425,13 +424,13 @@ fn find_closest_within( let dist_sq = widget.interact_rect.distance_sq_to_pos(pos); - if let Some(closest) = closest { - if dist_sq == closest_dist_sq { - // It's a tie! Pick the thin candidate over the thick one. - // This makes it easier to hit a thin resize-handle, for instance: - if should_prioritize_hits_on_back(closest.interact_rect, widget.interact_rect) { - continue; - } + if let Some(closest) = closest + && dist_sq == closest_dist_sq + { + // It's a tie! Pick the thin candidate over the thick one. + // This makes it easier to hit a thin resize-handle, for instance: + if should_prioritize_hits_on_back(closest.interact_rect, widget.interact_rect) { + continue; } } diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 33a08c79a..5f6aec789 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -884,10 +884,11 @@ impl InputState { ) -> impl Iterator { let accesskit_id = id.accesskit_id(); self.events.iter().filter_map(move |event| { - if let Event::AccessKitActionRequest(request) = event { - if request.target == accesskit_id && request.action == action { - return Some(request); - } + if let Event::AccessKitActionRequest(request) = event + && request.target == accesskit_id + && request.action == action + { + return Some(request); } None }) @@ -901,10 +902,10 @@ impl InputState { ) { let accesskit_id = id.accesskit_id(); self.events.retain(|event| { - if let Event::AccessKitActionRequest(request) = event { - if request.target == accesskit_id { - return !consume(request); - } + if let Event::AccessKitActionRequest(request) = event + && request.target == accesskit_id + { + return !consume(request); } true }); @@ -1468,10 +1469,10 @@ impl PointerState { return false; } - if let Some(press_start_time) = self.press_start_time { - if self.time - press_start_time > self.options.max_click_duration { - return false; - } + if let Some(press_start_time) = self.press_start_time + && self.time - press_start_time > self.options.max_click_duration + { + return false; } true diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index d81291a6f..bfac38da7 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -115,19 +115,19 @@ pub(crate) fn interact( ) -> InteractionSnapshot { profiling::function_scope!(); - if let Some(id) = interaction.potential_click_id { - if !widgets.contains(id) { - // The widget we were interested in clicking is gone. - interaction.potential_click_id = None; - } + if let Some(id) = interaction.potential_click_id + && !widgets.contains(id) + { + // The widget we were interested in clicking is gone. + interaction.potential_click_id = None; } - if let Some(id) = interaction.potential_drag_id { - if !widgets.contains(id) { - // The widget we were interested in dragging is gone. - // This is fine! This could be drag-and-drop, - // and the widget being dragged is now "in the air" and thus - // not registered in the new frame. - } + if let Some(id) = interaction.potential_drag_id + && !widgets.contains(id) + { + // The widget we were interested in dragging is gone. + // This is fine! This could be drag-and-drop, + // and the widget being dragged is now "in the air" and thus + // not registered in the new frame. } let mut clicked = None; @@ -172,13 +172,13 @@ pub(crate) fn interact( } PointerEvent::Released { click, button: _ } => { - if click.is_some() && !input.pointer.is_decidedly_dragging() { - if let Some(widget) = interaction + if click.is_some() + && !input.pointer.is_decidedly_dragging() + && let Some(widget) = interaction .potential_click_id .and_then(|id| widgets.get(id)) - { - clicked = Some(widget.id); - } + { + clicked = Some(widget.id); } interaction.potential_drag_id = None; @@ -190,21 +190,21 @@ pub(crate) fn interact( if dragged.is_none() { // Check if we started dragging something new: - if let Some(widget) = interaction.potential_drag_id.and_then(|id| widgets.get(id)) { - if widget.enabled { - let is_dragged = if widget.sense.senses_click() && widget.sense.senses_drag() { - // This widget is sensitive to both clicks and drags. - // When the mouse first is pressed, it could be either, - // so we postpone the decision until we know. - input.pointer.is_decidedly_dragging() - } else { - // This widget is just sensitive to drags, so we can mark it as dragged right away: - widget.sense.senses_drag() - }; + if let Some(widget) = interaction.potential_drag_id.and_then(|id| widgets.get(id)) + && widget.enabled + { + let is_dragged = if widget.sense.senses_click() && widget.sense.senses_drag() { + // This widget is sensitive to both clicks and drags. + // When the mouse first is pressed, it could be either, + // so we postpone the decision until we know. + input.pointer.is_decidedly_dragging() + } else { + // This widget is just sensitive to drags, so we can mark it as dragged right away: + widget.sense.senses_drag() + }; - if is_dragged { - dragged = Some(widget.id); - } + if is_dragged { + dragged = Some(widget.id); } } } diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 9b0889116..4aadff877 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -236,15 +236,15 @@ impl GraphicLayers { // First do the layers part of area_order: for layer_id in area_order { - if layer_id.order == order { - if let Some(list) = order_map.get_mut(&layer_id.id) { - if let Some(to_global) = to_global.get(layer_id) { - for clipped_shape in &mut list.0 { - clipped_shape.transform(*to_global); - } + if layer_id.order == order + && let Some(list) = order_map.get_mut(&layer_id.id) + { + if let Some(to_global) = to_global.get(layer_id) { + for clipped_shape in &mut list.0 { + clipped_shape.transform(*to_global); } - all_shapes.append(&mut list.0); } + all_shapes.append(&mut list.0); } } diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index eaf543528..cab041bff 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -535,36 +535,34 @@ impl Focus { self.focus_direction = FocusDirection::None; for event in &new_input.events { - if !event_filter.matches(event) { - if let crate::Event::Key { + if !event_filter.matches(event) + && let crate::Event::Key { key, pressed: true, modifiers, .. } = event - { - if let Some(cardinality) = match key { - crate::Key::ArrowUp => Some(FocusDirection::Up), - crate::Key::ArrowRight => Some(FocusDirection::Right), - crate::Key::ArrowDown => Some(FocusDirection::Down), - crate::Key::ArrowLeft => Some(FocusDirection::Left), + && let Some(cardinality) = match key { + crate::Key::ArrowUp => Some(FocusDirection::Up), + crate::Key::ArrowRight => Some(FocusDirection::Right), + crate::Key::ArrowDown => Some(FocusDirection::Down), + crate::Key::ArrowLeft => Some(FocusDirection::Left), - crate::Key::Tab => { - if modifiers.shift { - Some(FocusDirection::Previous) - } else { - Some(FocusDirection::Next) - } + crate::Key::Tab => { + if modifiers.shift { + Some(FocusDirection::Previous) + } else { + Some(FocusDirection::Next) } - crate::Key::Escape => { - self.focused_widget = None; - Some(FocusDirection::None) - } - _ => None, - } { - self.focus_direction = cardinality; } + crate::Key::Escape => { + self.focused_widget = None; + Some(FocusDirection::None) + } + _ => None, } + { + self.focus_direction = cardinality; } #[cfg(feature = "accesskit")] @@ -582,10 +580,10 @@ impl Focus { } pub(crate) fn end_pass(&mut self, used_ids: &IdMap) { - if self.focus_direction.is_cardinal() { - if let Some(found_widget) = self.find_widget_in_direction(used_ids) { - self.focused_widget = Some(FocusWidget::new(found_widget)); - } + if self.focus_direction.is_cardinal() + && let Some(found_widget) = self.find_widget_in_direction(used_ids) + { + self.focused_widget = Some(FocusWidget::new(found_widget)); } if let Some(focused_widget) = self.focused_widget { @@ -858,12 +856,12 @@ impl Memory { /// /// You must first give focus to the widget before calling this. pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) { - if self.had_focus_last_frame(id) && self.has_focus(id) { - if let Some(focused) = &mut self.focus_mut().focused_widget { - if focused.id == id { - focused.filter = event_filter; - } - } + if self.had_focus_last_frame(id) + && self.has_focus(id) + && let Some(focused) = &mut self.focus_mut().focused_widget + && focused.id == id + { + focused.filter = event_filter; } } @@ -933,13 +931,13 @@ impl Memory { /// Limit focus to widgets on the given layer and above. /// If this is called multiple times per frame, the top layer wins. pub fn set_modal_layer(&mut self, layer_id: LayerId) { - if let Some(current) = self.focus().and_then(|f| f.top_modal_layer_current_frame) { - if matches!( + if let Some(current) = self.focus().and_then(|f| f.top_modal_layer_current_frame) + && matches!( self.areas().compare_order(layer_id, current), std::cmp::Ordering::Less - ) { - return; - } + ) + { + return; } self.focus_mut().set_modal_layer(layer_id); @@ -1047,10 +1045,10 @@ impl Memory { /// 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 { - state.open_this_frame = true; - } + if let Some(state) = self.popups.get_mut(&self.viewport_id) + && state.id == popup_id + { + state.open_this_frame = true; } } @@ -1200,17 +1198,17 @@ impl Areas { layer_to_global: &HashMap, ) -> Option { for layer in self.order.iter().rev() { - if self.is_visible(layer) { - if let Some(state) = self.areas.get(&layer.id) { - let mut rect = state.rect(); - if state.interactable { - if let Some(to_global) = layer_to_global.get(layer) { - rect = *to_global * rect; - } + if self.is_visible(layer) + && let Some(state) = self.areas.get(&layer.id) + { + let mut rect = state.rect(); + if state.interactable { + if let Some(to_global) = layer_to_global.get(layer) { + rect = *to_global * rect; + } - if rect.contains(pos) { - return Some(*layer); - } + if rect.contains(pos) { + return Some(*layer); } } } diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index b83aeea63..0e31a593c 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -420,17 +420,14 @@ impl MenuRoot { } else if button .ctx .input(|i| i.pointer.any_pressed() && i.pointer.primary_down()) + && let Some(pos) = button.ctx.input(|i| i.pointer.interact_pos()) + && let Some(root) = root.inner.as_mut() + && root.id == id { - if let Some(pos) = button.ctx.input(|i| i.pointer.interact_pos()) { - if let Some(root) = root.inner.as_mut() { - if root.id == id { - // pressed somewhere while this menu is open - let in_menu = root.menu_state.read().area_contains(pos); - if !in_menu { - return MenuResponse::Close; - } - } - } + // pressed somewhere while this menu is open + let in_menu = root.menu_state.read().area_contains(pos); + if !in_menu { + return MenuResponse::Close; } } MenuResponse::Stay @@ -728,21 +725,21 @@ impl MenuState { return false; } - if let Some(sub_menu) = self.current_submenu() { - if let Some(pos) = pointer.hover_pos() { - let rect = sub_menu.read().rect; - return rect.intersects_ray(pos, pointer.direction().normalized()); - } + if let Some(sub_menu) = self.current_submenu() + && let Some(pos) = pointer.hover_pos() + { + let rect = sub_menu.read().rect; + return rect.intersects_ray(pos, pointer.direction().normalized()); } false } /// Check if pointer is hovering current submenu. fn hovering_current_submenu(&self, pointer: &PointerState) -> bool { - if let Some(sub_menu) = self.current_submenu() { - if let Some(pos) = pointer.hover_pos() { - return sub_menu.read().area_contains(pos); - } + if let Some(sub_menu) = self.current_submenu() + && let Some(pos) = pointer.hover_pos() + { + return sub_menu.read().area_contains(pos); } false } diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index da248a0f5..0405ca5da 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -316,74 +316,74 @@ impl LabelSelectionState { let may_select_widget = multi_widget_text_select || selection.primary.widget_id == response.id; - if self.is_dragging && may_select_widget { - if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { - let galley_rect = - global_from_galley * Rect::from_min_size(Pos2::ZERO, galley.size()); - let galley_rect = galley_rect.intersect(ui.clip_rect()); + if self.is_dragging + && may_select_widget + && let Some(pointer_pos) = ui.ctx().pointer_interact_pos() + { + let galley_rect = global_from_galley * Rect::from_min_size(Pos2::ZERO, galley.size()); + let galley_rect = galley_rect.intersect(ui.clip_rect()); - let is_in_same_column = galley_rect - .x_range() - .intersects(self.selection_bbox_last_frame.x_range()); + let is_in_same_column = galley_rect + .x_range() + .intersects(self.selection_bbox_last_frame.x_range()); - let has_reached_primary = - self.has_reached_primary || response.id == selection.primary.widget_id; - let has_reached_secondary = - self.has_reached_secondary || response.id == selection.secondary.widget_id; + let has_reached_primary = + self.has_reached_primary || response.id == selection.primary.widget_id; + let has_reached_secondary = + self.has_reached_secondary || response.id == selection.secondary.widget_id; - let new_primary = if response.contains_pointer() { - // Dragging into this widget - easy case: - Some(galley.cursor_from_pos((galley_from_global * pointer_pos).to_vec2())) - } else if is_in_same_column - && !self.has_reached_primary - && selection.primary.pos.y <= selection.secondary.pos.y - && pointer_pos.y <= galley_rect.top() - && galley_rect.top() <= selection.secondary.pos.y - { - // The user is dragging the text selection upwards, above the first selected widget (this one): - if DEBUG { - ui.ctx() - .debug_text(format!("Upwards drag; include {:?}", response.id)); - } - Some(galley.begin()) - } else if is_in_same_column - && has_reached_secondary - && has_reached_primary - && selection.secondary.pos.y <= selection.primary.pos.y - && selection.secondary.pos.y <= galley_rect.bottom() - && galley_rect.bottom() <= pointer_pos.y - { - // The user is dragging the text selection downwards, below this widget. - // We move the cursor to the end of this widget, - // (and we may do the same for the next widget too). - if DEBUG { - ui.ctx() - .debug_text(format!("Downwards drag; include {:?}", response.id)); - } - Some(galley.end()) - } else { - None - }; + let new_primary = if response.contains_pointer() { + // Dragging into this widget - easy case: + Some(galley.cursor_from_pos((galley_from_global * pointer_pos).to_vec2())) + } else if is_in_same_column + && !self.has_reached_primary + && selection.primary.pos.y <= selection.secondary.pos.y + && pointer_pos.y <= galley_rect.top() + && galley_rect.top() <= selection.secondary.pos.y + { + // The user is dragging the text selection upwards, above the first selected widget (this one): + if DEBUG { + ui.ctx() + .debug_text(format!("Upwards drag; include {:?}", response.id)); + } + Some(galley.begin()) + } else if is_in_same_column + && has_reached_secondary + && has_reached_primary + && selection.secondary.pos.y <= selection.primary.pos.y + && selection.secondary.pos.y <= galley_rect.bottom() + && galley_rect.bottom() <= pointer_pos.y + { + // The user is dragging the text selection downwards, below this widget. + // We move the cursor to the end of this widget, + // (and we may do the same for the next widget too). + if DEBUG { + ui.ctx() + .debug_text(format!("Downwards drag; include {:?}", response.id)); + } + Some(galley.end()) + } else { + None + }; - if let Some(new_primary) = new_primary { - selection.primary = - WidgetTextCursor::new(response.id, new_primary, global_from_galley, galley); + if let Some(new_primary) = new_primary { + selection.primary = + WidgetTextCursor::new(response.id, new_primary, global_from_galley, galley); - // We don't want the latency of `drag_started`. - let drag_started = ui.input(|i| i.pointer.any_pressed()); - if drag_started { - if selection.layer_id == response.layer_id { - if ui.input(|i| i.modifiers.shift) { - // A continuation of a previous selection. - } else { - // A new selection in the same layer. - selection.secondary = selection.primary; - } + // We don't want the latency of `drag_started`. + let drag_started = ui.input(|i| i.pointer.any_pressed()); + if drag_started { + if selection.layer_id == response.layer_id { + if ui.input(|i| i.modifiers.shift) { + // A continuation of a previous selection. } else { - // A new selection in a new layer. - selection.layer_id = response.layer_id; + // A new selection in the same layer. selection.secondary = selection.primary; } + } else { + // A new selection in a new layer. + selection.layer_id = response.layer_id; + selection.secondary = selection.primary; } } } @@ -511,26 +511,26 @@ impl LabelSelectionState { let old_range = cursor_state.range(galley); - if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { - if response.contains_pointer() { - let cursor_at_pointer = - galley.cursor_from_pos((galley_from_global * pointer_pos).to_vec2()); + if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() + && response.contains_pointer() + { + let cursor_at_pointer = + galley.cursor_from_pos((galley_from_global * pointer_pos).to_vec2()); - // This is where we handle start-of-drag and double-click-to-select. - // Actual drag-to-select happens elsewhere. - let dragged = false; - cursor_state.pointer_interaction(ui, response, cursor_at_pointer, galley, dragged); - } + // This is where we handle start-of-drag and double-click-to-select. + // Actual drag-to-select happens elsewhere. + let dragged = false; + cursor_state.pointer_interaction(ui, response, cursor_at_pointer, galley, dragged); } 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 |= galley_rect; - if let Some(selection) = &self.selection { - if selection.primary.widget_id == response.id { - process_selection_key_events(ui.ctx(), galley, response.id, &mut cursor_range); - } + if let Some(selection) = &self.selection + && selection.primary.widget_id == response.id + { + process_selection_key_events(ui.ctx(), galley, response.id, &mut cursor_range); } if got_copy_event(ui.ctx()) { diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 1e5f46129..13cf55051 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -705,74 +705,74 @@ impl ViewportBuilder { let mut commands = Vec::new(); - if let Some(new_title) = new_title { - if Some(&new_title) != self.title.as_ref() { - self.title = Some(new_title.clone()); - commands.push(ViewportCommand::Title(new_title)); - } + if let Some(new_title) = new_title + && Some(&new_title) != self.title.as_ref() + { + self.title = Some(new_title.clone()); + commands.push(ViewportCommand::Title(new_title)); } - if let Some(new_position) = new_position { - if Some(new_position) != self.position { - self.position = Some(new_position); - commands.push(ViewportCommand::OuterPosition(new_position)); - } + if let Some(new_position) = new_position + && Some(new_position) != self.position + { + self.position = Some(new_position); + commands.push(ViewportCommand::OuterPosition(new_position)); } - if let Some(new_inner_size) = new_inner_size { - if Some(new_inner_size) != self.inner_size { - self.inner_size = Some(new_inner_size); - commands.push(ViewportCommand::InnerSize(new_inner_size)); - } + if let Some(new_inner_size) = new_inner_size + && Some(new_inner_size) != self.inner_size + { + self.inner_size = Some(new_inner_size); + commands.push(ViewportCommand::InnerSize(new_inner_size)); } - if let Some(new_min_inner_size) = new_min_inner_size { - if Some(new_min_inner_size) != self.min_inner_size { - self.min_inner_size = Some(new_min_inner_size); - commands.push(ViewportCommand::MinInnerSize(new_min_inner_size)); - } + if let Some(new_min_inner_size) = new_min_inner_size + && Some(new_min_inner_size) != self.min_inner_size + { + self.min_inner_size = Some(new_min_inner_size); + commands.push(ViewportCommand::MinInnerSize(new_min_inner_size)); } - if let Some(new_max_inner_size) = new_max_inner_size { - if Some(new_max_inner_size) != self.max_inner_size { - self.max_inner_size = Some(new_max_inner_size); - commands.push(ViewportCommand::MaxInnerSize(new_max_inner_size)); - } + if let Some(new_max_inner_size) = new_max_inner_size + && Some(new_max_inner_size) != self.max_inner_size + { + self.max_inner_size = Some(new_max_inner_size); + commands.push(ViewportCommand::MaxInnerSize(new_max_inner_size)); } - if let Some(new_fullscreen) = new_fullscreen { - if Some(new_fullscreen) != self.fullscreen { - self.fullscreen = Some(new_fullscreen); - commands.push(ViewportCommand::Fullscreen(new_fullscreen)); - } + if let Some(new_fullscreen) = new_fullscreen + && Some(new_fullscreen) != self.fullscreen + { + self.fullscreen = Some(new_fullscreen); + commands.push(ViewportCommand::Fullscreen(new_fullscreen)); } - if let Some(new_maximized) = new_maximized { - if Some(new_maximized) != self.maximized { - self.maximized = Some(new_maximized); - commands.push(ViewportCommand::Maximized(new_maximized)); - } + if let Some(new_maximized) = new_maximized + && Some(new_maximized) != self.maximized + { + self.maximized = Some(new_maximized); + commands.push(ViewportCommand::Maximized(new_maximized)); } - if let Some(new_resizable) = new_resizable { - if Some(new_resizable) != self.resizable { - self.resizable = Some(new_resizable); - commands.push(ViewportCommand::Resizable(new_resizable)); - } + if let Some(new_resizable) = new_resizable + && Some(new_resizable) != self.resizable + { + self.resizable = Some(new_resizable); + commands.push(ViewportCommand::Resizable(new_resizable)); } - if let Some(new_transparent) = new_transparent { - if Some(new_transparent) != self.transparent { - self.transparent = Some(new_transparent); - commands.push(ViewportCommand::Transparent(new_transparent)); - } + if let Some(new_transparent) = new_transparent + && Some(new_transparent) != self.transparent + { + self.transparent = Some(new_transparent); + commands.push(ViewportCommand::Transparent(new_transparent)); } - if let Some(new_decorations) = new_decorations { - if Some(new_decorations) != self.decorations { - self.decorations = Some(new_decorations); - commands.push(ViewportCommand::Decorations(new_decorations)); - } + if let Some(new_decorations) = new_decorations + && Some(new_decorations) != self.decorations + { + self.decorations = Some(new_decorations); + commands.push(ViewportCommand::Decorations(new_decorations)); } if let Some(new_icon) = new_icon { @@ -787,25 +787,25 @@ impl ViewportBuilder { } } - if let Some(new_visible) = new_visible { - if Some(new_visible) != self.visible { - self.visible = Some(new_visible); - commands.push(ViewportCommand::Visible(new_visible)); - } + if let Some(new_visible) = new_visible + && Some(new_visible) != self.visible + { + self.visible = Some(new_visible); + commands.push(ViewportCommand::Visible(new_visible)); } - if let Some(new_mouse_passthrough) = new_mouse_passthrough { - if Some(new_mouse_passthrough) != self.mouse_passthrough { - self.mouse_passthrough = Some(new_mouse_passthrough); - commands.push(ViewportCommand::MousePassthrough(new_mouse_passthrough)); - } + if let Some(new_mouse_passthrough) = new_mouse_passthrough + && Some(new_mouse_passthrough) != self.mouse_passthrough + { + self.mouse_passthrough = Some(new_mouse_passthrough); + commands.push(ViewportCommand::MousePassthrough(new_mouse_passthrough)); } - if let Some(new_window_level) = new_window_level { - if Some(new_window_level) != self.window_level { - self.window_level = Some(new_window_level); - commands.push(ViewportCommand::WindowLevel(new_window_level)); - } + if let Some(new_window_level) = new_window_level + && Some(new_window_level) != self.window_level + { + self.window_level = Some(new_window_level); + commands.push(ViewportCommand::WindowLevel(new_window_level)); } // -------------------------------------------------------------- diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 1b585dab4..c0364e7ee 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -573,41 +573,39 @@ impl TextEdit<'_> { let text_clip_rect = rect; let painter = ui.painter_at(text_clip_rect.expand(1.0)); // expand to avoid clipping cursor - if interactive { - if let Some(pointer_pos) = response.interact_pointer_pos() { - if response.hovered() && text.is_mutable() { - ui.output_mut(|o| o.mutable_text_under_cursor = true); - } + if interactive && let Some(pointer_pos) = response.interact_pointer_pos() { + if response.hovered() && text.is_mutable() { + ui.output_mut(|o| o.mutable_text_under_cursor = true); + } - // TODO(emilk): drag selected text to either move or clone (ctrl on windows, alt on mac) + // TODO(emilk): drag selected text to either move or clone (ctrl on windows, alt on mac) - let cursor_at_pointer = - galley.cursor_from_pos(pointer_pos - rect.min + state.text_offset); + let cursor_at_pointer = + galley.cursor_from_pos(pointer_pos - rect.min + state.text_offset); - if ui.visuals().text_cursor.preview - && response.hovered() - && ui.input(|i| i.pointer.is_moving()) - { - // text cursor preview: - let cursor_rect = TSTransform::from_translation(rect.min.to_vec2()) - * cursor_rect(&galley, &cursor_at_pointer, row_height); - text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect); - } + if ui.visuals().text_cursor.preview + && response.hovered() + && ui.input(|i| i.pointer.is_moving()) + { + // text cursor preview: + let cursor_rect = TSTransform::from_translation(rect.min.to_vec2()) + * cursor_rect(&galley, &cursor_at_pointer, row_height); + text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect); + } - let is_being_dragged = ui.ctx().is_being_dragged(response.id); - let did_interact = state.cursor.pointer_interaction( - ui, - &response, - cursor_at_pointer, - &galley, - is_being_dragged, - ); + let is_being_dragged = ui.ctx().is_being_dragged(response.id); + let did_interact = state.cursor.pointer_interaction( + ui, + &response, + cursor_at_pointer, + &galley, + is_being_dragged, + ); - if did_interact || response.clicked() { - ui.memory_mut(|mem| mem.request_focus(response.id)); + if did_interact || response.clicked() { + ui.memory_mut(|mem| mem.request_focus(response.id)); - state.last_interaction_time = ui.ctx().input(|i| i.time); - } + state.last_interaction_time = ui.ctx().input(|i| i.time); } } @@ -718,11 +716,9 @@ impl TextEdit<'_> { let has_focus = ui.memory(|mem| mem.has_focus(id)); - if has_focus { - if let Some(cursor_range) = state.cursor.range(&galley) { - // Add text selection rectangles to the galley: - paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None); - } + if has_focus && let Some(cursor_range) = state.cursor.range(&galley) { + // Add text selection rectangles to the galley: + paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None); } if !clip_text { @@ -762,50 +758,47 @@ impl TextEdit<'_> { painter.galley(galley_pos, galley.clone(), text_color); - if has_focus { - if let Some(cursor_range) = state.cursor.range(&galley) { - let primary_cursor_rect = - cursor_rect(&galley, &cursor_range.primary, row_height) - .translate(galley_pos.to_vec2()); + if has_focus && let Some(cursor_range) = state.cursor.range(&galley) { + let primary_cursor_rect = cursor_rect(&galley, &cursor_range.primary, row_height) + .translate(galley_pos.to_vec2()); + if response.changed() || selection_changed { + // Scroll to keep primary cursor in view: + ui.scroll_to_rect(primary_cursor_rect + margin, None); + } + + if text.is_mutable() && interactive { + let now = ui.ctx().input(|i| i.time); if response.changed() || selection_changed { - // Scroll to keep primary cursor in view: - ui.scroll_to_rect(primary_cursor_rect + margin, None); + state.last_interaction_time = now; } - if text.is_mutable() && interactive { - let now = ui.ctx().input(|i| i.time); - if response.changed() || selection_changed { - state.last_interaction_time = now; - } + // Only show (and blink) cursor if the egui viewport has focus. + // This is for two reasons: + // * Don't give the impression that the user can type into a window without focus + // * Don't repaint the ui because of a blinking cursor in an app that is not in focus + let viewport_has_focus = ui.ctx().input(|i| i.focused); + if viewport_has_focus { + text_selection::visuals::paint_text_cursor( + ui, + &painter, + primary_cursor_rect, + now - state.last_interaction_time, + ); + } - // Only show (and blink) cursor if the egui viewport has focus. - // This is for two reasons: - // * Don't give the impression that the user can type into a window without focus - // * Don't repaint the ui because of a blinking cursor in an app that is not in focus - let viewport_has_focus = ui.ctx().input(|i| i.focused); - if viewport_has_focus { - text_selection::visuals::paint_text_cursor( - ui, - &painter, - primary_cursor_rect, - now - state.last_interaction_time, - ); - } + // Set IME output (in screen coords) when text is editable and visible + let to_global = ui + .ctx() + .layer_transform_to_global(ui.layer_id()) + .unwrap_or_default(); - // Set IME output (in screen coords) when text is editable and visible - let to_global = ui - .ctx() - .layer_transform_to_global(ui.layer_id()) - .unwrap_or_default(); - - ui.ctx().output_mut(|o| { - o.ime = Some(crate::output::IMEOutput { - rect: to_global * rect, - cursor_rect: to_global * primary_cursor_rect, - }); + ui.ctx().output_mut(|o| { + o.ime = Some(crate::output::IMEOutput { + rect: to_global * rect, + cursor_rect: to_global * primary_cursor_rect, }); - } + }); } } } diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index 16e380e81..c341d2385 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -64,11 +64,11 @@ impl eframe::App for ImageViewer { } #[cfg(not(target_arch = "wasm32"))] - if ui.button("file…").clicked() { - if let Some(path) = rfd::FileDialog::new().pick_file() { - self.uri_edit_text = format!("file://{}", path.display()); - self.current_uri = self.uri_edit_text.clone(); - } + if ui.button("file…").clicked() + && let Some(path) = rfd::FileDialog::new().pick_file() + { + self.uri_edit_text = format!("file://{}", path.display()); + self.current_uri = self.uri_edit_text.clone(); } }); }); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index eb901010a..9f9cb6a87 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -194,10 +194,10 @@ impl WrapApp { }; #[cfg(feature = "persistence")] - if let Some(storage) = cc.storage { - if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) { - slf.state = state; - } + if let Some(storage) = cc.storage + && let Some(state) = eframe::get_value(storage, eframe::APP_KEY) + { + slf.state = state; } slf 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 9ee660896..8033539dd 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -421,10 +421,11 @@ mod tests { } fn remove_leading_emoji(full_name: &str) -> &str { - if let Some((start, name)) = full_name.split_once(' ') { - if start.len() <= 4 && start.bytes().next().is_some_and(|byte| byte >= 128) { - return name; - } + if let Some((start, name)) = full_name.split_once(' ') + && start.len() <= 4 + && start.bytes().next().is_some_and(|byte| byte >= 128) + { + return name; } full_name } diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 5a55569b7..a347df502 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -336,5 +336,5 @@ fn long_text(row_index: usize) -> String { } fn thick_row(row_index: usize) -> bool { - row_index % 6 == 0 + row_index.is_multiple_of(6) } diff --git a/crates/egui_demo_lib/src/demo/tests/clipboard_test.rs b/crates/egui_demo_lib/src/demo/tests/clipboard_test.rs index e602d046c..fb4cf0906 100644 --- a/crates/egui_demo_lib/src/demo/tests/clipboard_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/clipboard_test.rs @@ -67,10 +67,9 @@ impl crate::View for ClipboardTest { if let Ok(egui::load::ImagePoll::Ready { image }) = ui.ctx().try_load_image(&uri, Default::default()) + && ui.button("📋").clicked() { - if ui.button("📋").clicked() { - ui.ctx().copy_image((*image).clone()); - } + ui.ctx().copy_image((*image).clone()); } }); diff --git a/crates/egui_demo_lib/src/demo/tests/input_event_history.rs b/crates/egui_demo_lib/src/demo/tests/input_event_history.rs index 9564e1b6d..25a242089 100644 --- a/crates/egui_demo_lib/src/demo/tests/input_event_history.rs +++ b/crates/egui_demo_lib/src/demo/tests/input_event_history.rs @@ -12,11 +12,11 @@ struct DeduplicatedHistory { impl DeduplicatedHistory { fn add(&mut self, summary: String, full: String) { - if let Some(entry) = self.history.back_mut() { - if entry.summary == summary { - entry.entries.push(full); - return; - } + if let Some(entry) = self.history.back_mut() + && entry.summary == summary + { + entry.entries.push(full); + return; } self.history.push_back(HistoryEntry { summary, diff --git a/crates/egui_demo_lib/src/demo/tests/input_test.rs b/crates/egui_demo_lib/src/demo/tests/input_test.rs index f6d3a310f..896716686 100644 --- a/crates/egui_demo_lib/src/demo/tests/input_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/input_test.rs @@ -10,11 +10,11 @@ struct DeduplicatedHistory { impl DeduplicatedHistory { fn add(&mut self, text: String) { - if let Some(entry) = self.history.back_mut() { - if entry.text == text { - entry.repeated += 1; - return; - } + if let Some(entry) = self.history.back_mut() + && entry.text == text + { + entry.repeated += 1; + return; } self.history.push_back(HistoryEntry { text, repeated: 1 }); if self.history.len() > 100 { diff --git a/crates/egui_demo_lib/src/demo/text_edit.rs b/crates/egui_demo_lib/src/demo/text_edit.rs index a36ad6837..345c88efe 100644 --- a/crates/egui_demo_lib/src/demo/text_edit.rs +++ b/crates/egui_demo_lib/src/demo/text_edit.rs @@ -65,20 +65,20 @@ impl crate::View for TextEditDemo { egui::Label::new("Press ctrl+Y to toggle the case of selected text (cmd+Y on Mac)"), ); - if ui.input_mut(|i| i.consume_key(egui::Modifiers::COMMAND, egui::Key::Y)) { - if let Some(text_cursor_range) = output.cursor_range { - use egui::TextBuffer as _; - let selected_chars = text_cursor_range.as_sorted_char_range(); - let selected_text = text.char_range(selected_chars.clone()); - let upper_case = selected_text.to_uppercase(); - let new_text = if selected_text == upper_case { - selected_text.to_lowercase() - } else { - upper_case - }; - text.delete_char_range(selected_chars.clone()); - text.insert_text(&new_text, selected_chars.start); - } + if ui.input_mut(|i| i.consume_key(egui::Modifiers::COMMAND, egui::Key::Y)) + && let Some(text_cursor_range) = output.cursor_range + { + use egui::TextBuffer as _; + let selected_chars = text_cursor_range.as_sorted_char_range(); + let selected_text = text.char_range(selected_chars.clone()); + let upper_case = selected_text.to_uppercase(); + let new_text = if selected_text == upper_case { + selected_text.to_lowercase() + } else { + upper_case + }; + text.delete_char_range(selected_chars.clone()); + text.insert_text(&new_text, selected_chars.start); } ui.horizontal(|ui| { diff --git a/crates/egui_demo_lib/src/demo/undo_redo.rs b/crates/egui_demo_lib/src/demo/undo_redo.rs index 04610031c..5e6aff2ca 100644 --- a/crates/egui_demo_lib/src/demo/undo_redo.rs +++ b/crates/egui_demo_lib/src/demo/undo_redo.rs @@ -60,15 +60,11 @@ impl crate::View for UndoRedoDemo { let undo = ui.add_enabled(can_undo, Button::new("⟲ Undo")).clicked(); let redo = ui.add_enabled(can_redo, Button::new("⟳ Redo")).clicked(); - if undo { - if let Some(undo_text) = self.undoer.undo(&self.state) { - self.state = undo_text.clone(); - } + if undo && let Some(undo_text) = self.undoer.undo(&self.state) { + self.state = undo_text.clone(); } - if redo { - if let Some(redo_text) = self.undoer.redo(&self.state) { - self.state = redo_text.clone(); - } + if redo && let Some(redo_text) = self.undoer.redo(&self.state) { + self.state = redo_text.clone(); } }); 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 976bdd394..9a66b8bc5 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 @@ -96,13 +96,13 @@ impl EasyMarkEditor { ui.add(egui::TextEdit::multiline(code).desired_width(f32::INFINITY)) }; - if let Some(mut state) = TextEdit::load_state(ui.ctx(), response.id) { - if let Some(mut ccursor_range) = state.cursor.char_range() { - let any_change = shortcuts(ui, code, &mut ccursor_range); - if any_change { - state.cursor.set_char_range(Some(ccursor_range)); - state.store(ui.ctx(), response.id); - } + if let Some(mut state) = TextEdit::load_state(ui.ctx(), response.id) + && let Some(mut ccursor_range) = state.cursor.char_range() + { + let any_change = shortcuts(ui, code, &mut ccursor_range); + if any_change { + state.cursor.set_char_range(Some(ccursor_range)); + state.store(ui.ctx(), response.id); } } } diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs index ed3ebe7f9..66c0dc04f 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs @@ -113,19 +113,19 @@ impl<'a> Parser<'a> { // ```{language}\n{code}``` fn code_block(&mut self) -> Option> { - if let Some(language_start) = self.s.strip_prefix("```") { - if let Some(newline) = language_start.find('\n') { - let language = &language_start[..newline]; - let code_start = &language_start[newline + 1..]; - if let Some(end) = code_start.find("\n```") { - let code = &code_start[..end].trim(); - self.s = &code_start[end + 4..]; - self.start_of_line = false; - return Some(Item::CodeBlock(language, code)); - } else { - self.s = ""; - return Some(Item::CodeBlock(language, code_start)); - } + if let Some(language_start) = self.s.strip_prefix("```") + && let Some(newline) = language_start.find('\n') + { + let language = &language_start[..newline]; + let code_start = &language_start[newline + 1..]; + if let Some(end) = code_start.find("\n```") { + let code = &code_start[..end].trim(); + self.s = &code_start[end + 4..]; + self.start_of_line = false; + return Some(Item::CodeBlock(language, code)); + } else { + self.s = ""; + return Some(Item::CodeBlock(language, code_start)); } } None @@ -171,14 +171,14 @@ impl<'a> Parser<'a> { let this_line = &self.s[..self.s.find('\n').unwrap_or(self.s.len())]; if let Some(bracket_end) = this_line.find(']') { let text = &this_line[1..bracket_end]; - if this_line[bracket_end + 1..].starts_with('(') { - if let Some(parens_end) = this_line[bracket_end + 2..].find(')') { - let parens_end = bracket_end + 2 + parens_end; - let url = &self.s[bracket_end + 2..parens_end]; - self.s = &self.s[parens_end + 1..]; - self.start_of_line = false; - return Some(Item::Hyperlink(self.style, text, url)); - } + if this_line[bracket_end + 1..].starts_with('(') + && let Some(parens_end) = this_line[bracket_end + 2..].find(')') + { + let parens_end = bracket_end + 2 + parens_end; + let url = &self.s[bracket_end + 2..parens_end]; + self.s = &self.s[parens_end + 1..]; + self.start_of_line = false; + return Some(Item::Hyperlink(self.style, text, url)); } } } diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 3acb6b582..6d735f688 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -161,12 +161,12 @@ impl ImageLoader for ImageCrateLoader { match ctx.try_load_bytes(uri) { Ok(BytesPoll::Ready { bytes, mime, .. }) => { // (2) - if let Some(mime) = mime { - if !is_supported_mime(&mime) { - return Err(LoadError::FormatNotSupported { - detected_format: Some(mime), - }); - } + if let Some(mime) = mime + && !is_supported_mime(&mime) + { + return Err(LoadError::FormatNotSupported { + detected_format: Some(mime), + }); } load_image(ctx, uri, &self.cache, &bytes) } diff --git a/crates/egui_extras/src/sizing.rs b/crates/egui_extras/src/sizing.rs index 7f32a84e0..71fd3b1c3 100644 --- a/crates/egui_extras/src/sizing.rs +++ b/crates/egui_extras/src/sizing.rs @@ -144,11 +144,11 @@ impl Sizing { let mut remainder_length = length - sum_non_remainder; let avg_remainder_length = 0.0f32.max(remainder_length / num_remainders as f32).floor(); for &size in &self.sizes { - if let Size::Remainder { range } = size { - if avg_remainder_length < range.min { - remainder_length -= range.min; - num_remainders -= 1; - } + if let Size::Remainder { range } = size + && avg_remainder_length < range.min + { + remainder_length -= range.min; + num_remainders -= 1; } } if num_remainders > 0 { diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 385e4197d..a984226ae 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -472,10 +472,10 @@ impl<'a> TableBuilder<'a> { for (i, column) in columns.iter_mut().enumerate() { let column_resize_id = ui.id().with("resize_column").with(i); - if let Some(response) = ui.ctx().read_response(column_resize_id) { - if response.double_clicked() { - column.auto_size_this_frame = true; - } + if let Some(response) = ui.ctx().read_response(column_resize_id) + && response.double_clicked() + { + column.auto_size_this_frame = true; } } @@ -864,27 +864,27 @@ impl Table<'_> { if column.auto_size_this_frame { // Auto-size: resize to what is needed. *column_width = width_range.clamp(max_used_widths[i]); - } else if resize_response.dragged() { - if let Some(pointer) = ui.ctx().pointer_latest_pos() { - let mut new_width = *column_width + pointer.x - x; - if !column.clip { - // Unless we clip we don't want to shrink below the - // size that was actually used. - // However, we still want to allow content that shrinks when you try - // to make the column less wide, so we allow some small shrinkage each frame: - // big enough to allow shrinking over time, small enough not to look ugly when - // shrinking fails. This is a bit of a HACK around immediate mode. - let max_shrinkage_per_frame = 8.0; - new_width = - new_width.at_least(max_used_widths[i] - max_shrinkage_per_frame); - } - new_width = width_range.clamp(new_width); - - let x = x - *column_width + new_width; - (p0.x, p1.x) = (x, x); - - *column_width = new_width; + } else if resize_response.dragged() + && let Some(pointer) = ui.ctx().pointer_latest_pos() + { + let mut new_width = *column_width + pointer.x - x; + if !column.clip { + // Unless we clip we don't want to shrink below the + // size that was actually used. + // However, we still want to allow content that shrinks when you try + // to make the column less wide, so we allow some small shrinkage each frame: + // big enough to allow shrinking over time, small enough not to look ugly when + // shrinking fails. This is a bit of a HACK around immediate mode. + let max_shrinkage_per_frame = 8.0; + new_width = + new_width.at_least(max_used_widths[i] - max_shrinkage_per_frame); } + new_width = width_range.clamp(new_width); + + let x = x - *column_width + new_width; + (p0.x, p1.x) = (x, x); + + *column_width = new_width; } let dragging_something_else = @@ -991,7 +991,7 @@ impl<'a> TableBody<'a> { row_index: self.row_index, col_index: 0, height, - striped: self.striped && self.row_index % 2 == 0, + striped: self.striped && self.row_index.is_multiple_of(2), hovered: self.hovered_row_index == Some(self.row_index), selected: false, overline: false, @@ -1073,7 +1073,7 @@ impl<'a> TableBody<'a> { row_index, col_index: 0, height: row_height_sans_spacing, - striped: self.striped && (row_index + self.row_index) % 2 == 0, + striped: self.striped && (row_index + self.row_index).is_multiple_of(2), hovered: self.hovered_row_index == Some(row_index), selected: false, overline: false, @@ -1155,7 +1155,7 @@ impl<'a> TableBody<'a> { row_index, col_index: 0, height: row_height, - striped: self.striped && (row_index + self.row_index) % 2 == 0, + striped: self.striped && (row_index + self.row_index).is_multiple_of(2), hovered: self.hovered_row_index == Some(row_index), selected: false, overline: false, @@ -1178,7 +1178,7 @@ impl<'a> TableBody<'a> { row_index, col_index: 0, height: row_height, - striped: self.striped && (row_index + self.row_index) % 2 == 0, + striped: self.striped && (row_index + self.row_index).is_multiple_of(2), hovered: self.hovered_row_index == Some(row_index), overline: false, selected: false, diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 82fd1aab0..4584c3457 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -239,17 +239,17 @@ impl FontImpl { return None; // these will result in the replacement character when rendering } - if c == '\t' { - if let Some(space) = self.glyph_info(' ') { - let glyph_info = GlyphInfo { - advance_width_unscaled: (crate::text::TAB_SIZE as f32 - * space.advance_width_unscaled.0) - .into(), - ..space - }; - self.glyph_info_cache.insert(c, glyph_info); - return Some(glyph_info); - } + if c == '\t' + && let Some(space) = self.glyph_info(' ') + { + let glyph_info = GlyphInfo { + advance_width_unscaled: (crate::text::TAB_SIZE as f32 + * space.advance_width_unscaled.0) + .into(), + ..space + }; + self.glyph_info_cache.insert(c, glyph_info); + return Some(glyph_info); } if c == '\u{2009}' { diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index e65f10701..cf791351a 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -113,13 +113,11 @@ pub fn layout(fonts: &mut FontsImpl, pixels_per_point: f32, job: Arc) let mut elided = false; let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided); - if elided { - if let Some(last_placed) = rows.last_mut() { - let last_row = Arc::make_mut(&mut last_placed.row); - replace_last_glyph_with_overflow_character(fonts, pixels_per_point, &job, last_row); - if let Some(last) = last_row.glyphs.last() { - last_row.size.x = last.max_x(); - } + if elided && let Some(last_placed) = rows.last_mut() { + let last_row = Arc::make_mut(&mut last_placed.row); + replace_last_glyph_with_overflow_character(fonts, pixels_per_point, &job, last_row); + if let Some(last) = last_row.glyphs.last() { + last_row.size.x = last.max_x(); } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index d1656efb2..1adcc515e 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -957,15 +957,15 @@ impl Galley { // Vertical margin around galley improves text selection UX const VMARGIN: f32 = 5.0; - if let Some(first_row) = self.rows.first() { - if pos.y < first_row.min_y() - VMARGIN { - return self.begin(); - } + if let Some(first_row) = self.rows.first() + && pos.y < first_row.min_y() - VMARGIN + { + return self.begin(); } - if let Some(last_row) = self.rows.last() { - if last_row.max_y() + VMARGIN < pos.y { - return self.end(); - } + if let Some(last_row) = self.rows.last() + && last_row.max_y() + VMARGIN < pos.y + { + return self.end(); } let mut best_y_dist = f32::INFINITY; diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index ac4f05251..f552adb26 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -29,10 +29,10 @@ impl eframe::App for MyApp { egui::CentralPanel::default().show(ctx, |ui| { ui.label("Drag-and-drop files onto the window!"); - if ui.button("Open file…").clicked() { - if let Some(path) = rfd::FileDialog::new().pick_file() { - self.picked_path = Some(path.display().to_string()); - } + if ui.button("Open file…").clicked() + && let Some(path) = rfd::FileDialog::new().pick_file() + { + self.picked_path = Some(path.display().to_string()); } if let Some(picked_path) = &self.picked_path { diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 6851b736e..ccec8c92a 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -56,24 +56,24 @@ impl Application { impl eframe::App for Application { fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { - if let Some(request_at) = self.request_at { - if request_at < SystemTime::now() { - self.request_at = None; - ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention(self.attention)); - if self.auto_reset { - self.auto_reset = false; - self.reset_at = Some(SystemTime::now() + Self::attention_reset_timeout()); - } + if let Some(request_at) = self.request_at + && request_at < SystemTime::now() + { + self.request_at = None; + ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention(self.attention)); + if self.auto_reset { + self.auto_reset = false; + self.reset_at = Some(SystemTime::now() + Self::attention_reset_timeout()); } } - if let Some(reset_at) = self.reset_at { - if reset_at < SystemTime::now() { - self.reset_at = None; - ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention( - UserAttentionType::Reset, - )); - } + if let Some(reset_at) = self.reset_at + && reset_at < SystemTime::now() + { + self.reset_at = None; + ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention( + UserAttentionType::Reset, + )); } CentralPanel::default().show(ctx, |ui| { From 6579bb910b5f7b555ea1b74f4785bafcbb9d712d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Oct 2025 20:09:48 +0200 Subject: [PATCH 247/388] Remove `log` feature (#7583) --- README.md | 9 +-------- crates/eframe/Cargo.toml | 5 +---- crates/egui-winit/Cargo.toml | 2 +- crates/egui/Cargo.toml | 5 +---- crates/egui/src/context.rs | 9 +-------- crates/egui/src/layers.rs | 1 - crates/egui/src/load/bytes_loader.rs | 3 --- crates/egui/src/load/texture_loader.rs | 2 -- crates/egui/src/os.rs | 1 - crates/egui/src/ui.rs | 2 -- crates/egui/src/util/id_type_map.rs | 1 - crates/egui_demo_app/Cargo.toml | 8 ++++++-- crates/epaint/Cargo.toml | 5 +---- crates/epaint/src/tessellator.rs | 10 ++++------ crates/epaint/src/text/fonts.rs | 1 - crates/epaint/src/texture_atlas.rs | 1 - 16 files changed, 16 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 0bcff697e..c1d25f913 100644 --- a/README.md +++ b/README.md @@ -143,14 +143,7 @@ Light Theme: ## Dependencies -`egui` has a minimal set of default dependencies: - -* [`ab_glyph`](https://crates.io/crates/ab_glyph) -* [`ahash`](https://crates.io/crates/ahash) -* [`bitflags`](https://crates.io/crates/bitflags) -* [`nohash-hasher`](https://crates.io/crates/nohash-hasher) -* [`parking_lot`](https://crates.io/crates/parking_lot) - +`egui` has a minimal set of default dependencies. Heavier dependencies are kept out of `egui`, even as opt-in. All code in `egui` is Wasm-friendly (even outside a browser). diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index b37a19899..7fd3b9e3a 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -124,10 +124,7 @@ x11 = [ __screenshot = [] [dependencies] -egui = { workspace = true, default-features = false, features = [ - "bytemuck", - "log", -] } +egui = { workspace = true, default-features = false, features = ["bytemuck"] } ahash.workspace = true document-features.workspace = true diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index f2932ab5b..3f4891f9e 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -55,7 +55,7 @@ wayland = ["winit/wayland", "bytemuck"] x11 = ["winit/x11", "bytemuck"] [dependencies] -egui = { workspace = true, default-features = false, features = ["log"] } +egui = { workspace = true, default-features = false } log.workspace = true profiling.workspace = true diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 32951c05b..c82ae5618 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -48,9 +48,6 @@ color-hex = ["epaint/color-hex"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["epaint/default_fonts"] -## Turn on the `log` feature, that makes egui log some errors using the [`log`](https://docs.rs/log) crate. -log = ["dep:log", "epaint/log"] - ## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). mint = ["epaint/mint"] @@ -80,6 +77,7 @@ epaint = { workspace = true, default-features = false } ahash.workspace = true bitflags.workspace = true +log.workspace = true nohash-hasher.workspace = true profiling.workspace = true smallvec.workspace = true @@ -93,6 +91,5 @@ backtrace = { workspace = true, optional = true } ## Enable this when generating docs. document-features = { workspace = true, optional = true } -log = { workspace = true, optional = true } ron = { workspace = true, optional = true } serde = { workspace = true, optional = true, features = ["derive", "rc"] } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index b496612e8..b037ce7df 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -535,7 +535,7 @@ impl ContextImpl { // New font definition loaded, so we need to reload all fonts. self.fonts = None; self.font_definitions = font_definitions; - #[cfg(feature = "log")] + log::trace!("Loading new font definitions"); } @@ -559,7 +559,6 @@ impl ContextImpl { .insert(font.name, Arc::new(font.data)); } - #[cfg(feature = "log")] log::trace!("Adding new fonts"); } @@ -568,7 +567,6 @@ impl ContextImpl { let mut is_new = false; let fonts = self.fonts.get_or_insert_with(|| { - #[cfg(feature = "log")] log::trace!("Creating new Fonts"); is_new = true; @@ -806,7 +804,6 @@ 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 @@ -1819,7 +1816,6 @@ impl Context { let cause = RepaintCause::new_reason(reason); self.output_mut(|o| o.request_discard_reasons.push(cause)); - #[cfg(feature = "log")] log::trace!( "request_discard: {}", if self.will_discard() { @@ -2525,7 +2521,6 @@ impl ContextImpl { let parent = *self.viewport_parents.entry(id).or_default(); if !all_viewport_ids.contains(&parent) { - #[cfg(feature = "log")] log::debug!( "Removing viewport {:?} ({:?}): the parent is gone", id, @@ -2538,7 +2533,6 @@ impl ContextImpl { let is_our_child = parent == ended_viewport_id && id != ViewportId::ROOT; if is_our_child { if !viewport.used { - #[cfg(feature = "log")] log::debug!( "Removing viewport {:?} ({:?}): it was never used this pass", id, @@ -2637,7 +2631,6 @@ impl Context { let texture_atlas = if let Some(fonts) = ctx.fonts.as_ref() { fonts.texture_atlas() } else { - #[cfg(feature = "log")] log::warn!("No font size matching {pixels_per_point} pixels per point found."); ctx.fonts .iter() diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 4aadff877..96ce46489 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -154,7 +154,6 @@ impl PaintList { #[inline(always)] pub fn set(&mut self, idx: ShapeIdx, clip_rect: Rect, shape: Shape) { if self.0.len() <= idx.0 { - #[cfg(feature = "log")] log::warn!("Index {} is out of bounds for PaintList", idx.0); return; } diff --git a/crates/egui/src/load/bytes_loader.rs b/crates/egui/src/load/bytes_loader.rs index 9f0c60356..6547a1e1d 100644 --- a/crates/egui/src/load/bytes_loader.rs +++ b/crates/egui/src/load/bytes_loader.rs @@ -19,7 +19,6 @@ impl DefaultBytesLoader { .or_insert_with_key(|_uri| { let bytes: Bytes = bytes.into(); - #[cfg(feature = "log")] log::trace!("loaded {} bytes for uri {_uri:?}", bytes.len()); bytes @@ -53,14 +52,12 @@ impl BytesLoader for DefaultBytesLoader { } fn forget(&self, uri: &str) { - #[cfg(feature = "log")] log::trace!("forget {uri:?}"); self.cache.lock().remove(uri); } fn forget_all(&self) { - #[cfg(feature = "log")] log::trace!("forget all"); self.cache.lock().clear(); diff --git a/crates/egui/src/load/texture_loader.rs b/crates/egui/src/load/texture_loader.rs index 39d8ff940..e75b05070 100644 --- a/crates/egui/src/load/texture_loader.rs +++ b/crates/egui/src/load/texture_loader.rs @@ -102,14 +102,12 @@ impl TextureLoader for DefaultTextureLoader { } fn forget(&self, uri: &str) { - #[cfg(feature = "log")] log::trace!("forget {uri:?}"); self.cache.lock().retain(|key, _value| key.uri != uri); } fn forget_all(&self) { - #[cfg(feature = "log")] log::trace!("forget all"); self.cache.lock().clear(); diff --git a/crates/egui/src/os.rs b/crates/egui/src/os.rs index a9b4a874c..bc76ff0d6 100644 --- a/crates/egui/src/os.rs +++ b/crates/egui/src/os.rs @@ -68,7 +68,6 @@ impl OperatingSystem { { Self::Nix } else { - #[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 diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 553be6f14..a08148a89 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1248,7 +1248,6 @@ impl Ui { if let Some(tag) = tag { tag.set_close(); } else { - #[cfg(feature = "log")] log::warn!("Called ui.close() on a Ui that has no closable parent."); } } @@ -1277,7 +1276,6 @@ impl Ui { if let Some(tag) = tag { tag.set_close(); } else { - #[cfg(feature = "log")] log::warn!("Called ui.close_kind({ui_kind:?}) on ui with no such closable parent."); } } diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index 366ad5190..395d1b6ca 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -291,7 +291,6 @@ fn from_ron_str(ron: &str) -> Option { match ron::from_str::(ron) { Ok(value) => Some(value), Err(_err) => { - #[cfg(feature = "log")] log::warn!( "egui: Failed to deserialize {} from memory: {}, ron error: {:?}", std::any::type_name::(), diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index ff441af8b..97e6806ff 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -53,7 +53,7 @@ chrono = { version = "0.4", default-features = false, features = [ eframe = { workspace = true, default-features = false, features = [ "web_screen_reader", ] } -egui = { workspace = true, features = ["callstack", "default", "log"] } +egui = { workspace = true, features = ["callstack", "default"] } egui_demo_lib = { workspace = true, features = ["default", "chrono"] } egui_extras = { workspace = true, features = ["default", "image"] } image = { workspace = true, default-features = false, features = [ @@ -70,7 +70,11 @@ puffin = { workspace = true, optional = true } puffin_http = { workspace = true, optional = true } # Enable both WebGL & WebGPU when targeting the web (these features have no effect when not targeting wasm32) # Also enable the default features so we have a supported backend for every platform. -wgpu = { workspace = true, features = ["default", "webgpu", "webgl"], optional = true } +wgpu = { workspace = true, features = [ + "default", + "webgpu", + "webgl", +], optional = true } # feature "http": diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 3cbb4f474..49806be8f 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -44,9 +44,6 @@ color-hex = ["ecolor/color-hex"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["epaint_default_fonts"] -## Turn on the `log` feature, that makes egui log some errors using the [`log`](https://docs.rs/log) crate. -log = ["dep:log"] - ## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). mint = ["emath/mint"] @@ -71,6 +68,7 @@ ecolor.workspace = true ab_glyph = "0.2.11" ahash.workspace = true +log.workspace = true nohash-hasher.workspace = true parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. profiling = { workspace = true} @@ -81,7 +79,6 @@ bytemuck = { workspace = true, optional = true, features = ["derive"] } ## Enable this when generating docs. document-features = { workspace = true, optional = true } -log = { workspace = true, optional = true } rayon = { version = "1.7", optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde) . diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 4670d0b2e..0c4da74d1 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -2000,12 +2000,10 @@ impl Tessellator { } if galley.pixels_per_point != self.pixels_per_point { - let warn = "epaint: WARNING: pixels_per_point (dpi scale) have changed between text layout and tessellation. \ - You must recreate your text shapes if pixels_per_point changes."; - #[cfg(feature = "log")] - log::warn!("{warn}"); - #[cfg(not(feature = "log"))] - println!("{warn}"); + log::warn!( + "epaint: WARNING: pixels_per_point (dpi scale) have changed between text layout and tessellation. \ + You must recreate your text shapes if pixels_per_point changes." + ); } out.vertices.reserve(galley.num_vertices); diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 3efcbe8ea..7f7be2ffa 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -463,7 +463,6 @@ impl CachedFamily { .glyph_info_no_cache_or_fallback(PRIMARY_REPLACEMENT_CHAR, fonts_by_id) .or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR, fonts_by_id)) .unwrap_or_else(|| { - #[cfg(feature = "log")] log::warn!( "Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}. Will use empty glyph." ); diff --git a/crates/epaint/src/texture_atlas.rs b/crates/epaint/src/texture_atlas.rs index 36dd1b48e..6488d9079 100644 --- a/crates/epaint/src/texture_atlas.rs +++ b/crates/epaint/src/texture_atlas.rs @@ -238,7 +238,6 @@ impl TextureAtlas { if required_height > self.max_height() { // This is a bad place to be - we need to start reusing space :/ - #[cfg(feature = "log")] log::warn!("epaint texture atlas overflowed!"); self.cursor = (0, self.image.height() / 3); // Restart a bit down - the top of the atlas has too many important things in it From 096ed1c0cb2d7d624d35e1947ccd014f448cab87 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Oct 2025 20:19:23 +0200 Subject: [PATCH 248/388] Replace cargo check with cargo clippy on ci (#7581) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/rust.yml | 51 ++++++++------------- crates/eframe/src/native/epi_integration.rs | 16 ++++--- crates/egui/src/context.rs | 1 + 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b45f027e0..1d4a9555d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -38,39 +38,26 @@ jobs: - name: Lint vertical spacing run: ./scripts/lint.py - - name: check --all-features - run: cargo check --locked --all-features --all-targets + - run: cargo clippy --locked --all-features --all-targets - - name: check egui_extras --all-features - run: cargo check --locked --all-features -p egui_extras + - run: cargo clippy --locked --all-features -p egui_extras - - name: check default features - run: cargo check --locked --all-targets + - run: cargo clippy --locked --all-targets - - name: check --no-default-features - run: cargo check --locked --no-default-features --lib --all-targets + - run: cargo clippy --locked --no-default-features --lib --all-targets - - name: check eframe --no-default-features - run: cargo check --locked --no-default-features --features x11 --lib -p eframe + - run: cargo clippy --locked --no-default-features --features x11 --lib -p eframe - - name: check egui_extras --no-default-features - run: cargo check --locked --no-default-features --lib -p egui_extras + - run: cargo clippy --locked --no-default-features --lib -p egui_extras - - name: check epaint --no-default-features - run: cargo check --locked --no-default-features --lib -p epaint + - run: cargo clippy --locked --no-default-features --lib -p epaint # Regression test for https://github.com/emilk/egui/issues/4771 - - name: cargo check -p test_egui_extras_compilation - run: cargo check -p test_egui_extras_compilation + - run: cargo clippy -p test_egui_extras_compilation - - name: cargo doc --lib - run: cargo doc --lib --no-deps --all-features + - run: cargo doc --lib --no-deps --all-features - - name: cargo doc --document-private-items - run: cargo doc --document-private-items --no-deps --all-features - - - name: clippy - run: cargo clippy --all-targets --all-features -- -D warnings + - run: cargo doc --document-private-items --no-deps --all-features - name: clippy release run: cargo clippy --all-targets --all-features --release -- -D warnings @@ -93,14 +80,14 @@ jobs: - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - - name: Check wasm32 egui_demo_app - run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown + - name: clippy wasm32 egui_demo_app + run: cargo clippy -p egui_demo_app --lib --target wasm32-unknown-unknown - - name: Check wasm32 egui_demo_app --all-features - run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features + - name: clippy wasm32 egui_demo_app --all-features + run: cargo clippy -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features - - name: Check wasm32 eframe - run: cargo check -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown + - name: clippy wasm32 eframe + run: cargo clippy -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 @@ -222,11 +209,9 @@ jobs: - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - - name: Check all - run: cargo check --all-targets --all-features + - run: cargo clippy --all-targets --all-features - - name: Check hello_world - run: cargo check -p hello_world + - run: cargo clippy -p hello_world # --------------------------------------------------------------------------- diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 69c42427f..1b4c4b664 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -135,7 +135,7 @@ pub fn create_storage(_app_name: &str) -> Option> { None } -#[expect(clippy::unnecessary_wraps)] +#[allow(clippy::allow_attributes, clippy::unnecessary_wraps)] pub fn create_storage_with_file(_file: impl Into) -> Option> { #[cfg(feature = "persistence")] return Some(Box::new( @@ -168,7 +168,7 @@ pub struct EpiIntegration { } impl EpiIntegration { - #[expect(clippy::too_many_arguments)] + #[allow(clippy::allow_attributes, clippy::too_many_arguments)] pub fn new( egui_ctx: egui::Context, window: &winit::window::Window, @@ -325,13 +325,15 @@ impl EpiIntegration { } } - #[allow(clippy::unused_self, clippy::allow_attributes)] - pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) { + pub fn save(&mut self, app: &mut dyn epi::App, window: Option<&winit::window::Window>) { + #[cfg(not(feature = "persistence"))] + let _ = (self, app, window); + #[cfg(feature = "persistence")] if let Some(storage) = self.frame.storage_mut() { profiling::function_scope!(); - if let Some(window) = _window + if let Some(window) = window && self.persist_window { profiling::scope!("native_window"); @@ -341,14 +343,14 @@ impl EpiIntegration { &WindowSettings::from_window(self.egui_ctx.zoom_factor(), window), ); } - if _app.persist_egui_memory() { + if app.persist_egui_memory() { profiling::scope!("egui_memory"); self.egui_ctx .memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem)); } { profiling::scope!("App::save"); - _app.save(storage); + app.save(storage); } profiling::scope!("Storage::flush"); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index b037ce7df..03b46b652 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2497,6 +2497,7 @@ impl ContextImpl { if self.memory.options.repaint_on_widget_change { profiling::scope!("compare-widget-rects"); + #[allow(clippy::allow_attributes, clippy::collapsible_if)] // false positive on wasm if viewport.prev_pass.widgets != viewport.this_pass.widgets { repaint_needed = true; // Some widget has moved } From 427c0766fdd38e75dd422a995c4ba4005fb07800 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 3 Oct 2025 09:54:46 +0200 Subject: [PATCH 249/388] Update wgpu to 27.0.0 (#7580) --- Cargo.lock | 101 ++++++++++++-------- Cargo.toml | 2 +- crates/egui-wgpu/src/setup.rs | 4 +- crates/egui_kittest/src/texture_to_image.rs | 7 +- crates/egui_kittest/src/wgpu.rs | 10 +- deny.toml | 5 +- 6 files changed, 78 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90690bf9e..ed40f3056 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec27574c1baeb7747c802a194566b46b602461e81dc4957949580ea8da695038" dependencies = [ "accesskit", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -60,7 +60,7 @@ checksum = "bf962bfd305aed21133d06128ab3f4a6412031a5b8505534d55af869788af272" dependencies = [ "accesskit", "accesskit_consumer", - "hashbrown", + "hashbrown 0.15.2", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -92,7 +92,7 @@ checksum = "e4cd727229c389e32c1a78fe9f74dc62d7c9fb6eac98cfa1a17efde254fb2d98" dependencies = [ "accesskit", "accesskit_consumer", - "hashbrown", + "hashbrown 0.15.2", "static_assertions", "windows 0.61.1", "windows-core 0.61.0", @@ -1701,6 +1701,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "fontconfig-parser" version = "0.5.7" @@ -2012,7 +2018,7 @@ checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.0", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -2041,7 +2047,16 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "foldhash", + "foldhash 0.1.4", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", ] [[package]] @@ -2297,7 +2312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -2435,9 +2450,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libloading" @@ -2638,9 +2653,9 @@ dependencies = [ [[package]] name = "naga" -version = "26.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" +checksum = "12b2e757b11b47345d44e7760e45458339bc490463d9548cd8651c53ae523153" dependencies = [ "arrayvec", "bit-set 0.8.0", @@ -2649,7 +2664,7 @@ dependencies = [ "cfg_aliases", "codespan-reporting", "half", - "hashbrown", + "hashbrown 0.16.0", "hexf-parse", "indexmap", "libm", @@ -2658,7 +2673,7 @@ dependencies = [ "once_cell", "rustc-hash", "spirv", - "thiserror 2.0.11", + "thiserror 2.0.17", "unicode-ident", ] @@ -3506,7 +3521,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.11", + "thiserror 2.0.17", ] [[package]] @@ -3895,9 +3910,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" @@ -4118,11 +4133,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.17", ] [[package]] @@ -4138,9 +4153,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -4751,16 +4766,16 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "26.0.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" +checksum = "3a355f55850044d897fdaa72199509ff08baaa0c387c4c80599decb5ee86790b" dependencies = [ "arrayvec", "bitflags 2.9.0", "cfg-if", "cfg_aliases", "document-features", - "hashbrown", + "hashbrown 0.16.0", "js-sys", "log", "naga", @@ -4780,17 +4795,18 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "26.0.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9" +checksum = "893764e276cdafec946c7f394f044e283bc8f1e445ab3fea8ad3b6dbc10c0322" dependencies = [ "arrayvec", "bit-set 0.8.0", "bit-vec 0.8.0", "bitflags 2.9.0", + "bytemuck", "cfg_aliases", "document-features", - "hashbrown", + "hashbrown 0.16.0", "indexmap", "log", "naga", @@ -4801,7 +4817,7 @@ dependencies = [ "raw-window-handle", "rustc-hash", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.17", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", "wgpu-core-deps-wasm", @@ -4812,45 +4828,45 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "26.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf" +checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "26.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d" +checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-wasm" -version = "26.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03b9f9e1a50686d315fc6debe4980cc45cd37b0e919351917df494e8fdc8885" +checksum = "9b1027dcf3b027a877e44819df7ceb0e2e98578830f8cd34cd6c3c7c2a7a50b7" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "26.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14" +checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "26.0.4" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df2c64ac282a91ad7662c90bc4a77d4a2135bc0b2a2da5a4d4e267afc034b9e" +checksum = "a753c3dc95e69be3aacfe9c871c5fa2cfa9e35748cdc87de7ba5fc1735b61604" dependencies = [ "android_system_properties", "arrayvec", @@ -4867,7 +4883,7 @@ dependencies = [ "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hashbrown", + "hashbrown 0.16.0", "js-sys", "khronos-egl", "libc", @@ -4877,6 +4893,7 @@ dependencies = [ "naga", "ndk-sys", "objc", + "once_cell", "ordered-float", "parking_lot", "portable-atomic", @@ -4886,7 +4903,7 @@ dependencies = [ "raw-window-handle", "renderdoc-sys", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.17", "wasm-bindgen", "web-sys", "wgpu-types", @@ -4896,15 +4913,15 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "26.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" +checksum = "d67453b02f7adc33c452d17da1c2cad813448221df1547bce9dd4b02d3558538" dependencies = [ "bitflags 2.9.0", "bytemuck", "js-sys", "log", - "thiserror 2.0.11", + "thiserror 2.0.17", "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 7facc3c23..6be41d27d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = "0.3.73" web-time = "1.1.0" # Timekeeping for native and web -wgpu = { version = "26.0.1", default-features = false } +wgpu = { version = "27.0.0", default-features = false } windows-sys = "0.60" winit = { version = "0.30.12", default-features = false } diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index 9e0adb9a6..0aaa8ac94 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -179,15 +179,13 @@ impl Default for WgpuSetupCreateNew { wgpu::DeviceDescriptor { label: Some("egui wgpu device"), - required_features: wgpu::Features::default(), required_limits: wgpu::Limits { // When using a depth buffer, we have to be able to create a texture // large enough for the entire surface, and we want to support 4k+ displays. max_texture_dimension_2d: 8192, ..base_limits }, - memory_hints: wgpu::MemoryHints::default(), - trace: wgpu::Trace::Off, + ..Default::default() } }), } diff --git a/crates/egui_kittest/src/texture_to_image.rs b/crates/egui_kittest/src/texture_to_image.rs index e215cd4ec..5172555fe 100644 --- a/crates/egui_kittest/src/texture_to_image.rs +++ b/crates/egui_kittest/src/texture_to_image.rs @@ -5,6 +5,8 @@ use std::iter; use std::mem::size_of; use std::sync::mpsc::channel; +use crate::wgpu::WAIT_TIMEOUT; + pub(crate) fn texture_to_image(device: &Device, queue: &Queue, texture: &Texture) -> RgbaImage { let buffer_dimensions = BufferDimensions::new(texture.width() as usize, texture.height() as usize); @@ -48,7 +50,10 @@ pub(crate) fn texture_to_image(device: &Device, queue: &Queue, texture: &Texture // Poll the device in a blocking manner so that our future resolves. device - .poll(wgpu::PollType::WaitForSubmissionIndex(submission_index)) + .poll(wgpu::PollType::Wait { + submission_index: Some(submission_index), + timeout: Some(WAIT_TIMEOUT), + }) .expect("Failed to poll device"); receiver.recv().unwrap().unwrap(); diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index 382ea20b4..c64e2f684 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -1,5 +1,5 @@ -use std::iter::once; use std::sync::Arc; +use std::{iter::once, time::Duration}; use egui::TexturesDelta; use egui_wgpu::{RenderState, ScreenDescriptor, WgpuSetup, wgpu}; @@ -7,6 +7,9 @@ use image::RgbaImage; use crate::texture_to_image::texture_to_image; +/// Timeout for waiting on the GPU to finish rendering. +pub(crate) const WAIT_TIMEOUT: Duration = Duration::from_secs(1); + /// Default wgpu setup used for the wgpu renderer. pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup { let mut setup = egui_wgpu::WgpuSetupCreateNew::default(); @@ -205,7 +208,10 @@ impl crate::TestRenderer for WgpuTestRenderer { self.render_state .device - .poll(wgpu::PollType::Wait) + .poll(wgpu::PollType::Wait { + submission_index: None, + timeout: Some(WAIT_TIMEOUT), + }) .map_err(|err| format!("PollError: {err}"))?; Ok(texture_to_image( diff --git a/deny.toml b/deny.toml index 8bf0644ad..305932868 100644 --- a/deny.toml +++ b/deny.toml @@ -57,8 +57,9 @@ skip = [ { name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems ] skip-tree = [ - { name = "rfd" }, # example dependency - { name = "windows" }, # the ecosystem is currently transitioning from 0.58 to 0.61 + { name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15 + { name = "rfd" }, # example dependency + { name = "windows" }, # the ecosystem is currently transitioning from 0.58 to 0.61 ] From f6fe3bff180d8f93ebd4ccf7c29201cf2fb8894d Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 3 Oct 2025 13:05:50 +0200 Subject: [PATCH 250/388] Increate wgpu kittest wait timeout (#7585) --- crates/egui_kittest/src/wgpu.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index c64e2f684..a71a69d76 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -8,7 +8,12 @@ use image::RgbaImage; use crate::texture_to_image::texture_to_image; /// Timeout for waiting on the GPU to finish rendering. -pub(crate) const WAIT_TIMEOUT: Duration = Duration::from_secs(1); +/// +/// Windows will reset native drivers after 2 seconds of being stuck (known was TDR - timeout detection & recovery). +/// However, software rasterizers like lavapipe may not do that and take longer if there's a lot of work in flight. +/// In the end, what we really want to protect here against is undetected errors that lead to device loss +/// and therefore infinite waits it happens occasionally on MacOS/Metal as of writing. +pub(crate) const WAIT_TIMEOUT: Duration = Duration::from_secs(10); /// Default wgpu setup used for the wgpu renderer. pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup { From f0faacc7d18617e8fa0d293be77329fc4bf7aa89 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 6 Oct 2025 15:57:31 +0200 Subject: [PATCH 251/388] Add workflow to accept snapshots via kitdiff (#7577) This adds a new workflow `update_kittest_snapshots.yml` that can be triggered through the [kitdiff](https://github.com/rerun-io/kitdiff) ui when viewing a ci artefact. Also adds a link to kitdiff to view the pr changes to each commit (via the preview build comment) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/preview_build.yml | 14 +++++++ .github/workflows/preview_deploy.yml | 2 + .../workflows/update_kittest_snapshots.yml | 40 +++++++++++++++++++ scripts/accept_snapshots.sh | 14 +++++++ scripts/update_snapshots_from_ci.sh | 18 ++++----- 5 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/update_kittest_snapshots.yml create mode 100755 scripts/accept_snapshots.sh diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index fe37eb8cb..4d6447356 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -17,6 +17,20 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: + + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + env: + URL_SLUG: ${{ github.event.number }}-${{ github.head_ref }} + with: + message: | + Preview is being built... + + Preview will be available at https://egui-pr-preview.github.io/pr/${{ env.URL_SLUG }} + + View snapshot changes at [kitdiff](https://rerun-io.github.io/kitdiff/?url=${{ github.event.pull_request.html_url }}) + comment_tag: 'egui-preview' + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/preview_deploy.yml b/.github/workflows/preview_deploy.yml index 5a97658cd..8fbe8bae7 100644 --- a/.github/workflows/preview_deploy.yml +++ b/.github/workflows/preview_deploy.yml @@ -61,5 +61,7 @@ jobs: message: | Preview available at https://egui-pr-preview.github.io/pr/${{ env.URL_SLUG }} Note that it might take a couple seconds for the update to show up after the preview_build workflow has completed. + + View snapshot changes at [kitdiff](https://rerun-io.github.io/kitdiff/?url=${{ github.event.pull_request.html_url }}) pr_number: ${{ env.PR_NUMBER }} comment_tag: 'egui-preview' diff --git a/.github/workflows/update_kittest_snapshots.yml b/.github/workflows/update_kittest_snapshots.yml new file mode 100644 index 000000000..e06382386 --- /dev/null +++ b/.github/workflows/update_kittest_snapshots.yml @@ -0,0 +1,40 @@ +on: + workflow_dispatch: + inputs: + run_id: + description: 'The run ID that produced the artifact' + required: true + type: string + +permissions: + actions: read + +name: Update kittest snapshots +jobs: + update-snapshots: + name: Update snapshots from artifact + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + lfs: true + # We can't use the workflow token since that would prevent our commit to cause further workflows. + # See https://github.com/stefanzweifel/git-auto-commit-action#commits-made-by-this-action-do-not-trigger-new-workflow-runs + token: '${{ secrets.GITHUB_TOKEN }}' + + - name: Accept snapshots + env: + GH_TOKEN: ${{ github.token }} + RUN_ID: ${{ github.event.inputs.run_id }} + run: ./scripts/update_snapshots_from_ci.sh + + - name: Git status + run: git status + + - uses: stefanzweifel/git-auto-commit-action@v6 + with: + commit_message: 'Update snapshot images' + diff --git a/scripts/accept_snapshots.sh b/scripts/accept_snapshots.sh new file mode 100755 index 000000000..9e02eadbc --- /dev/null +++ b/scripts/accept_snapshots.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# This script moves all {name}.new.png files to {name}.png. +# Its main use is in the update_kittest_snapshots CI job, but you can also use it locally. + +set -eu + +# rename the .new.png files to .png +find . -type d -path "*/tests/snapshots*" | while read dir; do + find "$dir" -type f -name "*.new.png" | while read file; do + mv -f "$file" "${file%.new.png}.png" + done +done + +echo "Done!" diff --git a/scripts/update_snapshots_from_ci.sh b/scripts/update_snapshots_from_ci.sh index c15360365..46262d727 100755 --- a/scripts/update_snapshots_from_ci.sh +++ b/scripts/update_snapshots_from_ci.sh @@ -8,9 +8,12 @@ set -eu BRANCH=$(git rev-parse --abbrev-ref HEAD) -RUN_ID=$(gh run list --branch "$BRANCH" --workflow "Rust" --json databaseId -q '.[0].databaseId') - -echo "Downloading test results from run $RUN_ID from branch $BRANCH" +if [ -z "${RUN_ID:-}" ]; then + RUN_ID=$(gh run list --branch "$BRANCH" --workflow "Rust" --json databaseId -q '.[0].databaseId') + echo "Downloading test results from run $RUN_ID from branch $BRANCH" +else + echo "Using provided RUN_ID: $RUN_ID" +fi # remove any existing .new.png that might have been left behind find . -type d -path "*/tests/snapshots*" | while read dir; do @@ -27,11 +30,4 @@ rsync -a tmp_artefacts/ . rm -r tmp_artefacts -# rename the .new.png files to .png -find . -type d -path "*/tests/snapshots*" | while read dir; do - find "$dir" -type f -name "*.new.png" | while read file; do - mv -f "$file" "${file%.new.png}.png" - done -done - -echo "Done!" +./scripts/accept_snapshots.sh From 65249013c426d4a4d5e208c4a98ea31999d64ba9 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 7 Oct 2025 10:16:35 +0200 Subject: [PATCH 252/388] Fix stuck menu when submenu vanishes (#7589) * Closes https://github.com/rerun-io/rerun/issues/11301 This fixes a bug where a menu could get stuck, not closing at all, when the currently open submenu stops being shown. I also added a way to reproduce this to the demo, as well as a test ensuring that there is no race condition in the fix. --- crates/egui/src/containers/menu.rs | 43 +++++++++-- crates/egui/src/containers/popup.rs | 5 +- crates/egui_demo_lib/src/demo/popups.rs | 34 ++++---- crates/egui_kittest/tests/regression_tests.rs | 77 ++++++++++++++++++- 4 files changed, 137 insertions(+), 22 deletions(-) diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index c83b5297d..f2aaee046 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -152,7 +152,8 @@ impl MenuState { pub fn from_id(ctx: &Context, id: Id, f: impl FnOnce(&mut Self) -> R) -> R { let pass_nr = ctx.cumulative_pass_nr(); ctx.data_mut(|data| { - let state = data.get_temp_mut_or_insert_with(id.with(Self::ID), || Self { + let state_id = id.with(Self::ID); + let mut state = data.get_temp(state_id).unwrap_or(Self { open_item: None, last_visible_pass: pass_nr, }); @@ -160,14 +161,38 @@ impl MenuState { if state.last_visible_pass + 1 < pass_nr { state.open_item = None; } - state.last_visible_pass = pass_nr; - f(state) + if let Some(item) = state.open_item { + if data + .get_temp(item.with(Self::ID)) + .is_none_or(|item: Self| item.last_visible_pass + 1 < pass_nr) + { + // If the open item wasn't shown for at least a frame, reset the open item + state.open_item = None; + } + } + let r = f(&mut state); + data.insert_temp(state_id, state); + r }) } + pub fn mark_shown(ctx: &Context, id: Id) { + let pass_nr = ctx.cumulative_pass_nr(); + Self::from_id(ctx, id, |state| { + state.last_visible_pass = pass_nr; + }); + } + /// Is the menu with this id the deepest sub menu? (-> no child sub menu is open) - pub fn is_deepest_sub_menu(ctx: &Context, id: Id) -> bool { - Self::from_id(ctx, id, |state| state.open_item.is_none()) + /// + /// Note: This only returns correct results if called after the menu contents were shown. + pub fn is_deepest_open_sub_menu(ctx: &Context, id: Id) -> bool { + let pass_nr = ctx.cumulative_pass_nr(); + let open_item = Self::from_id(ctx, id, |state| state.open_item); + // If we have some open item, check if that was actually shown this frame + open_item.is_none_or(|submenu_id| { + Self::from_id(ctx, submenu_id, |state| state.last_visible_pass != pass_nr) + }) } } @@ -399,6 +424,9 @@ impl SubMenu { } /// Show the submenu. + /// + /// This does some heuristics to check if the `button_response` was the last thing in the + /// menu that was hovered/clicked, and if so, shows the submenu. pub fn show( self, ui: &Ui, @@ -409,6 +437,7 @@ impl SubMenu { let id = Self::id_from_widget_id(button_response.id); + // Get the state from the parent menu let (open_item, menu_id, parent_config) = MenuState::from_ui(ui, |state, stack| { (state.open_item, stack.id, MenuConfig::from_stack(stack)) }); @@ -452,7 +481,7 @@ impl SubMenu { set_open = Some(true); is_open = true; // Ensure that all other sub menus are closed when we open the menu - MenuState::from_id(ui.ctx(), id, |state| { + MenuState::from_id(ui.ctx(), menu_id, |state| { state.open_item = None; }); } @@ -488,7 +517,7 @@ impl SubMenu { if let Some(popup_response) = &popup_response { // If no child sub menu is open means we must be the deepest child sub menu. - let is_deepest_submenu = MenuState::is_deepest_sub_menu(ui.ctx(), id); + let is_deepest_submenu = MenuState::is_deepest_open_sub_menu(ui.ctx(), id); // If the user clicks and the cursor is not hovering over our menu rect, it's // safe to assume they clicked outside the menu, so we close everything. diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index e8c0ac596..a5a5b37bb 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -604,8 +604,11 @@ impl<'a> Popup<'a> { PopupCloseBehavior::IgnoreClicks => false, }; + // Mark the menu as shown, so the sub menu open state is not reset + MenuState::mark_shown(&ctx, id); + // If a submenu is open, the CloseBehavior is handled there - let is_any_submenu_open = !MenuState::is_deepest_sub_menu(&response.response.ctx, id); + let is_any_submenu_open = !MenuState::is_deepest_open_sub_menu(&response.response.ctx, id); let should_close = (!is_any_submenu_open && closed_by_click) || ctx.input(|i| i.key_pressed(Key::Escape)) diff --git a/crates/egui_demo_lib/src/demo/popups.rs b/crates/egui_demo_lib/src/demo/popups.rs index 9998dd59f..cb14f0cc7 100644 --- a/crates/egui_demo_lib/src/demo/popups.rs +++ b/crates/egui_demo_lib/src/demo/popups.rs @@ -20,6 +20,19 @@ pub struct PopupsDemo { color: egui::Color32, } +impl Default for PopupsDemo { + fn default() -> Self { + Self { + align4: RectAlign::default(), + gap: 4.0, + close_behavior: PopupCloseBehavior::CloseOnClick, + popup_open: false, + checked: true, + color: egui::Color32::RED, + } + } +} + impl PopupsDemo { fn apply_options<'a>(&self, popup: Popup<'a>) -> Popup<'a> { popup @@ -95,6 +108,14 @@ impl PopupsDemo { color_picker_color32(ui, &mut self.color, Alpha::Opaque); }); + if self.checked { + ui.menu_button("Only visible when checked", |ui| { + if ui.button("Remove myself").clicked() { + self.checked = false; + } + }); + } + if ui.button("Open…").clicked() { ui.close(); } @@ -102,19 +123,6 @@ impl PopupsDemo { } } -impl Default for PopupsDemo { - fn default() -> Self { - Self { - align4: RectAlign::default(), - gap: 4.0, - close_behavior: PopupCloseBehavior::CloseOnClick, - popup_open: false, - checked: false, - color: egui::Color32::RED, - } - } -} - impl crate::Demo for PopupsDemo { fn name(&self) -> &'static str { "\u{2755} Popups" diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index a3b7f6d92..9b1a9a096 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -1,5 +1,5 @@ use egui::accesskit::{self, Role}; -use egui::{Button, ComboBox, Image, Vec2, Widget as _}; +use egui::{Button, ComboBox, Image, Modifiers, Popup, Vec2, Widget as _}; #[cfg(all(feature = "wgpu", feature = "snapshot"))] use egui_kittest::SnapshotResults; use egui_kittest::{Harness, kittest::Queryable as _}; @@ -187,3 +187,78 @@ pub fn override_text_color_affects_interactive_widgets() { #[cfg(all(feature = "wgpu", feature = "snapshot"))] results.add(harness.try_snapshot("override_text_color_interactive")); } + +/// +#[test] +pub fn menus_should_close_even_if_submenu_disappears() { + const OTHER_BUTTON: &str = "Other button"; + const MENU_BUTTON: &str = "Menu"; + const SUB_MENU_BUTTON: &str = "Always here"; + const TOGGLABLE_SUB_MENU_BUTTON: &str = "Maybe here"; + const INSIDE_SUB_MENU_BUTTON: &str = "Inside submenu"; + + for frame_delay in (0..3).rev() { + let mut harness = Harness::builder().build_ui_state( + |ui, state| { + let _ = ui.button(OTHER_BUTTON).clicked(); + let response = ui.button(MENU_BUTTON); + + Popup::menu(&response).show(|ui| { + let _ = ui.button(SUB_MENU_BUTTON); + if *state { + ui.menu_button(TOGGLABLE_SUB_MENU_BUTTON, |ui| { + let _ = ui.button(INSIDE_SUB_MENU_BUTTON); + }); + } + }); + }, + true, + ); + + // Open the main menu + harness.get_by_label(MENU_BUTTON).click(); + harness.run(); + + // Open the sub menu + harness + .get_by_label_contains(TOGGLABLE_SUB_MENU_BUTTON) + .hover(); + harness.run(); + + // Have we opened the submenu successfully? + harness.get_by_label(INSIDE_SUB_MENU_BUTTON).hover(); + harness.run(); + + // We click manually, since we want to precisely time that the sub menu disappears when the + // button is released + let center = harness.get_by_label(OTHER_BUTTON).rect().center(); + harness.input_mut().events.push(egui::Event::PointerButton { + pos: center, + button: egui::PointerButton::Primary, + pressed: true, + modifiers: Modifiers::default(), + }); + harness.step(); + + // Yank the sub menu from under the pointer + *harness.state_mut() = false; + + // See if we handle it with or without a frame delay + harness.run_steps(frame_delay); + + // Actually close the menu by clicking somewhere outside + harness.input_mut().events.push(egui::Event::PointerButton { + pos: center, + button: egui::PointerButton::Primary, + pressed: false, + modifiers: Modifiers::default(), + }); + + harness.run(); + + assert!( + harness.query_by_label_contains(SUB_MENU_BUTTON).is_none(), + "Menu failed to close. frame_delay = {frame_delay}" + ); + } +} From 30277233ce37d213f5e0ef347bd0ec3d58898044 Mon Sep 17 00:00:00 2001 From: Ian Hobson Date: Tue, 7 Oct 2025 12:30:09 +0200 Subject: [PATCH 253/388] Add support for the safe area on iOS (#7578) This PR is a continuation of #4915 by @frederik-uni and @lucasmerlin that introduces support for keeping egui content within the 'safe area' on iOS (avoiding the notch / dynamic island / menu bar etc.), with the following changes: - `SafeArea` now wraps `MarginF32` and has been renamed to `SafeAreaInsets` to clarify its purpose. - `InputState::screen_rect` is now marked as deprecated in favour of either `viewport_rect` (which contains the entire screen), or `content_rect` (which is the viewport rect with the safe area insets removed). - I added some comments to the safe area insets logic pointing out the [safe area API coming in winit v0.31](https://github.com/rust-windowing/winit/issues/3910). --------- Co-authored-by: frederik-uni <147479464+frederik-uni@users.noreply.github.com> Co-authored-by: Lucas Meurer Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 3 ++ Cargo.toml | 1 + crates/egui-winit/Cargo.toml | 18 ++++++++ crates/egui-winit/src/lib.rs | 16 +++++++ crates/egui-winit/src/safe_area.rs | 56 ++++++++++++++++++++++ crates/egui/src/containers/area.rs | 2 +- crates/egui/src/containers/modal.rs | 2 +- crates/egui/src/containers/panel.rs | 6 +-- crates/egui/src/containers/popup.rs | 2 +- crates/egui/src/containers/resize.rs | 2 +- crates/egui/src/context.rs | 69 +++++++++++++++++++--------- crates/egui/src/data/input.rs | 29 +++++++++++- crates/egui/src/debug_text.rs | 4 +- crates/egui/src/input_state/mod.rs | 59 ++++++++++++++++++++---- crates/egui/src/menu.rs | 10 ++-- crates/egui/src/pass_state.rs | 12 ++--- crates/egui/src/ui.rs | 2 +- crates/egui_demo_app/src/wrap_app.rs | 6 +-- crates/egui_demo_lib/src/lib.rs | 2 +- crates/egui_kittest/src/wgpu.rs | 2 +- crates/emath/src/rect_align.rs | 6 +-- examples/file_dialog/src/main.rs | 6 +-- tests/test_viewports/src/main.rs | 8 +++- 23 files changed, 259 insertions(+), 64 deletions(-) create mode 100644 crates/egui-winit/src/safe_area.rs diff --git a/Cargo.lock b/Cargo.lock index ed40f3056..6121aa348 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1314,6 +1314,9 @@ dependencies = [ "document-features", "egui", "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", "profiling", "raw-window-handle", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6be41d27d..c3a1195de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ nohash-hasher = "0.2" objc2 = "0.5.1" objc2-app-kit = { version = "0.2.0", default-features = false } objc2-foundation = { version = "0.2.0", default-features = false } +objc2-ui-kit = { version = "0.2.0", default-features = false } parking_lot = "0.12" percent-encoding = "2.1" pollster = "0.4" diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 3f4891f9e..814abef4a 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -76,6 +76,24 @@ document-features = { workspace = true, optional = true } serde = { workspace = true, optional = true } webbrowser = { version = "1.0.0", optional = true } +[target.'cfg(target_os = "ios")'.dependencies] +objc2 = { workspace = true } +objc2-foundation = { workspace = true, features = [ + "std", + "NSThread", +] } +objc2-ui-kit = { workspace = true, features = [ + "std", + "UIApplication", + "UIGeometry", + "UIResponder", + "UIScene", + "UISceneDefinitions", + "UIView", + "UIWindow", + "UIWindowScene", +] } + [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] smithay-clipboard = { version = "0.7.2", optional = true } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 914c17751..92e8c3417 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -18,6 +18,7 @@ use egui::{Pos2, Rect, Theme, Vec2, ViewportBuilder, ViewportCommand, ViewportId pub use winit; pub mod clipboard; +mod safe_area; mod window_settings; pub use window_settings::WindowSettings; @@ -274,6 +275,21 @@ impl State { } use winit::event::WindowEvent; + + #[cfg(target_os = "ios")] + match &event { + WindowEvent::Resized(_) + | WindowEvent::ScaleFactorChanged { .. } + | WindowEvent::Focused(true) + | WindowEvent::Occluded(false) => { + // Once winit v0.31 has been released this can be reworked to get the safe area from + // `Window::safe_area`, and updated from a new event which is being discussed in + // https://github.com/rust-windowing/winit/issues/3911. + self.egui_input_mut().safe_area_insets = Some(safe_area::get_safe_area_insets()); + } + _ => {} + } + match event { WindowEvent::ScaleFactorChanged { scale_factor, .. } => { let native_pixels_per_point = *scale_factor as f32; diff --git a/crates/egui-winit/src/safe_area.rs b/crates/egui-winit/src/safe_area.rs new file mode 100644 index 000000000..d29d654a3 --- /dev/null +++ b/crates/egui-winit/src/safe_area.rs @@ -0,0 +1,56 @@ +#[cfg(target_os = "ios")] +pub use ios::get_safe_area_insets; + +#[cfg(target_os = "ios")] +mod ios { + use egui::{SafeAreaInsets, epaint::MarginF32}; + use objc2::{ClassType, rc::Retained}; + use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; + use objc2_ui_kit::{UIApplication, UISceneActivationState, UIWindowScene}; + + /// Gets the ios safe area insets. + /// + /// A safe area defines the area within a view that isn’t covered by a navigation bar, tab bar, + /// toolbar, or other views a window might provide. Safe areas are essential for avoiding a + /// device’s interactive and display features, like Dynamic Island on iPhone or the camera + /// housing on some Mac models. + /// + /// Once winit v0.31 has been released this can be removed in favor of + /// `winit::Window::safe_area`. + pub fn get_safe_area_insets() -> SafeAreaInsets { + let Some(main_thread_marker) = MainThreadMarker::new() else { + log::error!("Getting safe area insets needs to be performed on the main thread"); + return SafeAreaInsets::default(); + }; + + let app = UIApplication::sharedApplication(main_thread_marker); + + #[allow(unsafe_code)] + unsafe { + // Look for the first window scene that's in the foreground + for scene in app.connectedScenes() { + if scene.isKindOfClass(UIWindowScene::class()) + && matches!( + scene.activationState(), + UISceneActivationState::ForegroundActive + | UISceneActivationState::ForegroundInactive + ) + { + // Safe to cast, the class kind was checked above + let window_scene = Retained::cast::(scene.clone()); + if let Some(window) = window_scene.keyWindow() { + let insets = window.safeAreaInsets(); + return SafeAreaInsets(MarginF32 { + top: insets.top as f32, + left: insets.left as f32, + right: insets.right as f32, + bottom: insets.bottom as f32, + }); + } + } + } + } + + SafeAreaInsets::default() + } +} diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 07f2e0cf9..22ab67d3e 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -436,7 +436,7 @@ impl Area { sizing_pass: force_sizing_pass, } = self; - let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.screen_rect()); + let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.content_rect()); let layer_id = LayerId::new(order, id); diff --git a/crates/egui/src/containers/modal.rs b/crates/egui/src/containers/modal.rs index e36ad6e1b..6b846ab5e 100644 --- a/crates/egui/src/containers/modal.rs +++ b/crates/egui/src/containers/modal.rs @@ -90,7 +90,7 @@ impl Modal { inner: (inner, backdrop_response), response, } = area.show(ctx, |ui| { - let bg_rect = ui.ctx().screen_rect(); + let bg_rect = ui.ctx().content_rect(); let bg_sense = Sense::CLICK | Sense::DRAG; let mut backdrop = ui.new_child(UiBuilder::new().sense(bg_sense).max_rect(bg_rect)); backdrop.set_min_size(bg_rect.size()); diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 1cb10269b..15d1b32b8 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -389,7 +389,7 @@ impl SidePanel { .layer_id(LayerId::background()) .max_rect(available_rect), ); - panel_ui.set_clip_rect(ctx.screen_rect()); + panel_ui.set_clip_rect(ctx.content_rect()); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); let rect = inner_response.response.rect; @@ -887,7 +887,7 @@ impl TopBottomPanel { .layer_id(LayerId::background()) .max_rect(available_rect), ); - panel_ui.set_clip_rect(ctx.screen_rect()); + panel_ui.set_clip_rect(ctx.content_rect()); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); let rect = inner_response.response.rect; @@ -1152,7 +1152,7 @@ impl CentralPanel { .layer_id(LayerId::background()) .max_rect(ctx.available_rect().round_ui()), ); - panel_ui.set_clip_rect(ctx.screen_rect()); + panel_ui.set_clip_rect(ctx.content_rect()); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index a5a5b37bb..a9c00661d 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -485,7 +485,7 @@ impl<'a> Popup<'a> { .chain(RectAlign::MENU_ALIGNS.iter().copied()), ), ), - self.ctx.screen_rect(), + self.ctx.content_rect(), anchor_rect, self.gap, expected_popup_size, diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index bb001dc67..50cc28774 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -220,7 +220,7 @@ impl Resize { .at_least(self.min_size) .at_most(self.max_size) .at_most( - ui.ctx().screen_rect().size() - ui.spacing().window_margin.sum(), // hack for windows + ui.ctx().content_rect().size() - ui.spacing().window_margin.sum(), // hack for windows ) .round_ui(); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 03b46b652..44c4d8fa5 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -17,10 +17,10 @@ use epaint::{ use crate::{ Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, - ModifierNames, Modifiers, NumExt as _, Order, Painter, Plugin, RawInput, Response, RichText, - ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, - ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, - Widget as _, WidgetRect, WidgetText, + ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, + SafeAreaInsets, 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, @@ -374,6 +374,7 @@ struct ContextImpl { animation_manager: AnimationManager, plugins: plugin::Plugins, + safe_area: SafeAreaInsets, /// All viewports share the same texture manager and texture namespace. /// @@ -419,6 +420,10 @@ impl ContextImpl { .unwrap_or_default(); let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent_id); + if let Some(safe_area) = new_raw_input.safe_area_insets { + self.safe_area = safe_area; + } + let is_outermost_viewport = self.viewport_stack.is_empty(); // not necessarily root, just outermost immediate viewport self.viewport_stack.push(ids); @@ -432,7 +437,7 @@ impl ContextImpl { let input = &viewport.input; // This is a bit hacky, but is required to avoid jitter: - let mut rect = input.screen_rect; + let mut rect = input.content_rect(); rect.min = (ratio * rect.min.to_vec2()).to_pos2(); rect.max = (ratio * rect.max.to_vec2()).to_pos2(); new_raw_input.screen_rect = Some(rect); @@ -459,9 +464,9 @@ impl ContextImpl { ); let repaint_after = viewport.input.wants_repaint_after(); - let screen_rect = viewport.input.screen_rect; + let content_rect = viewport.input.content_rect(); - viewport.this_pass.begin_pass(screen_rect); + viewport.this_pass.begin_pass(content_rect); { let mut layers: Vec = viewport.prev_pass.widgets.layer_ids().collect(); @@ -494,9 +499,9 @@ impl ContextImpl { self.memory.areas_mut().set_state( LayerId::background(), AreaState { - pivot_pos: Some(screen_rect.left_top()), + pivot_pos: Some(content_rect.left_top()), pivot: Align2::LEFT_TOP, - size: Some(screen_rect.size()), + size: Some(content_rect.size()), interactable: true, last_became_visible_at: None, }, @@ -1075,14 +1080,14 @@ impl Context { } let show_error = |widget_rect: Rect, text: String| { - let screen_rect = self.screen_rect(); + let content_rect = self.content_rect(); let text = format!("🔥 {text}"); let color = self.style().visuals.error_fg_color; let painter = self.debug_painter(); painter.rect_stroke(widget_rect, 0.0, (1.0, color), StrokeKind::Outside); - let below = widget_rect.bottom() + 32.0 < screen_rect.bottom(); + let below = widget_rect.bottom() + 32.0 < content_rect.bottom(); let text_rect = if below { painter.debug_text( @@ -1426,8 +1431,8 @@ impl Context { /// Get a full-screen painter for a new or existing layer pub fn layer_painter(&self, layer_id: LayerId) -> Painter { - let screen_rect = self.screen_rect(); - Painter::new(self.clone(), layer_id, screen_rect) + let content_rect = self.content_rect(); + Painter::new(self.clone(), layer_id, content_rect) } /// Paint on top of everything else @@ -1861,13 +1866,13 @@ impl Context { }); } - /// Register a [`Plugin`] + /// Register a [`Plugin`](plugin::Plugin) /// /// Plugins are called in the order they are added. /// /// A plugin of the same type can only be added once (further calls with the same type will be ignored). /// This way it's convenient to add plugins in `eframe::run_simple_native`. - pub fn add_plugin(&self, plugin: impl Plugin + 'static) { + pub fn add_plugin(&self, plugin: impl plugin::Plugin + 'static) { let handle = plugin::PluginHandle::new(plugin); let added = self.write(|ctx| ctx.plugins.add(handle.clone())); @@ -1880,7 +1885,10 @@ impl Context { /// Call the provided closure with the plugin of type `T`, if it was registered. /// /// Returns `None` if the plugin was not registered. - pub fn with_plugin(&self, f: impl FnOnce(&mut T) -> R) -> Option { + pub fn with_plugin( + &self, + f: impl FnOnce(&mut T) -> R, + ) -> Option { let plugin = self.read(|ctx| ctx.plugins.get(std::any::TypeId::of::())); plugin.map(|plugin| f(plugin.lock().typed_plugin_mut())) } @@ -1889,7 +1897,7 @@ impl Context { /// /// ## Panics /// If the plugin of type `T` was not registered, this will panic. - pub fn plugin(&self) -> TypedPluginHandle { + pub fn plugin(&self) -> TypedPluginHandle { if let Some(plugin) = self.plugin_opt() { plugin } else { @@ -1898,13 +1906,13 @@ impl Context { } /// Get a handle to the plugin of type `T`, if it was registered. - pub fn plugin_opt(&self) -> Option> { + pub fn plugin_opt(&self) -> Option> { let plugin = self.read(|ctx| ctx.plugins.get(std::any::TypeId::of::())); plugin.map(TypedPluginHandle::new) } /// Get a handle to the plugin of type `T`, or insert its default. - pub fn plugin_or_default(&self) -> TypedPluginHandle { + pub fn plugin_or_default(&self) -> TypedPluginHandle { if let Some(plugin) = self.plugin_opt() { plugin } else { @@ -2658,9 +2666,28 @@ impl Context { // --------------------------------------------------------------------- + /// Returns the position and size of the egui area that is safe for content rendering. + /// + /// Returns [`Self::viewport_rect`] minus areas that might be partially covered by, for example, + /// the OS status bar or display notches. + pub fn content_rect(&self) -> Rect { + self.input(|i| i.content_rect()).round_ui() + } + + /// Returns the position and size of the full area available to egui + /// + /// This includes reas that might be partially covered by, for example, the OS status bar or + /// display notches. See [`Self::content_rect`] to get a rect that is safe for content. + pub fn viewport_rect(&self) -> Rect { + self.input(|i| i.viewport_rect()).round_ui() + } + /// Position and size of the egui area. + #[deprecated( + note = "screen_rect has been renamed to viewport_rect. Consider switching to content_rect." + )] pub fn screen_rect(&self) -> Rect { - self.input(|i| i.screen_rect()).round_ui() + self.input(|i| i.content_rect()).round_ui() } /// How much space is still available after panels have been added. @@ -3235,7 +3262,7 @@ impl Context { ui.image(SizedTexture::new(texture_id, size)) .on_hover_ui(|ui| { // show larger on hover - let max_size = 0.5 * ui.ctx().screen_rect().size(); + let max_size = 0.5 * ui.ctx().content_rect().size(); let mut size = point_size; size *= max_size.x / size.x.max(max_size.x); size *= max_size.y / size.y.max(max_size.y); diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 7c23a13bd..8eecd979d 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -1,6 +1,6 @@ //! The input needed by egui. -use epaint::ColorImage; +use epaint::{ColorImage, MarginF32}; use crate::{ Key, OrderedViewportIdMap, Theme, ViewportId, ViewportIdMap, @@ -27,6 +27,11 @@ pub struct RawInput { /// Information about all egui viewports. pub viewports: ViewportIdMap, + /// The insets used to only render content in a mobile safe area + /// + /// `None` will be treated as "same as last frame" + pub safe_area_insets: Option, + /// Position and size of the area that egui should use, in points. /// Usually you would set this to /// @@ -98,6 +103,7 @@ impl Default for RawInput { dropped_files: Default::default(), focused: true, // integrations opt into global focus tracking system_theme: None, + safe_area_insets: Default::default(), } } } @@ -122,6 +128,7 @@ impl RawInput { .map(|(id, info)| (*id, info.take())) .collect(), screen_rect: self.screen_rect.take(), + safe_area_insets: self.safe_area_insets.take(), max_texture_side: self.max_texture_side.take(), time: self.time, predicted_dt: self.predicted_dt, @@ -149,6 +156,7 @@ impl RawInput { mut dropped_files, focused, system_theme, + safe_area_insets: safe_area, } = newer; self.viewport_id = viewport_ids; @@ -163,6 +171,7 @@ impl RawInput { self.dropped_files.append(&mut dropped_files); self.focused = focused; self.system_theme = system_theme; + self.safe_area_insets = safe_area; } } @@ -1132,6 +1141,7 @@ impl RawInput { dropped_files, focused, system_theme, + safe_area_insets: safe_area, } = self; ui.label(format!("Active viewport: {viewport_id:?}")); @@ -1161,6 +1171,7 @@ impl RawInput { ui.label(format!("dropped_files: {}", dropped_files.len())); ui.label(format!("focused: {focused}")); ui.label(format!("system_theme: {system_theme:?}")); + ui.label(format!("safe_area: {safe_area:?}")); ui.scope(|ui| { ui.set_min_height(150.0); ui.label(format!("events: {events:#?}")) @@ -1297,3 +1308,19 @@ impl EventFilter { } } } + +/// The 'safe area' insets of the screen +/// +/// This represents the area taken up by the status bar, navigation controls, notches, +/// or any other items that obscure parts of the screen. +#[derive(Debug, PartialEq, Copy, Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct SafeAreaInsets(pub MarginF32); + +impl std::ops::Sub for Rect { + type Output = Self; + + fn sub(self, rhs: SafeAreaInsets) -> Self::Output { + self - rhs.0 + } +} diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index ed0bea724..cabde8c62 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -72,7 +72,7 @@ impl DebugTextPlugin { // Show debug-text next to the cursor. let mut pos = ctx .input(|i| i.pointer.latest_pos()) - .unwrap_or_else(|| ctx.screen_rect().center()) + .unwrap_or_else(|| ctx.content_rect().center()) + 8.0 * Vec2::Y; let painter = ctx.debug_painter(); @@ -96,7 +96,7 @@ impl DebugTextPlugin { { // Paint `text` to right of `pos`: - let available_width = ctx.screen_rect().max.x - pos.x; + let available_width = ctx.content_rect().max.x - pos.x; let galley = text.into_galley_impl( ctx, &ctx.style(), diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 5f6aec789..e78342ace 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -5,6 +5,7 @@ use crate::data::input::{ PointerButton, RawInput, TouchDeviceId, ViewportInfo, }; use crate::{ + SafeAreaInsets, emath::{NumExt as _, Pos2, Rect, Vec2, vec2}, util::History, }; @@ -272,7 +273,12 @@ pub struct InputState { // ---------------------------------------------- /// Position and size of the egui area. - pub screen_rect: Rect, + /// + /// This is including the area that may be covered by the `safe_area_insets`. + viewport_rect: Rect, + + /// The safe area insets, subtracted from the `viewport_rect` in [`Self::content_rect`]. + safe_area_insets: SafeAreaInsets, /// Also known as device pixel ratio, > 1 for high resolution screens. pub pixels_per_point: f32, @@ -359,7 +365,8 @@ impl Default for InputState { zoom_factor_delta: 1.0, rotation_radians: 0.0, - screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)), + viewport_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)), + safe_area_insets: Default::default(), pixels_per_point: 1.0, max_texture_side: 2048, time: 0.0, @@ -397,7 +404,8 @@ impl InputState { new.predicted_dt }; - let screen_rect = new.screen_rect.unwrap_or(self.screen_rect); + let safe_area_insets = new.safe_area_insets.unwrap_or(self.safe_area_insets); + let viewport_rect = new.screen_rect.unwrap_or(self.viewport_rect); self.create_touch_states_for_new_devices(&new.events); for touch_state in self.touch_states.values_mut() { touch_state.begin_pass(time, &new, self.pointer.interact_pos); @@ -437,7 +445,7 @@ impl InputState { let mut delta = match unit { MouseWheelUnit::Point => *delta, MouseWheelUnit::Line => options.line_scroll_speed * *delta, - MouseWheelUnit::Page => screen_rect.height() * *delta, + MouseWheelUnit::Page => viewport_rect.height() * *delta, }; let is_horizontal = modifiers.matches_any(options.horizontal_scroll_modifier); @@ -552,7 +560,8 @@ impl InputState { zoom_factor_delta, rotation_radians, - screen_rect, + viewport_rect, + safe_area_insets, pixels_per_point, max_texture_side: new.max_texture_side.unwrap_or(self.max_texture_side), time, @@ -574,9 +583,41 @@ impl InputState { self.raw.viewport() } + /// Returns the full area available to egui, including parts that might be partially covered, + /// for example, by the OS status bar or notches (see [`Self::safe_area_insets`]). + /// + /// Usually you want to use [`Self::content_rect`] instead. + pub fn viewport_rect(&self) -> Rect { + self.viewport_rect + } + + /// Returns the region of the screen that is safe for content rendering + /// + /// Returns the `viewport_rect` with the `safe_area_insets` removed. #[inline(always)] + pub fn content_rect(&self) -> Rect { + self.viewport_rect - self.safe_area_insets + } + + /// Returns the full area available to egui, including parts that might be partially + /// covered, for example, by the OS status bar or notches. + /// + /// Usually you want to use [`Self::content_rect`] instead. + #[deprecated( + note = "screen_rect has been renamed to viewport_rect. Consider switching to content_rect." + )] pub fn screen_rect(&self) -> Rect { - self.screen_rect + self.content_rect() + } + + /// Get the safe area insets. + /// + /// This represents the area of the screen covered by status bars, navigation controls, notches, + /// or other items that obscure part of the screen. + /// + /// See [`Self::content_rect`] to get the `viewport_rect` with the safe area insets removed. + pub fn safe_area_insets(&self) -> SafeAreaInsets { + self.safe_area_insets } /// Uniform zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture). @@ -1559,7 +1600,8 @@ impl InputState { rotation_radians, zoom_factor_delta, - screen_rect, + viewport_rect, + safe_area_insets, pixels_per_point, max_texture_side, time, @@ -1612,7 +1654,8 @@ impl InputState { ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x")); ui.label(format!("rotation_radians: {rotation_radians:.3} radians")); - ui.label(format!("screen_rect: {screen_rect:?} points")); + ui.label(format!("viewport_rect: {viewport_rect:?} points")); + ui.label(format!("safe_area_insets: {safe_area_insets:?} points")); ui.label(format!( "{pixels_per_point} physical pixels for each logical point" )); diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 0e31a593c..348f42c21 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -401,14 +401,14 @@ impl MenuRoot { if let Some(root) = root.inner.as_mut() { let menu_rect = root.menu_state.read().rect; - let screen_rect = button.ctx.input(|i| i.screen_rect); + let content_rect = button.ctx.input(|i| i.content_rect()); - if pos.y + menu_rect.height() > screen_rect.max.y { - pos.y = screen_rect.max.y - menu_rect.height() - button.rect.height(); + if pos.y + menu_rect.height() > content_rect.max.y { + pos.y = content_rect.max.y - menu_rect.height() - button.rect.height(); } - if pos.x + menu_rect.width() > screen_rect.max.x { - pos.x = screen_rect.max.x - menu_rect.width(); + if pos.x + menu_rect.width() > content_rect.max.x { + pos.x = content_rect.max.x - menu_rect.width(); } } diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index ca0d15720..66a1f6e10 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -137,7 +137,7 @@ impl DebugRect { let galley = painter.layout_no_wrap(text, font_id, text_color); // Position the text either under or above: - let screen_rect = ctx.screen_rect(); + let content_rect = ctx.content_rect(); let y = if galley.size().y <= rect.top() { // Above rect.top() - galley.size().y - 16.0 @@ -147,12 +147,12 @@ impl DebugRect { }; let y = y - .at_most(screen_rect.bottom() - galley.size().y) + .at_most(content_rect.bottom() - galley.size().y) .at_least(0.0); let x = rect .left() - .at_most(screen_rect.right() - galley.size().x) + .at_most(content_rect.right() - galley.size().x) .at_least(0.0); let text_pos = pos2(x, y); @@ -258,7 +258,7 @@ impl Default for PassState { } impl PassState { - pub(crate) fn begin_pass(&mut self, screen_rect: Rect) { + pub(crate) fn begin_pass(&mut self, content_rect: Rect) { profiling::function_scope!(); let Self { used_ids, @@ -282,8 +282,8 @@ impl PassState { widgets.clear(); tooltips.clear(); layers.clear(); - *available_rect = screen_rect; - *unused_rect = screen_rect; + *available_rect = content_rect; + *unused_rect = content_rect; *used_by_panels = Rect::NOTHING; *scroll_target = [None, None]; *scroll_delta = Default::default(); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index a08148a89..e2d89c0bc 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -142,7 +142,7 @@ impl Ui { "Top-level Ui:s should not have an id_salt" ); - let max_rect = max_rect.unwrap_or_else(|| ctx.screen_rect()); + let max_rect = max_rect.unwrap_or_else(|| ctx.content_rect()); let clip_rect = max_rect; let layout = layout.unwrap_or_default(); let disabled = disabled || invisible; diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 9f9cb6a87..127139a97 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -467,10 +467,10 @@ impl WrapApp { let painter = ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target"))); - let screen_rect = ctx.screen_rect(); - painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192)); + let content_rect = ctx.content_rect(); + painter.rect_filled(content_rect, 0.0, Color32::from_black_alpha(192)); painter.text( - screen_rect.center(), + content_rect.center(), Align2::CENTER_CENTER, text, TextStyle::Heading.resolve(&ctx.style()), diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index be3816a38..76be28859 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -109,6 +109,6 @@ fn test_egui_zero_window_size() { /// Detect narrow screens. This is used to show a simpler UI on mobile devices, /// especially for the web demo at . pub fn is_mobile(ctx: &egui::Context) -> bool { - let screen_size = ctx.screen_rect().size(); + let screen_size = ctx.content_rect().size(); screen_size.x < 550.0 } diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index a71a69d76..d0a6e8482 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -151,7 +151,7 @@ impl crate::TestRenderer for WgpuTestRenderer { label: Some("Egui Command Encoder"), }); - let size = ctx.screen_rect().size() * ctx.pixels_per_point(); + let size = ctx.content_rect().size() * ctx.pixels_per_point(); let screen = ScreenDescriptor { pixels_per_point: ctx.pixels_per_point(), size_in_pixels: [size.x.round() as u32, size.y.round() as u32], diff --git a/crates/emath/src/rect_align.rs b/crates/emath/src/rect_align.rs index 31580405f..84cf60ce0 100644 --- a/crates/emath/src/rect_align.rs +++ b/crates/emath/src/rect_align.rs @@ -236,7 +236,7 @@ impl RectAlign { } /// Look for the first alternative [`RectAlign`] that allows the child rect to fit - /// inside the `screen_rect`. + /// inside the `content_rect`. /// /// If no alternative fits, the first is returned. /// If no alternatives are given, `None` is returned. @@ -246,7 +246,7 @@ impl RectAlign { /// - [`RectAlign::MENU_ALIGNS`] for the 12 common menu positions pub fn find_best_align( values_to_try: impl Iterator, - screen_rect: Rect, + content_rect: Rect, parent_rect: Rect, gap: f32, expected_size: Vec2, @@ -258,7 +258,7 @@ impl RectAlign { let suggested_popup_rect = align.align_rect(&parent_rect, expected_size, gap); - if screen_rect.contains_rect(suggested_popup_rect) { + if content_rect.contains_rect(suggested_popup_rect) { return Some(align); } } diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index f552adb26..9a8bbe966 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -107,10 +107,10 @@ fn preview_files_being_dropped(ctx: &egui::Context) { let painter = ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target"))); - let screen_rect = ctx.screen_rect(); - painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192)); + let content_rect = ctx.content_rect(); + painter.rect_filled(content_rect, 0.0, Color32::from_black_alpha(192)); painter.text( - screen_rect.center(), + content_rect.center(), Align2::CENTER_CENTER, text, TextStyle::Heading.resolve(&ctx.style()), diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index ad39ffb34..ab31a4ece 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -247,8 +247,12 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc>], close_ if let Some(monitor_size) = ctx.input(|i| i.viewport().monitor_size) { ui.label(format!("monitor_size: {monitor_size:?} (points)")); } - if let Some(screen_rect) = ui.input(|i| i.raw.screen_rect) { - ui.label(format!("Screen rect size: Pos: {:?}", screen_rect.size())); + if let Some(viewport_rect) = ui.input(|i| i.raw.screen_rect) { + ui.label(format!( + "Viewport Rect: Pos: {:?}, Size: {:?} (points)", + viewport_rect.min, + viewport_rect.size() + )); } if let Some(inner_rect) = ctx.input(|i| i.viewport().inner_rect) { ui.label(format!( From 0281b95bd9f512d417343caaec303e1b12b81a55 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 14:12:30 +0200 Subject: [PATCH 254/388] CI: use latest typos (#7595) --- .github/workflows/spelling_and_links.yml | 2 +- crates/egui/src/viewport.rs | 2 +- crates/egui_extras/src/layout.rs | 2 +- crates/epaint/src/text/fonts.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/spelling_and_links.yml b/.github/workflows/spelling_and_links.yml index faceada95..e09e01562 100644 --- a/.github/workflows/spelling_and_links.yml +++ b/.github/workflows/spelling_and_links.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Check spelling of entire workspace - uses: crate-ci/typos@v1.36.3 + uses: crate-ci/typos@v1.38.0 lychee: name: lychee diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 13cf55051..3d757e477 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1193,7 +1193,7 @@ pub struct ViewportOutput { /// since those don't result in real viewports. pub class: ViewportClass, - /// The window attrbiutes such as title, position, size, etc. + /// The window attributes such as title, position, size, etc. /// /// Use this when first constructing the native window. /// Also check for changes in it using [`ViewportBuilder::patch`], diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index 8b2a0fd6e..594763daf 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -35,7 +35,7 @@ pub(crate) struct StripLayoutFlags { pub(crate) selected: bool, pub(crate) overline: bool, - /// Used when we want to accruately measure the size of this cell. + /// Used when we want to accurately measure the size of this cell. pub(crate) sizing_pass: bool, } diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 7f7be2ffa..6bc7ccf8f 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -913,7 +913,7 @@ impl GalleyCache { debug_assert_eq!( child_hashes.len(), child_galleys.len(), - "Bug in `layout_each_paragraph_individuallly`" + "Bug in `layout_each_paragraph_individually`" ); let galley = Arc::new(Galley::concat(job, &child_galleys, pixels_per_point)); From 78fffc7707a287b3da34a4e4eeb74997abb55ec4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 14:12:51 +0200 Subject: [PATCH 255/388] clean up the clippy.toml:s (#7594) --- clippy.toml | 24 +++++++++--------------- scripts/clippy_wasm/clippy.toml | 9 ++++----- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/clippy.toml b/clippy.toml index 1b39bdae9..839586db2 100644 --- a/clippy.toml +++ b/clippy.toml @@ -35,17 +35,13 @@ disallowed-macros = [ # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods disallowed-methods = [ - "std::env::temp_dir", # Use the tempdir crate instead - - # There are many things that aren't allowed on wasm, + # NOTE: There are many things that aren't allowed on wasm, # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) # so we do that in `clipppy_wasm.toml` instead. - "std::thread::spawn", # Use `std::thread::Builder` and name the thread - - "sha1::Digest::new", # SHA1 is cryptographically broken - - "std::panic::catch_unwind", # We compile with `panic = "abort"` + { path = "std::env::temp_dir", readon = "Use the tempdir crate instead" }, + { path = "std::panic::catch_unwind", reason = "We compile with `panic = abort" }, + { path = "std::thread::spawn", readon = "Use `std::thread::Builder` and name the thread" }, ] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names @@ -53,14 +49,12 @@ disallowed-names = [] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types disallowed-types = [ - # Use the faster & simpler non-poisonable primitives in `parking_lot` instead - "std::sync::Mutex", - "std::sync::RwLock", - "std::sync::Condvar", + { path = "std::sync::Condvar", reason = "Use parking_lot instead" }, + { path = "std::sync::Mutex", reason = "Use epaint::mutex instead" }, + { path = "std::sync::RwLock", reason = "Use epaint::mutex instead" }, + { path = "winit::dpi::LogicalPosition", reason = "We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account" }, + { path = "winit::dpi::LogicalSize", reason = "We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account" }, # "std::sync::Once", # enabled for now as the `log_once` macro uses it internally - - "winit::dpi::LogicalSize", # We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account - "winit::dpi::LogicalPosition", # We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account ] # ----------------------------------------------------------------------------- diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index c138ea5ba..d2b8365d9 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -26,12 +26,11 @@ type-complexity-threshold = 350 # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods disallowed-methods = [ - "std::time::Instant::now", # use `instant` crate instead for wasm/web compatibility - "std::time::Duration::elapsed", # use `instant` crate instead for wasm/web compatibility - "std::time::SystemTime::now", # use `instant` or `time` crates instead for wasm/web compatibility + { path = "std::time::Instant::elapsed", reason = "use `instant` crate instead for wasm/web compatibility" }, + { path = "std::time::Instant::now", reason = "use `instant` crate instead for wasm/web compatibility" }, + { path = "std::time::SystemTime::now", reason = "use `instant` or `time` crates instead for wasm/web compatibility" }, - # Cannot spawn threads on wasm: - "std::thread::spawn", + { path = "std::thread::spawn", reason = "Cannot spawn threads on wasm" }, ] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types From ab461f41155ae44c111a87f45ed0abe7a966b0ed Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 14:21:10 +0200 Subject: [PATCH 256/388] Fix eframe window not being focused on mac on startup (#7593) * Workaround for https://github.com/rust-windowing/winit/issues/4371 * Closes https://github.com/emilk/egui/issues/7588 Tested manually --- crates/eframe/src/native/glow_integration.rs | 17 +++++++++++++++-- crates/eframe/src/native/wgpu_integration.rs | 17 +++++++++++++++-- crates/egui-winit/src/lib.rs | 13 +++++++++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index a5924db10..a087c9fab 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -775,8 +775,21 @@ impl GlowWinitRunning<'_> { let mut repaint_asap = false; match event { - winit::event::WindowEvent::Focused(new_focused) => { - glutin.focused_viewport = new_focused.then(|| viewport_id).flatten(); + winit::event::WindowEvent::Focused(focused) => { + let focused = if cfg!(target_os = "macos") + && let Some(viewport_id) = viewport_id + && let Some(viewport) = glutin.viewports.get(&viewport_id) + && let Some(window) = &viewport.window + { + // TODO(emilk): remove this work-around once we update winit + // https://github.com/rust-windowing/winit/issues/4371 + // https://github.com/emilk/egui/issues/7588 + window.has_focus() + } else { + *focused + }; + + glutin.focused_viewport = focused.then_some(viewport_id).flatten(); } winit::event::WindowEvent::Resized(physical_size) => { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 0c06012e7..53269c51e 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -775,8 +775,21 @@ impl WgpuWinitRunning<'_> { let mut repaint_asap = false; match event { - winit::event::WindowEvent::Focused(new_focused) => { - shared.focused_viewport = new_focused.then(|| viewport_id).flatten(); + winit::event::WindowEvent::Focused(focused) => { + let focused = if cfg!(target_os = "macos") + && let Some(viewport_id) = viewport_id + && let Some(viewport) = shared.viewports.get(&viewport_id) + && let Some(window) = &viewport.window + { + // TODO(emilk): remove this work-around once we update winit + // https://github.com/rust-windowing/winit/issues/4371 + // https://github.com/emilk/egui/issues/7588 + window.has_focus() + } else { + *focused + }; + + shared.focused_viewport = focused.then_some(viewport_id).flatten(); } winit::event::WindowEvent::Resized(physical_size) => { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 92e8c3417..16fc7b7b6 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -423,10 +423,19 @@ impl State { } } WindowEvent::Focused(focused) => { - self.egui_input.focused = *focused; + let focused = if cfg!(target_os = "macos") { + // TODO(emilk): remove this work-around once we update winit + // https://github.com/rust-windowing/winit/issues/4371 + // https://github.com/emilk/egui/issues/7588 + window.has_focus() + } else { + *focused + }; + + self.egui_input.focused = focused; self.egui_input .events - .push(egui::Event::WindowFocused(*focused)); + .push(egui::Event::WindowFocused(focused)); EventResponse { repaint: true, consumed: false, From d83f4500a3879d6bca27e25a44bf73e0fe4ba9ab Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 7 Oct 2025 14:39:22 +0200 Subject: [PATCH 257/388] Add `Harness::debug_open_snapshot` helper (#7590) Adds a helper to quickly see whats going on in a kittest test. Not all test have snapshots, but when debugging tests it might still be useful to see whats actually going on, so this adds a helper fn that renders a snapshot image to a temporary file and opens it with the default image viewer: https://github.com/user-attachments/assets/08785850-0a12-4572-b9b5-cea36951081c --- Cargo.lock | 48 ++++++++++++++++++++++++++--- Cargo.toml | 4 ++- clippy.toml | 2 +- crates/egui_kittest/Cargo.toml | 4 ++- crates/egui_kittest/src/snapshot.rs | 45 ++++++++++++++++++++++++++- deny.toml | 5 +-- 6 files changed, 97 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6121aa348..8d5913da6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1422,7 +1422,9 @@ dependencies = [ "egui_extras", "image", "kittest", + "open", "pollster", + "tempfile", "wgpu", ] @@ -2318,6 +2320,15 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -2329,6 +2340,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itertools" version = "0.10.5" @@ -3065,6 +3086,17 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -3143,6 +3175,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4064,15 +4102,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.13.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", - "rustix 0.38.38", - "windows-sys 0.59.0", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c3a1195de..52dad4fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,7 @@ objc2 = "0.5.1" objc2-app-kit = { version = "0.2.0", default-features = false } objc2-foundation = { version = "0.2.0", default-features = false } objc2-ui-kit = { version = "0.2.0", default-features = false } +open = "5" parking_lot = "0.12" percent-encoding = "2.1" pollster = "0.4" @@ -107,6 +108,7 @@ serde = { version = "1", features = ["derive"] } similar-asserts = "1.4.2" smallvec = "1" static_assertions = "1.1.0" +tempfile = "3" thiserror = "1.0.37" type-map = "0.5.0" unicode-segmentation = "1.12.0" @@ -114,7 +116,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = "0.3.73" web-time = "1.1.0" # Timekeeping for native and web -wgpu = { version = "27.0.0", default-features = false } +wgpu = { version = "27.0.0", default-features = false, features = ["std"] } windows-sys = "0.60" winit = { version = "0.30.12", default-features = false } diff --git a/clippy.toml b/clippy.toml index 839586db2..9ce151034 100644 --- a/clippy.toml +++ b/clippy.toml @@ -39,7 +39,7 @@ disallowed-methods = [ # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) # so we do that in `clipppy_wasm.toml` instead. - { path = "std::env::temp_dir", readon = "Use the tempdir crate instead" }, + { path = "std::env::temp_dir", readon = "Use the tempfile crate instead" }, { path = "std::panic::catch_unwind", reason = "We compile with `panic = abort" }, { path = "std::thread::spawn", readon = "Use `std::thread::Builder` and name the thread" }, ] diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index 19340fdd0..adb955e9f 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -33,7 +33,7 @@ wgpu = [ ] ## Adds a dify-based image snapshot utility. -snapshot = ["dep:dify", "dep:image", "image/png"] +snapshot = ["dep:dify", "dep:image", "dep:open", "dep:tempfile", "image/png"] ## Allows testing eframe::App eframe = ["dep:eframe", "eframe/accesskit"] @@ -61,6 +61,8 @@ wgpu = { workspace = true, features = [ # snapshot dependencies dify = { workspace = true, optional = true } +open = { workspace = true, optional = true } +tempfile = { workspace = true, optional = true } # Enable this when generating docs. document-features = { workspace = true, optional = true } diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index da00795f5..7e171346b 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -544,7 +544,7 @@ pub fn image_snapshot(current: &image::RgbaImage, name: impl Into) { } } -#[cfg(feature = "wgpu")] +#[cfg(any(feature = "wgpu", feature = "snapshot"))] impl Harness<'_, State> { /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot /// with custom options. @@ -635,6 +635,49 @@ impl Harness<'_, State> { } } } + + /// Render a snapshot, save it to a temp file and open it in the default image viewer. + /// + /// This method is marked as deprecated to trigger errors in CI (so that it's not accidentally + /// committed). + #[deprecated = "Only for debugging, don't commit this."] + pub fn debug_open_snapshot(&mut self) { + let image = self + .render() + .map_err(|err| SnapshotError::RenderError { err }) + .unwrap(); + let temp_file = tempfile::Builder::new() + .disable_cleanup(true) // we keep the file so it's accessible even after the test ends + .prefix("kittest-snapshot") + .suffix(".png") + .tempfile() + .expect("Failed to create temp file"); + + let path = temp_file.path(); + + image + .save(temp_file.path()) + .map_err(|err| SnapshotError::WriteSnapshot { + err, + path: path.to_path_buf(), + }) + .unwrap(); + + #[expect(clippy::print_stdout)] + { + println!("Wrote debug snapshot to: {}", path.display()); + } + let result = open::that(path); + if let Err(err) = result { + #[expect(clippy::print_stderr)] + { + eprintln!( + "Failed to open image {} in default image viewer: {err}", + path.display() + ); + } + } + } } /// Utility to collect snapshot errors and display them at the end of the test. diff --git a/deny.toml b/deny.toml index 305932868..5d7894482 100644 --- a/deny.toml +++ b/deny.toml @@ -48,13 +48,14 @@ skip = [ { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "core-foundation" }, # version conflict between winit and wgpu ecosystems + { name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems + { name = "getrandom" }, # ring / rustls (and thus ehttp) still depend on getrandom 0.2 { name = "quick-xml" }, # old version via wayland-scanner { name = "redox_syscall" }, # old version via winit { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x { name = "thiserror-impl" }, # same as above { name = "windows-sys" }, # mostly hopeless to avoid - { name = "core-foundation" }, # version conflict between winit and wgpu ecosystems - { name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems ] skip-tree = [ { name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15 From 7fc80d8623f774678ed7d903c72eb6f5ee3e443e Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 7 Oct 2025 14:39:49 +0200 Subject: [PATCH 258/388] Accessibility inspector plugin (#7368) Adds an accessibility inspector plugin that shows the current AccessKit tree: https://github.com/user-attachments/assets/78f4f221-1bd2-4ce4-adf5-fc3b00f5c16c Macos has a built in accessibility inspector, but it doesn't seem to work with AccessKit / eframe so this provides some insight into the accesskit state. This also showed a couple issues that are easy to fix: - [ ] Links show up as `Label` instead of links - [ ] Not all supported actions are advertised (e.g. scrolling) - [ ] The resize handles in windows shouldn't be focusable - [ ] Checkbox has no value - [ ] Menus should have the button as parent widget (not 100% sure on this one) Currently the plugin lives in the demo app, but I think it should be moved somewhere else. Maybe egui_extras? This could also be relevant for #4650 --- Cargo.lock | 2 + Cargo.toml | 1 + crates/egui/src/id.rs | 19 ++ crates/egui_demo_app/Cargo.toml | 4 +- .../src/accessibility_inspector.rs | 254 ++++++++++++++++++ crates/egui_demo_app/src/lib.rs | 3 +- crates/egui_demo_app/src/wrap_app.rs | 4 + 7 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 crates/egui_demo_app/src/accessibility_inspector.rs diff --git a/Cargo.lock b/Cargo.lock index 8d5913da6..8338f81b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1331,6 +1331,8 @@ dependencies = [ name = "egui_demo_app" version = "0.32.3" dependencies = [ + "accesskit", + "accesskit_consumer", "bytemuck", "chrono", "eframe", diff --git a/Cargo.toml b/Cargo.toml index 52dad4fa8..2e35a139f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ egui_kittest = { version = "0.32.3", path = "crates/egui_kittest", default-featu eframe = { version = "0.32.3", path = "crates/eframe", default-features = false } accesskit = "0.21.0" +accesskit_consumer = "0.30.0" accesskit_winit = "0.29" ahash = { version = "0.8.11", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 7bcef8dc2..0565dc567 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -83,6 +83,25 @@ impl Id { pub(crate) fn accesskit_id(&self) -> accesskit::NodeId { self.value().into() } + + /// Create a new [`Id`] from a high-entropy value. No hashing is done. + /// + /// This can be useful if you have an [`Id`] that was converted to some other type + /// (e.g. accesskit::NodeId) and you want to convert it back to an [`Id`]. + /// + /// # Safety + /// You need to ensure that the value is high-entropy since it might be used in + /// a [`IdSet`] or [`IdMap`], which rely on the assumption that [`Id`]s have good entropy. + /// + /// The method is not unsafe in terms of memory safety. + /// + /// # Panics + /// If the value is zero, this will panic. + #[doc(hidden)] + #[expect(unsafe_code)] + pub unsafe fn from_high_entropy_bits(value: u64) -> Self { + Self(NonZeroU64::new(value).expect("Id must be non-zero.")) + } } impl std::fmt::Debug for Id { diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 97e6806ff..703347a32 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -28,6 +28,7 @@ default = ["glow", "persistence"] # image_viewer adds about 0.9 MB of WASM web_app = ["http", "persistence"] +accessibility_inspector = ["dep:accesskit", "dep:accesskit_consumer", "eframe/accesskit"] http = ["ehttp", "image/jpeg", "poll-promise", "egui_extras/image"] image_viewer = ["image/jpeg", "egui_extras/all_loaders", "rfd"] persistence = [ @@ -64,7 +65,8 @@ log.workspace = true profiling.workspace = true # Optional dependencies: - +accesskit = { workspace = true, optional = true } +accesskit_consumer = { workspace = true, optional = true } bytemuck = { workspace = true, optional = true } puffin = { workspace = true, optional = true } puffin_http = { workspace = true, optional = true } diff --git a/crates/egui_demo_app/src/accessibility_inspector.rs b/crates/egui_demo_app/src/accessibility_inspector.rs new file mode 100644 index 000000000..df80149de --- /dev/null +++ b/crates/egui_demo_app/src/accessibility_inspector.rs @@ -0,0 +1,254 @@ +use accesskit::{Action, ActionRequest, NodeId}; +use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler}; +use eframe::epaint::text::TextWrapMode; +use egui::collapsing_header::CollapsingState; +use egui::{ + Button, Color32, Context, Event, Frame, FullOutput, Id, Key, KeyboardShortcut, Label, + Modifiers, RawInput, RichText, ScrollArea, SidePanel, TopBottomPanel, Ui, +}; +use std::mem; + +/// This [`egui::Plugin`] adds an inspector Panel. +/// +/// It can be opened with the `(Cmd/Ctrl)+Alt+I`. It shows the current AccessKit tree and details +/// for the selected node. +/// Useful when debugging accessibility issues or trying to understand the structure of the Ui. +/// +/// Add via +/// ``` +/// # use egui_demo_app::accessibility_inspector::AccessibilityInspectorPlugin; +/// # let ctx = egui::Context::default(); +/// ctx.add_plugin(AccessibilityInspectorPlugin::default()); +/// ``` +#[derive(Default, Debug)] +pub struct AccessibilityInspectorPlugin { + pub open: bool, + tree: Option, + selected_node: Option, + queued_action: Option, +} + +struct ChangeHandler; + +impl TreeChangeHandler for ChangeHandler { + fn node_added(&mut self, _node: &Node<'_>) {} + + fn node_updated(&mut self, _old_node: &Node<'_>, _new_node: &Node<'_>) {} + + fn focus_moved(&mut self, _old_node: Option<&Node<'_>>, _new_node: Option<&Node<'_>>) {} + + fn node_removed(&mut self, _node: &Node<'_>) {} +} + +impl egui::Plugin for AccessibilityInspectorPlugin { + fn debug_name(&self) -> &'static str { + "Accessibility Inspector" + } + + fn input_hook(&mut self, input: &mut RawInput) { + if let Some(queued_action) = self.queued_action.take() { + input + .events + .push(Event::AccessKitActionRequest(queued_action)); + } + } + + fn output_hook(&mut self, output: &mut FullOutput) { + if let Some(update) = output.platform_output.accesskit_update.clone() { + self.tree = match mem::take(&mut self.tree) { + None => { + // Create a new tree if it doesn't exist + Some(Tree::new(update, true)) + } + Some(mut tree) => { + // Update the tree with the latest accesskit data + tree.update_and_process_changes(update, &mut ChangeHandler); + + Some(tree) + } + } + } + } + + fn on_begin_pass(&mut self, ctx: &Context) { + if ctx.input_mut(|i| { + i.consume_shortcut(&KeyboardShortcut::new( + Modifiers::COMMAND | Modifiers::ALT, + Key::I, + )) + }) { + self.open = !self.open; + } + + if !self.open { + return; + } + + ctx.enable_accesskit(); + + SidePanel::right(Self::id()).show(ctx, |ui| { + let response = ui.heading("🔎 AccessKit Inspector"); + ctx.with_accessibility_parent(response.id, || { + if let Some(selected_node) = self.selected_node { + TopBottomPanel::bottom(Self::id().with("details_panel")) + .frame(Frame::new()) + .show_separator_line(false) + .show_inside(ui, |ui| { + self.selection_ui(ui, selected_node); + }); + } + + ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); + ScrollArea::vertical().show(ui, |ui| { + if let Some(tree) = &self.tree { + Self::node_ui(ui, &tree.state().root(), &mut self.selected_node); + } + }); + }); + }); + } +} + +impl AccessibilityInspectorPlugin { + fn id() -> Id { + Id::new("Accessibility Inspector") + } + + fn selection_ui(&mut self, ui: &mut Ui, selected_node: Id) { + ui.separator(); + + if let Some(tree) = &self.tree + && let Some(node) = tree.state().node_by_id(NodeId::from(selected_node.value())) + { + let node_response = ui.ctx().read_response(selected_node); + + if let Some(widget_response) = node_response { + ui.ctx().debug_painter().debug_rect( + widget_response.rect, + ui.style_mut().visuals.selection.bg_fill, + "", + ); + } + + egui::Grid::new("node_details_grid") + .num_columns(2) + .show(ui, |ui| { + ui.label("Node ID"); + ui.strong(format!("{selected_node:?}")); + ui.end_row(); + + ui.label("Role"); + ui.strong(format!("{:?}", node.role())); + ui.end_row(); + + ui.label("Label"); + ui.add( + Label::new(RichText::new(node.label().unwrap_or_default()).strong()) + .truncate(), + ); + ui.end_row(); + + ui.label("Value"); + ui.add( + Label::new(RichText::new(node.value().unwrap_or_default()).strong()) + .truncate(), + ); + ui.end_row(); + + ui.label("Children"); + ui.label(RichText::new(node.children().len().to_string()).strong()); + ui.end_row(); + }); + + ui.label("Actions"); + ui.horizontal_wrapped(|ui| { + // Iterate through all possible actions via the `Action::n` helper. + let mut current_action = 0; + let all_actions = std::iter::from_fn(|| { + let action = Action::n(current_action); + current_action += 1; + action + }); + + for action in all_actions { + if node.supports_action(action, &|_node| FilterResult::Include) + && ui.button(format!("{action:?}")).clicked() + { + let action_request = ActionRequest { + target: node.id(), + action, + data: None, + }; + self.queued_action = Some(action_request); + } + } + }); + } else { + ui.label("Node not found"); + } + } + + fn node_ui(ui: &mut Ui, node: &Node<'_>, selected_node: &mut Option) { + if node.id() == Self::id().value().into() + || node + .value() + .as_deref() + .is_some_and(|l| l.contains("AccessKit Inspector")) + { + return; + } + let label = node + .label() + .or(node.value()) + .unwrap_or(node.id().0.to_string()); + let label = format!("({:?}) {}", node.role(), label); + + // Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`. + #[expect(unsafe_code)] + let egui_node_id = unsafe { Id::from_high_entropy_bits(node.id().0) }; + + ui.push_id(node.id(), |ui| { + let child_count = node.children().len(); + let has_children = child_count > 0; + let default_open = child_count == 1 && node.role() != accesskit::Role::Label; + + let mut collapsing = CollapsingState::load_with_default_open( + ui.ctx(), + egui_node_id.with("ak_collapse"), + default_open, + ); + + let header_response = ui.horizontal(|ui| { + let text = if collapsing.is_open() { "⏷" } else { "⏵" }; + + if ui + .add_visible(has_children, Button::new(text).frame_when_inactive(false)) + .clicked() + { + collapsing.set_open(!collapsing.is_open()); + } + let label_response = + ui.selectable_value(selected_node, Some(egui_node_id), label.clone()); + if label_response.hovered() { + let widget_response = ui.ctx().read_response(egui_node_id); + + if let Some(widget_response) = widget_response { + ui.ctx() + .debug_painter() + .debug_rect(widget_response.rect, Color32::RED, ""); + } + } + }); + + if has_children { + collapsing.show_body_indented(&header_response.response, ui, |ui| { + node.children().for_each(|c| { + Self::node_ui(ui, &c, selected_node); + }); + }); + } + + collapsing.store(ui.ctx()); + }); + } +} diff --git a/crates/egui_demo_app/src/lib.rs b/crates/egui_demo_app/src/lib.rs index 9cfa26baa..05b3c4bd6 100644 --- a/crates/egui_demo_app/src/lib.rs +++ b/crates/egui_demo_app/src/lib.rs @@ -16,7 +16,8 @@ pub(crate) fn seconds_since_midnight() -> f64 { } // ---------------------------------------------------------------------------- - +#[cfg(feature = "accessibility_inspector")] +pub mod accessibility_inspector; #[cfg(target_arch = "wasm32")] mod web; diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 127139a97..bec0aacf3 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -183,6 +183,10 @@ impl WrapApp { // This gives us image support: egui_extras::install_image_loaders(&cc.egui_ctx); + #[cfg(feature = "accessibility_inspector")] + cc.egui_ctx + .add_plugin(crate::accessibility_inspector::AccessibilityInspectorPlugin::default()); + #[allow(unused_mut, clippy::allow_attributes)] let mut slf = Self { state: State::default(), From 843ceea90c648b6b0860f23052c0141e8ebac043 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 15:07:16 +0200 Subject: [PATCH 259/388] Use more workspace dependencies (#7596) --- Cargo.toml | 26 ++++++++++++++++++- crates/ecolor/Cargo.toml | 4 +-- crates/egui-winit/Cargo.toml | 17 +++++------- crates/egui_demo_app/Cargo.toml | 18 +++++-------- crates/egui_demo_lib/Cargo.toml | 6 ++--- crates/egui_extras/Cargo.toml | 16 +++++------- crates/egui_glow/Cargo.toml | 2 +- crates/emath/Cargo.toml | 2 +- crates/epaint/Cargo.toml | 4 +-- examples/confirm_exit/Cargo.toml | 5 +--- examples/custom_3d_glow/Cargo.toml | 5 +--- examples/custom_font/Cargo.toml | 5 +--- examples/custom_font_style/Cargo.toml | 5 +--- examples/custom_keypad/Cargo.toml | 5 +--- examples/custom_style/Cargo.toml | 5 +--- examples/custom_window_frame/Cargo.toml | 5 +--- examples/external_eventloop/Cargo.toml | 7 ++--- examples/external_eventloop_async/Cargo.toml | 11 +++----- examples/file_dialog/Cargo.toml | 7 ++--- examples/hello_android/Cargo.toml | 6 ++--- examples/hello_world/Cargo.toml | 5 +--- examples/hello_world_par/Cargo.toml | 5 +--- examples/hello_world_simple/Cargo.toml | 5 +--- examples/images/Cargo.toml | 5 +--- examples/keyboard_events/Cargo.toml | 5 +--- examples/multiple_viewports/Cargo.toml | 5 +--- examples/popups/Cargo.toml | 5 +--- examples/puffin_profiler/Cargo.toml | 11 +++----- examples/screenshot/Cargo.toml | 5 +--- examples/serial_windows/Cargo.toml | 7 ++--- examples/user_attention/Cargo.toml | 5 +--- tests/test_egui_extras_compilation/Cargo.toml | 2 +- tests/test_inline_glow_paint/Cargo.toml | 5 +--- tests/test_size_pass/Cargo.toml | 5 +--- tests/test_ui_stack/Cargo.toml | 5 +--- tests/test_viewports/Cargo.toml | 5 +--- 36 files changed, 92 insertions(+), 154 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e35a139f..56ca4cac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,17 +71,26 @@ eframe = { version = "0.32.3", path = "crates/eframe", default-features = false accesskit = "0.21.0" accesskit_consumer = "0.30.0" accesskit_winit = "0.29" +ab_glyph = "0.2.11" ahash = { version = "0.8.11", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", ] } +android_logger = "0.14" +arboard = { version = "3.3", default-features = false} backtrace = "0.3" bitflags = "2.6" bytemuck = "1.23.2" +chrono = { version = "0.4", default-features = false } +cint = "0.3.1" +color-hex = "0.2.0" criterion = { version = "0.7", default-features = false } dify = { version = "0.7", default-features = false } directories = "6" document-features = "0.2.10" +ehttp = { version = "0.5", default-features = false } +enum-map = "2" +env_logger = { version = "0.10", default-features = false } glow = "0.16" glutin = { version = "0.32.0", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } @@ -90,7 +99,10 @@ image = { version = "0.25", default-features = false } js-sys = "0.3" kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } log = { version = "0.4", features = ["std"] } +memoffset = "0.9" mimalloc = "0.1.46" +mime_guess2 = { version = "2", default-features = false } +mint = "0.5.6" nohash-hasher = "0.2" objc2 = "0.5.1" objc2-app-kit = { version = "0.2.0", default-features = false } @@ -99,28 +111,40 @@ objc2-ui-kit = { version = "0.2.0", default-features = false } open = "5" parking_lot = "0.12" percent-encoding = "2.1" +poll-promise = { version = "0.3", default-features = false } pollster = "0.4" profiling = { version = "1.0.16", default-features = false } puffin = "0.19" puffin_http = "0.16" +rand = "0.9" raw-window-handle = "0.6.0" +rayon = "1.7" +resvg = { version = "0.45", default-features = false } +rfd = "0.15.3" ron = "0.11" serde = { version = "1", features = ["derive"] } similar-asserts = "1.4.2" smallvec = "1" +smithay-clipboard = "0.7.2" static_assertions = "1.1.0" +syntect = { version = "5", default-features = false } tempfile = "3" thiserror = "1.0.37" +tokio = "1" type-map = "0.5.0" +unicode_names2 = { version = "0.6.0", default-features = false } unicode-segmentation = "1.12.0" -wasm-bindgen = "0.2" +wasm-bindgen = "=0.2.100" wasm-bindgen-futures = "0.4" +wayland-cursor = { version = "0.31.1", default-features = false } web-sys = "0.3.73" web-time = "1.1.0" # Timekeeping for native and web +webbrowser = "1.0.0" wgpu = { version = "27.0.0", default-features = false, features = ["std"] } windows-sys = "0.60" winit = { version = "0.30.12", default-features = false } + [workspace.lints.rust] unsafe_code = "deny" diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index c1a10070c..e8db84b3e 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -39,10 +39,10 @@ emath.workspace = true bytemuck = { workspace = true, optional = true, features = ["derive"] } ## [`cint`](https://docs.rs/cint) enables interoperability with other color libraries. -cint = { version = "0.3.1", optional = true } +cint = { workspace = true, optional = true } ## Enable the [`hex_color`] macro. -color-hex = { version = "0.2.0", optional = true } +color-hex = { workspace = true, optional = true } ## Enable this when generating docs. document-features = { workspace = true, optional = true } diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 814abef4a..45e5fa515 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -74,14 +74,11 @@ bytemuck = { workspace = true, optional = true } document-features = { workspace = true, optional = true } serde = { workspace = true, optional = true } -webbrowser = { version = "1.0.0", optional = true } +webbrowser = { workspace = true, optional = true } [target.'cfg(target_os = "ios")'.dependencies] -objc2 = { workspace = true } -objc2-foundation = { workspace = true, features = [ - "std", - "NSThread", -] } +objc2.workspace = true +objc2-foundation = { workspace = true, features = ["std", "NSThread"] } objc2-ui-kit = { workspace = true, features = [ "std", "UIApplication", @@ -95,14 +92,14 @@ objc2-ui-kit = { workspace = true, features = [ ] } [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] -smithay-clipboard = { version = "0.7.2", optional = true } +smithay-clipboard = { workspace = true, optional = true } # The wayland-cursor normally selected doesn't properly enable all the features it uses # and thus doesn't compile as it is used in egui-winit. This is fixed upstream, so force # a slightly newer version. Remove this when winit upgrades past this version. -wayland-cursor = { version = "0.31.1", default-features = false, optional = true } +wayland-cursor = { workspace = true, optional = true } [target.'cfg(not(target_os = "android"))'.dependencies] -arboard = { version = "3.3", optional = true, default-features = false, features = [ - "image-data", +arboard = { workspace = true, optional = true, features = [ + "image-data", ] } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 703347a32..aac086e16 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -47,10 +47,7 @@ wayland = ["eframe/wayland"] x11 = ["eframe/x11"] [dependencies] -chrono = { version = "0.4", default-features = false, features = [ - "js-sys", - "wasmbind", -] } +chrono = { workspace = true, features = ["js-sys", "wasmbind"] } eframe = { workspace = true, default-features = false, features = [ "web_screen_reader", ] } @@ -80,8 +77,8 @@ wgpu = { workspace = true, features = [ # feature "http": -ehttp = { version = "0.5", optional = true } -poll-promise = { version = "0.3", optional = true, default-features = false } +ehttp = { workspace = true, optional = true } +poll-promise = { workspace = true, optional = true } # feature "persistence": serde = { workspace = true, optional = true } @@ -89,16 +86,13 @@ serde = { workspace = true, optional = true } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } mimalloc.workspace = true -rfd = { version = "0.15.3", optional = true } +rfd = { workspace = true, optional = true } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "=0.2.100" +wasm-bindgen.workspace = true wasm-bindgen-futures.workspace = true web-sys.workspace = true diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index f83258e52..6d1473ea6 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -45,10 +45,10 @@ syntect = ["egui_extras/syntect"] egui = { workspace = true, default-features = false, features = ["color-hex"] } egui_extras = { workspace = true, features = ["image", "svg"] } -unicode_names2 = { version = "0.6.0", default-features = false } # this old version has fewer dependencies +unicode_names2.workspace = true # this old version has fewer dependencies #! ### Optional dependencies -chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } +chrono = { workspace = true, optional = true, features = ["js-sys", "wasmbind"] } ## Enable this when generating docs. document-features = { workspace = true, optional = true } serde = { workspace = true, optional = true } @@ -61,7 +61,7 @@ egui_extras = { workspace = true, features = ["image", "svg"] } egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } image = { workspace = true, features = ["png"] } mimalloc.workspace = true # for benchmarks -rand = "0.9" +rand.workspace = true [[bench]] name = "benchmark" diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index 15b88f8bc..b53d3f078 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -73,7 +73,7 @@ syntect = ["dep:syntect"] egui = { workspace = true, default-features = false } ahash.workspace = true -enum-map = "2" +enum-map.workspace = true log.workspace = true profiling.workspace = true @@ -83,7 +83,7 @@ profiling.workspace = true serde = { workspace = true, optional = true } # Date operations needed for datepicker widget -chrono = { version = "0.4", optional = true, default-features = false, features = [ +chrono = { workspace = true, optional = true, features = [ "clock", "js-sys", "std", @@ -96,15 +96,13 @@ document-features = { workspace = true, optional = true } image = { workspace = true, optional = true } # file feature -mime_guess2 = { version = "2", optional = true, default-features = false } +mime_guess2 = { workspace = true, optional = true } - -syntect = { version = "5", optional = true, default-features = false, features = [ - "default-fancy", -] } +# syntax highlighting +syntect = { workspace = true, optional = true, features = ["default-fancy"] } # svg feature -resvg = { version = "0.45", optional = true, default-features = false } +resvg = { workspace = true, optional = true } # http feature -ehttp = { version = "0.5", optional = true, default-features = false } +ehttp = { workspace = true, optional = true } diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 7a15d4d3c..30e74d22c 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -56,7 +56,7 @@ egui-winit = { workspace = true, optional = true, default-features = false } bytemuck.workspace = true glow.workspace = true log.workspace = true -memoffset = "0.9" +memoffset.workspace = true profiling.workspace = true #! ### Optional dependencies diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index ed098808b..416d32750 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -37,7 +37,7 @@ bytemuck = { workspace = true, optional = true, features = ["derive"] } document-features = { workspace = true, optional = true } ## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). -mint = { version = "0.5.6", optional = true } +mint = { workspace = true, optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde). serde = { workspace = true, optional = true } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 49806be8f..d287ffb38 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -66,7 +66,7 @@ _override_unity = [] emath.workspace = true ecolor.workspace = true -ab_glyph = "0.2.11" +ab_glyph.workspace = true ahash.workspace = true log.workspace = true nohash-hasher.workspace = true @@ -79,7 +79,7 @@ bytemuck = { workspace = true, optional = true, features = ["derive"] } ## Enable this when generating docs. document-features = { workspace = true, optional = true } -rayon = { version = "1.7", optional = true } +rayon = { workspace = true, optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde) . serde = { workspace = true, optional = true, features = ["derive", "rc"] } diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 59c7a1e7d..087f801af 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -16,7 +16,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index f302b6642..9a172825b 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -16,7 +16,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index e1c1a6253..4de7d16ba 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -16,7 +16,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 76c16e4a1..1f3343397 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -16,7 +16,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index b78820884..e5a7f6965 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -20,7 +20,4 @@ eframe = { workspace = true, features = [ # For image support: egui_extras = { workspace = true, features = ["default", "image"] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index 9c82fbbdc..5bbd93c8e 100644 --- a/examples/custom_style/Cargo.toml +++ b/examples/custom_style/Cargo.toml @@ -19,10 +19,7 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } egui_demo_lib.workspace = true egui_extras = { workspace = true, features = ["image"] } image = { workspace = true, features = ["png"] } diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index 99e3eda71..7dc1c7017 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -16,7 +16,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/external_eventloop/Cargo.toml b/examples/external_eventloop/Cargo.toml index ea63d9de6..c26d5ea98 100644 --- a/examples/external_eventloop/Cargo.toml +++ b/examples/external_eventloop/Cargo.toml @@ -17,9 +17,6 @@ eframe = { workspace = true, features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } -winit = { workspace = true } +winit.workspace = true diff --git a/examples/external_eventloop_async/Cargo.toml b/examples/external_eventloop_async/Cargo.toml index 1b21ddaef..ad9d30990 100644 --- a/examples/external_eventloop_async/Cargo.toml +++ b/examples/external_eventloop_async/Cargo.toml @@ -23,13 +23,10 @@ eframe = { workspace = true, features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } -log = { workspace = true } +log.workspace = true -winit = { workspace = true } +winit.workspace = true -tokio = { version = "1", features = ["rt", "time", "net"] } +tokio = { workspace = true, features = ["rt", "time", "net"] } diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 4a3f948d9..1fe1f64c4 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -16,8 +16,5 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } -rfd = "0.15.3" +env_logger = { workspace = true, features = ["auto-color", "humantime"] } +rfd.workspace = true diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index d563e48bf..880a7d7cd 100644 --- a/examples/hello_android/Cargo.toml +++ b/examples/hello_android/Cargo.toml @@ -27,9 +27,9 @@ egui_demo_lib = { workspace = true, features = ["chrono"] } # For image support: egui_extras = { workspace = true, features = ["default", "image"] } -log = { workspace = true } -winit = { workspace = true } -android_logger = "0.14" +android_logger.workspace = true +log.workspace = true +winit.workspace = true [package.metadata.android] build_targets = ["armv7-linux-androideabi", "aarch64-linux-android"] diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index d715509de..924157ef5 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -20,7 +20,4 @@ eframe = { workspace = true, features = [ # For image support: egui_extras = { workspace = true, features = ["default", "image"] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 79b698ec7..11693e985 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -23,10 +23,7 @@ eframe = { workspace = true, default-features = false, features = [ "x11", "wgpu", ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } # This is normally enabled by eframe/default, which is not being used here # because of accesskit, as mentioned above winit = { workspace = true, features = ["default"] } diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 547290d53..2c60803f5 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -16,7 +16,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index e6b8e1dde..2618618ae 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -21,8 +21,5 @@ eframe = { workspace = true, features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } egui_extras = { workspace = true, features = ["default", "all_loaders"] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } image = { workspace = true, features = ["jpeg", "png"] } diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index be99d0ef0..325b7c28d 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -16,7 +16,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 4ec653067..9810b0241 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -18,7 +18,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/popups/Cargo.toml b/examples/popups/Cargo.toml index 2f9d6b092..85038c00c 100644 --- a/examples/popups/Cargo.toml +++ b/examples/popups/Cargo.toml @@ -12,10 +12,7 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } [lints] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index a98573d38..046e33a4c 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -23,11 +23,8 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } -log = { workspace = true } -puffin = "0.19" -puffin_http = "0.16" +env_logger = { workspace = true, features = ["auto-color", "humantime"] } +log.workspace = true +puffin.workspace = true +puffin_http.workspace = true profiling = { workspace = true, features = ["profile-with-puffin"] } diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 60aa948b8..ca90a7c01 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -20,8 +20,5 @@ eframe = { workspace = true, features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO "wgpu", ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } image = { workspace = true, features = ["png"] } diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index a82060f4e..139221fac 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -16,8 +16,5 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } -log = { workspace = true } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } +log.workspace = true diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 293f3f0a8..dc25daf6e 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -15,7 +15,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index 6bfc96e3a..5a3555771 100644 --- a/tests/test_egui_extras_compilation/Cargo.toml +++ b/tests/test_egui_extras_compilation/Cargo.toml @@ -17,4 +17,4 @@ ignored = [ [dependencies] eframe = { workspace = true, features = ["default", "persistence"] } -egui_extras = { workspace = true } +egui_extras.workspace = true diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index 16a42ecbe..9935996a7 100644 --- a/tests/test_inline_glow_paint/Cargo.toml +++ b/tests/test_inline_glow_paint/Cargo.toml @@ -17,7 +17,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml index 3d8b954da..257a59d11 100644 --- a/tests/test_size_pass/Cargo.toml +++ b/tests/test_size_pass/Cargo.toml @@ -18,7 +18,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/tests/test_ui_stack/Cargo.toml b/tests/test_ui_stack/Cargo.toml index 50b62941d..c0e5ffbd2 100644 --- a/tests/test_ui_stack/Cargo.toml +++ b/tests/test_ui_stack/Cargo.toml @@ -21,7 +21,4 @@ eframe = { workspace = true, features = [ # For image support: egui_extras = { workspace = true, features = ["default", "image", "serde"] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/tests/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml index 7f19a9de3..a9fac7f8f 100644 --- a/tests/test_viewports/Cargo.toml +++ b/tests/test_viewports/Cargo.toml @@ -18,7 +18,4 @@ eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = { version = "0.10", default-features = false, features = [ - "auto-color", - "humantime", -] } +env_logger = { workspace = true, features = ["auto-color", "humantime"] } From ac4e04d0b8dda70ba0bc8355005e65fcbd018e24 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 15:30:00 +0200 Subject: [PATCH 260/388] `cargo upgrade` (#7598) --- .github/workflows/rust.yml | 2 +- Cargo.lock | 271 +++++++++++++++++++++---------------- Cargo.toml | 58 ++++---- deny.toml | 3 +- scripts/setup_web.sh | 4 +- 5 files changed, 186 insertions(+), 152 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1d4a9555d..ac575e8db 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -92,7 +92,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.100" + version: "0.2.104" - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.lock b/Cargo.lock index 8338f81b9..1d14708ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0690ad6e6f9597b8439bd3c95e8c6df5cd043afd950c6d68f3b37df641e27c" +checksum = "cf203f9d3bd8f29f98833d1fbef628df18f759248a547e7e01cfbf63cda36a99" dependencies = [ "enumn", "serde", @@ -38,15 +38,15 @@ dependencies = [ "accesskit_consumer", "atspi-common", "serde", - "thiserror 1.0.66", + "thiserror 1.0.69", "zvariant", ] [[package]] name = "accesskit_consumer" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec27574c1baeb7747c802a194566b46b602461e81dc4957949580ea8da695038" +checksum = "bdd06f5fea9819250fffd4debf926709f3593ac22f8c1541a2573e5ee0ca01cd" dependencies = [ "accesskit", "hashbrown 0.15.2", @@ -129,16 +129,16 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.16", + "getrandom 0.3.1", "once_cell", "serde", "version_check", - "zerocopy", + "zerocopy 0.8.27", ] [[package]] @@ -168,7 +168,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror 1.0.66", + "thiserror 1.0.69", ] [[package]] @@ -236,11 +236,11 @@ dependencies = [ "clipboard-win", "image", "log", - "objc2 0.6.0", - "objc2-app-kit 0.3.0", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", "windows-sys 0.60.2", @@ -290,6 +290,9 @@ dependencies = [ "serde", "serde_repr", "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", "zbus", ] @@ -601,11 +604,11 @@ dependencies = [ [[package]] name = "block2" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.0", + "objc2 0.6.3", ] [[package]] @@ -640,18 +643,18 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", @@ -687,7 +690,7 @@ dependencies = [ "polling", "rustix 0.38.38", "slab", - "thiserror 1.0.66", + "thiserror 1.0.69", ] [[package]] @@ -1164,14 +1167,14 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dispatch2" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.9.0", - "block2 0.6.0", + "block2 0.6.2", "libc", - "objc2 0.6.0", + "objc2 0.6.3", ] [[package]] @@ -1297,7 +1300,7 @@ dependencies = [ "epaint", "log", "profiling", - "thiserror 1.0.66", + "thiserror 1.0.69", "type-map", "web-time", "wgpu", @@ -1922,22 +1925,22 @@ dependencies = [ [[package]] name = "glutin" -version = "0.32.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec69412a0bf07ea7607e638b415447857a808846c2b685a43c8aa18bc6d5e499" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ "bitflags 2.9.0", "cfg_aliases", "cgl", - "core-foundation 0.9.4", - "dispatch", + "dispatch2", "glutin_egl_sys", "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", "once_cell", "raw-window-handle", "wayland-sys", @@ -1959,9 +1962,9 @@ dependencies = [ [[package]] name = "glutin_egl_sys" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae99fff4d2850dbe6fb8c1fa8e4fead5525bab715beaacfccf3fb994e01c827" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" dependencies = [ "gl_generator", "windows-sys 0.52.0", @@ -1969,9 +1972,9 @@ dependencies = [ [[package]] name = "glutin_glx_sys" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2b2d3918e76e18e08796b55eb64e8fe6ec67d5a6b2e2a7e2edce224ad24c63" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" dependencies = [ "gl_generator", "x11-dl", @@ -1979,9 +1982,9 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" dependencies = [ "gl_generator", ] @@ -2013,7 +2016,7 @@ checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", - "thiserror 1.0.66", + "thiserror 1.0.69", "windows 0.58.0", ] @@ -2124,11 +2127,11 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2387,7 +2390,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.66", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -2415,9 +2418,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -2498,9 +2501,9 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" dependencies = [ "cc", "libc", @@ -2619,9 +2622,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" dependencies = [ "libmimalloc-sys", ] @@ -2697,7 +2700,7 @@ dependencies = [ "log", "num-traits", "once_cell", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "thiserror 2.0.17", "unicode-ident", @@ -2715,7 +2718,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror 1.0.66", + "thiserror 1.0.69", ] [[package]] @@ -2816,9 +2819,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", ] @@ -2841,15 +2844,16 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.9.0", - "block2 0.6.0", - "objc2 0.6.0", + "block2 0.6.2", + "objc2 0.6.3", + "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] @@ -2890,22 +2894,24 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.9.0", - "objc2 0.6.0", + "dispatch2", + "objc2 0.6.3", ] [[package]] name = "objc2-core-graphics" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.9.0", - "objc2 0.6.0", + "dispatch2", + "objc2 0.6.3", "objc2-core-foundation", "objc2-io-surface", ] @@ -2955,23 +2961,23 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.9.0", - "objc2 0.6.0", + "objc2 0.6.3", "objc2-core-foundation", ] [[package]] name = "objc2-io-surface" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.9.0", - "objc2 0.6.0", + "objc2 0.6.3", "objc2-core-foundation", ] @@ -3345,7 +3351,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -3374,9 +3380,9 @@ dependencies = [ [[package]] name = "profiling" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" dependencies = [ "profiling-procmacros", "puffin", @@ -3384,9 +3390,9 @@ dependencies = [ [[package]] name = "profiling-procmacros" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", "syn", @@ -3520,9 +3526,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -3530,9 +3536,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -3618,19 +3624,19 @@ dependencies = [ [[package]] name = "rfd" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" dependencies = [ "ashpd", - "block2 0.6.0", + "block2 0.6.2", "dispatch2", "js-sys", "log", - "objc2 0.6.0", - "objc2-app-kit 0.3.0", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", "objc2-core-foundation", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "pollster", "raw-window-handle", "urlencoding", @@ -3694,6 +3700,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.38" @@ -3971,7 +3983,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.38", - "thiserror 1.0.66", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -4097,7 +4109,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 1.0.66", + "thiserror 1.0.69", "walkdir", "yaml-rust", ] @@ -4167,11 +4179,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.66", + "thiserror-impl 1.0.69", ] [[package]] @@ -4185,9 +4197,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -4381,11 +4393,11 @@ dependencies = [ [[package]] name = "type-map" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -4579,21 +4591,22 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -4617,9 +4630,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4627,9 +4640,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -4640,9 +4653,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -4686,11 +4699,11 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.7" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 0.38.38", + "rustix 1.0.8", "wayland-client", "xcursor", ] @@ -4758,9 +4771,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -4786,8 +4799,8 @@ dependencies = [ "jni", "log", "ndk-context", - "objc2 0.6.0", - "objc2-foundation 0.3.0", + "objc2 0.6.3", + "objc2-foundation 0.3.2", "url", "web-sys", ] @@ -4809,9 +4822,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "27.0.0" +version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a355f55850044d897fdaa72199509ff08baaa0c387c4c80599decb5ee86790b" +checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" dependencies = [ "arrayvec", "bitflags 2.9.0", @@ -4858,7 +4871,7 @@ dependencies = [ "portable-atomic", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.17", "wgpu-core-deps-apple", @@ -5685,7 +5698,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive 0.8.27", ] [[package]] @@ -5699,6 +5721,17 @@ dependencies = [ "syn", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 56ca4cac7..10c9b58d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,79 +68,79 @@ egui_glow = { version = "0.32.3", path = "crates/egui_glow", default-features = egui_kittest = { version = "0.32.3", path = "crates/egui_kittest", default-features = false } eframe = { version = "0.32.3", path = "crates/eframe", default-features = false } -accesskit = "0.21.0" -accesskit_consumer = "0.30.0" +accesskit = "0.21.1" +accesskit_consumer = "0.30.1" accesskit_winit = "0.29" -ab_glyph = "0.2.11" -ahash = { version = "0.8.11", default-features = false, features = [ +ab_glyph = "0.2.32" +ahash = { version = "0.8.12", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", ] } android_logger = "0.14" -arboard = { version = "3.3", default-features = false} +arboard = { version = "3.6", default-features = false} backtrace = "0.3" -bitflags = "2.6" -bytemuck = "1.23.2" +bitflags = "2.9" +bytemuck = "1.24.0" chrono = { version = "0.4", default-features = false } cint = "0.3.1" color-hex = "0.2.0" criterion = { version = "0.7", default-features = false } dify = { version = "0.7", default-features = false } directories = "6" -document-features = "0.2.10" +document-features = "0.2.11" ehttp = { version = "0.5", default-features = false } enum-map = "2" env_logger = { version = "0.10", default-features = false } glow = "0.16" -glutin = { version = "0.32.0", default-features = false } +glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } -home = "0.5.9" +home = "0.5.11" image = { version = "0.25", default-features = false } js-sys = "0.3" kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } log = { version = "0.4", features = ["std"] } memoffset = "0.9" -mimalloc = "0.1.46" +mimalloc = "0.1.48" mime_guess2 = { version = "2", default-features = false } -mint = "0.5.6" +mint = "0.5.9" nohash-hasher = "0.2" -objc2 = "0.5.1" -objc2-app-kit = { version = "0.2.0", default-features = false } -objc2-foundation = { version = "0.2.0", default-features = false } -objc2-ui-kit = { version = "0.2.0", default-features = false } +objc2 = "0.5.2" +objc2-app-kit = { version = "0.2.2", default-features = false } +objc2-foundation = { version = "0.2.2", default-features = false } +objc2-ui-kit = { version = "0.2.2", default-features = false } open = "5" parking_lot = "0.12" -percent-encoding = "2.1" +percent-encoding = "2.3" poll-promise = { version = "0.3", default-features = false } pollster = "0.4" -profiling = { version = "1.0.16", default-features = false } +profiling = { version = "1.0.17", default-features = false } puffin = "0.19" puffin_http = "0.16" rand = "0.9" -raw-window-handle = "0.6.0" -rayon = "1.7" +raw-window-handle = "0.6.2" +rayon = "1.11" resvg = { version = "0.45", default-features = false } -rfd = "0.15.3" +rfd = "0.15.4" ron = "0.11" serde = { version = "1", features = ["derive"] } -similar-asserts = "1.4.2" +similar-asserts = "1.7.0" smallvec = "1" smithay-clipboard = "0.7.2" static_assertions = "1.1.0" syntect = { version = "5", default-features = false } tempfile = "3" -thiserror = "1.0.37" +thiserror = "1.0.69" tokio = "1" -type-map = "0.5.0" +type-map = "0.5.1" unicode_names2 = { version = "0.6.0", default-features = false } unicode-segmentation = "1.12.0" -wasm-bindgen = "=0.2.100" +wasm-bindgen = "=0.2.104" wasm-bindgen-futures = "0.4" -wayland-cursor = { version = "0.31.1", default-features = false } -web-sys = "0.3.73" +wayland-cursor = { version = "0.31.11", default-features = false } +web-sys = "0.3.81" web-time = "1.1.0" # Timekeeping for native and web -webbrowser = "1.0.0" -wgpu = { version = "27.0.0", default-features = false, features = ["std"] } +webbrowser = "1.0.5" +wgpu = { version = "27.0.1", default-features = false, features = ["std"] } windows-sys = "0.60" winit = { version = "0.30.12", default-features = false } diff --git a/deny.toml b/deny.toml index 5d7894482..db42ae206 100644 --- a/deny.toml +++ b/deny.toml @@ -53,8 +53,9 @@ skip = [ { name = "getrandom" }, # ring / rustls (and thus ehttp) still depend on getrandom 0.2 { name = "quick-xml" }, # old version via wayland-scanner { name = "redox_syscall" }, # old version via winit - { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x + { name = "rustc-hash" }, # Small enough { name = "thiserror-impl" }, # same as above + { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x { name = "windows-sys" }, # mostly hopeless to avoid ] skip-tree = [ diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index 0193bf3e5..34016992e 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -9,6 +9,6 @@ set -x rustup target add wasm32-unknown-unknown # For generating JS bindings: -if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.100'; then - cargo install --force --quiet wasm-bindgen-cli --version 0.2.100 +if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.104'; then + cargo install --force --quiet wasm-bindgen-cli --version 0.2.104 fi From 86dc9ea64e5d4409dc56ebf9519708031dadb53c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 16:14:43 +0200 Subject: [PATCH 261/388] Inline log format args (#7600) --- crates/eframe/src/epi.rs | 2 +- crates/eframe/src/native/file_storage.rs | 17 ++++++++--------- crates/eframe/src/web/events.rs | 5 ++++- crates/egui-wgpu/src/capture.rs | 5 ++--- crates/egui-wgpu/src/renderer.rs | 2 +- crates/egui-wgpu/src/setup.rs | 2 +- crates/egui-winit/src/lib.rs | 2 +- crates/egui_demo_app/src/apps/custom3d_glow.rs | 5 +---- crates/egui_glow/examples/pure_glow.rs | 2 +- crates/egui_glow/src/lib.rs | 13 ++----------- crates/egui_glow/src/painter.rs | 4 ++-- crates/egui_glow/src/shader_version.rs | 6 +----- crates/egui_glow/src/vao.rs | 8 ++++---- crates/egui_glow/src/winit.rs | 2 +- crates/egui_kittest/src/snapshot.rs | 8 ++++---- 15 files changed, 34 insertions(+), 49 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 5502b6341..384b8e918 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -916,7 +916,7 @@ pub fn set_value(storage: &mut dyn Storage, key: &str, valu profiling::function_scope!(key); match ron::ser::to_string(value) { Ok(string) => storage.set_string(key, string), - Err(err) => log::error!("eframe failed to encode data using ron: {}", err), + Err(err) => log::error!("eframe failed to encode data using ron: {err}"), } } diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 13965841b..f26a6df74 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -118,7 +118,7 @@ impl FileStorage { pub(crate) fn from_ron_filepath(ron_filepath: impl Into) -> Self { profiling::function_scope!(); let ron_filepath: PathBuf = ron_filepath.into(); - log::debug!("Loading app state from {:?}…", ron_filepath); + log::debug!("Loading app state from {}…", ron_filepath.display()); Self { kv: read_ron(&ron_filepath).unwrap_or_default(), ron_filepath, @@ -133,9 +133,8 @@ impl FileStorage { if let Some(data_dir) = storage_dir(app_id) { if let Err(err) = std::fs::create_dir_all(&data_dir) { log::warn!( - "Saving disabled: Failed to create app path at {:?}: {}", - data_dir, - err + "Saving disabled: Failed to create app path at {}: {err}", + data_dir.display() ); None } else { @@ -197,7 +196,7 @@ fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { && !parent_dir.exists() && let Err(err) = std::fs::create_dir_all(parent_dir) { - log::warn!("Failed to create directory {parent_dir:?}: {err}"); + log::warn!("Failed to create directory {}: {err}", parent_dir.display()); } match std::fs::File::create(file_path) { @@ -210,13 +209,13 @@ fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { .to_io_writer_pretty(&mut writer, &kv, config) .and_then(|_| writer.flush().map_err(|err| err.into())) { - log::warn!("Failed to serialize app state: {}", err); + log::warn!("Failed to serialize app state: {err}"); } else { - log::trace!("Persisted to {:?}", file_path); + log::trace!("Persisted to {}", file_path.display()); } } Err(err) => { - log::warn!("Failed to create file {file_path:?}: {err}"); + log::warn!("Failed to create file {}: {err}", file_path.display()); } } } @@ -234,7 +233,7 @@ where match ron::de::from_reader(reader) { Ok(value) => Some(value), Err(err) => { - log::warn!("Failed to parse RON: {}", err); + log::warn!("Failed to parse RON: {err}"); None } } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 4f09958dc..eb0d848e0 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -998,7 +998,10 @@ fn install_drag_and_drop(runner_ref: &WebRunner, target: &EventTarget) -> Result } } Err(err) => { - log::error!("Failed to read file: {:?}", err); + log::error!( + "Failed to read file: {}", + string_from_js_value(&err) + ); } } }; diff --git a/crates/egui-wgpu/src/capture.rs b/crates/egui-wgpu/src/capture.rs index 917740061..cd42b838c 100644 --- a/crates/egui-wgpu/src/capture.rs +++ b/crates/egui-wgpu/src/capture.rs @@ -198,15 +198,14 @@ impl CaptureState { 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 + "Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {format:?}" ); return; } }; buffer_slice.map_async(wgpu::MapMode::Read, move |result| { if let Err(err) = result { - log::error!("Failed to map buffer for reading: {:?}", err); + log::error!("Failed to map buffer for reading: {err}"); return; } let buffer_slice = buffer.slice(..); diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index a626c8efa..2cb5bda5b 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -344,7 +344,7 @@ impl Renderer { fragment: Some(wgpu::FragmentState { module: &module, entry_point: Some(if output_color_format.is_srgb() { - log::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_color_format); + log::warn!("Detected a linear (sRGBA aware) framebuffer {output_color_format:?}. egui prefers Rgba8Unorm or Bgra8Unorm"); "fs_main_linear_framebuffer" } else { "fs_main_gamma_framebuffer" // this is what we prefer diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index 0aaa8ac94..bd587350e 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -64,7 +64,7 @@ impl WgpuSetup { } } - log::debug!("Creating wgpu instance with backends {:?}", backends); + log::debug!("Creating wgpu instance with backends {backends:?}"); wgpu::util::new_instance_with_webgpu_detection(&create_new.instance_descriptor) .await } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 16fc7b7b6..ed293e39a 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1064,7 +1064,7 @@ pub fn update_viewport_info( fn open_url_in_browser(_url: &str) { #[cfg(feature = "webbrowser")] if let Err(err) = webbrowser::open(_url) { - log::warn!("Failed to open url: {}", err); + log::warn!("Failed to open url: {err}"); } #[cfg(not(feature = "webbrowser"))] diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index b3ff74b12..803f6156f 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -91,10 +91,7 @@ impl RotatingTriangle { let program = gl.create_program().expect("Cannot create program"); if !shader_version.is_new_shader_interface() { - log::warn!( - "Custom 3D painting hasn't been ported to {:?}", - shader_version - ); + log::warn!("Custom 3D painting hasn't been ported to {shader_version:?}"); return None; } diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index 7f38700cd..a56b85bc1 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -66,7 +66,7 @@ impl GlutinWindowContext { .expect("failed to get window handle") .as_raw() }); - log::debug!("raw window handle: {:?}", raw_window_handle); + log::debug!("raw window handle: {raw_window_handle:?}"); let context_attributes = glutin::context::ContextAttributesBuilder::new().build(raw_window_handle); // by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index 174f02226..0b815417f 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -88,20 +88,11 @@ pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, contex if context.is_empty() { log::error!( - "GL error, at {}:{}: {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues", - file, - line, - error_str, - error_code, + "GL error, at {file}:{line}: {error_str} (0x{error_code:X}). Please file a bug at https://github.com/emilk/egui/issues" ); } else { log::error!( - "GL error, at {}:{} ({}): {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues", - file, - line, - context, - error_str, - error_code, + "GL error, at {file}:{line} ({context}): {error_str} (0x{error_code:X}). Please file a bug at https://github.com/emilk/egui/issues" ); } } diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 8409765d6..5071f1a0c 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -168,7 +168,7 @@ impl Painter { let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl)); let is_webgl_1 = shader_version == ShaderVersion::Es100; let shader_version_declaration = shader_version.version_declaration(); - log::debug!("Shader header: {:?}.", shader_version_declaration); + log::debug!("Shader header: {shader_version_declaration:?}."); let supported_extensions = gl.supported_extensions(); log::trace!("OpenGL extensions: {supported_extensions:?}"); @@ -179,7 +179,7 @@ impl Painter { // {GL,GLX,WGL}_ARB_framebuffer_sRGB, … extension.ends_with("ARB_framebuffer_sRGB") }); - log::debug!("SRGB framebuffer Support: {:?}", supports_srgb_framebuffer); + log::debug!("SRGB framebuffer Support: {supports_srgb_framebuffer}"); unsafe { let vert = compile_shader( diff --git a/crates/egui_glow/src/shader_version.rs b/crates/egui_glow/src/shader_version.rs index b655c567c..3b2ec5347 100644 --- a/crates/egui_glow/src/shader_version.rs +++ b/crates/egui_glow/src/shader_version.rs @@ -24,11 +24,7 @@ impl ShaderVersion { let shading_lang_string = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }; let shader_version = Self::parse(&shading_lang_string); - log::debug!( - "Shader version: {:?} ({:?}).", - shader_version, - shading_lang_string - ); + log::debug!("Shader version: {shader_version:?} ({shading_lang_string:?})."); shader_version } diff --git a/crates/egui_glow/src/vao.rs b/crates/egui_glow/src/vao.rs index febe67fdd..736daf2f7 100644 --- a/crates/egui_glow/src/vao.rs +++ b/crates/egui_glow/src/vao.rs @@ -118,7 +118,7 @@ fn supports_vao(gl: &glow::Context) -> bool { const OPENGL_ES_PREFIX: &str = "OpenGL ES "; let version_string = unsafe { gl.get_parameter_string(glow::VERSION) }; - log::debug!("GL version: {:?}.", version_string); + log::debug!("GL version: {version_string:?}."); // Examples: // * "WebGL 2.0 (OpenGL ES 3.0 Chromium)" @@ -129,7 +129,7 @@ fn supports_vao(gl: &glow::Context) -> bool { if version_str.contains("1.0") { // need to test OES_vertex_array_object . let supported_extensions = gl.supported_extensions(); - log::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + log::debug!("Supported OpenGL extensions: {supported_extensions:?}"); supported_extensions.contains("OES_vertex_array_object") || supported_extensions.contains("GL_OES_vertex_array_object") } else { @@ -140,7 +140,7 @@ fn supports_vao(gl: &glow::Context) -> bool { if version_string.contains("2.0") { // need to test OES_vertex_array_object . let supported_extensions = gl.supported_extensions(); - log::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + log::debug!("Supported OpenGL extensions: {supported_extensions:?}"); supported_extensions.contains("OES_vertex_array_object") || supported_extensions.contains("GL_OES_vertex_array_object") } else { @@ -152,7 +152,7 @@ fn supports_vao(gl: &glow::Context) -> bool { // I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object // but APPLE's and ATI's very old extension. let supported_extensions = gl.supported_extensions(); - log::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + log::debug!("Supported OpenGL extensions: {supported_extensions:?}"); supported_extensions.contains("ARB_vertex_array_object") || supported_extensions.contains("GL_ARB_vertex_array_object") } else { diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index 0c1a47670..49f43cb11 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -89,7 +89,7 @@ impl EguiGlow { &mut actions_requested, ); for action in actions_requested { - log::warn!("{:?} not yet supported by EguiGlow", action); + log::warn!("{action:?} not yet supported by EguiGlow"); } } diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 7e171346b..07bea5c9c 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -520,7 +520,7 @@ pub fn image_snapshot_options( match try_image_snapshot_options(current, name, options) { Ok(_) => {} Err(err) => { - panic!("{}", err); + panic!("{err}"); } } } @@ -539,7 +539,7 @@ pub fn image_snapshot(current: &image::RgbaImage, name: impl Into) { match try_image_snapshot(current, name) { Ok(_) => {} Err(err) => { - panic!("{}", err); + panic!("{err}"); } } } @@ -613,7 +613,7 @@ impl Harness<'_, State> { match self.try_snapshot_options(name, options) { Ok(_) => {} Err(err) => { - panic!("{}", err); + panic!("{err}"); } } } @@ -631,7 +631,7 @@ impl Harness<'_, State> { match self.try_snapshot(name) { Ok(_) => {} Err(err) => { - panic!("{}", err); + panic!("{err}"); } } } From 56b1def0644ccb8ecd496657e659642932b8bded Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 16:26:13 +0200 Subject: [PATCH 262/388] Update to the latest dependencies (#7599) --- Cargo.lock | 616 +++++++++++++++++++++++++++--------------- Cargo.toml | 78 +++--- crates/egui/src/os.rs | 3 +- deny.toml | 3 +- 4 files changed, 446 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d14708ee..d503a6024 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,15 +30,15 @@ dependencies = [ [[package]] name = "accesskit_atspi_common" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb511e093896d3cae0efba40322087dff59ea322308a3e6edf70f28d22f2607" +checksum = "29f73a9b855b6f4af4962a94553ef0c092b80cf5e17038724d5e30945d036f69" dependencies = [ "accesskit", "accesskit_consumer", "atspi-common", "serde", - "thiserror 1.0.69", + "thiserror 1.0.66", "zvariant", ] @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "accesskit_macos" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf962bfd305aed21133d06128ab3f4a6412031a5b8505534d55af869788af272" +checksum = "93fbaf15815f39084e0cb24950c232f0e3634702c2dfbf182ae3b4919a4a1d45" dependencies = [ "accesskit", "accesskit_consumer", @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "accesskit_unix" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2abbfb16144cca5bb2ea6acad5865b7c1e70d4fa171ceba1a52ea8e78a7515f4" +checksum = "64926a930368d52d95422b822ede15014c04536cabaa2394f99567a1f4788dc6" dependencies = [ "accesskit", "accesskit_atspi_common", @@ -86,9 +86,9 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4cd727229c389e32c1a78fe9f74dc62d7c9fb6eac98cfa1a17efde254fb2d98" +checksum = "792991159fa9ba57459de59e12e918bb90c5346fea7d40ac1a11f8632b41e63a" dependencies = [ "accesskit", "accesskit_consumer", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "accesskit_winit" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822493d0e54e6793da77525bb7235a19e4fef8418194aaf25a988bc93740d683" +checksum = "cd9db0ea66997e3f4eae4a5f2c6b6486cf206642639ee629dbbb860ace1dec87" dependencies = [ "accesskit", "accesskit_macos", @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -157,7 +157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.0", + "bitflags 2.9.4", "cc", "cesu8", "jni", @@ -168,7 +168,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror 1.0.69", + "thiserror 1.0.66", ] [[package]] @@ -177,23 +177,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_log-sys" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" [[package]] name = "android_logger" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" dependencies = [ "android_log-sys", "env_filter", @@ -215,12 +209,56 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.92" @@ -285,7 +323,7 @@ dependencies = [ "enumflags2", "futures-channel", "futures-util", - "rand", + "rand 0.9.2", "raw-window-handle", "serde", "serde_repr", @@ -514,9 +552,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -524,7 +562,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -542,30 +580,15 @@ dependencies = [ "serde", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -580,9 +603,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] @@ -685,12 +708,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "log", "polling", "rustix 0.38.38", "slab", - "thiserror 1.0.69", + "thiserror 1.0.66", ] [[package]] @@ -751,16 +774,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -853,6 +875,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "colored" version = "2.2.0" @@ -958,7 +986,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "core-foundation 0.10.1", "libc", ] @@ -1171,7 +1199,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.6.2", "libc", "objc2 0.6.3", @@ -1265,7 +1293,7 @@ dependencies = [ "web-sys", "web-time", "wgpu", - "windows-sys 0.60.2", + "windows-sys 0.61.2", "winit", ] @@ -1276,7 +1304,7 @@ dependencies = [ "accesskit", "ahash", "backtrace", - "bitflags 2.9.0", + "bitflags 2.9.4", "document-features", "emath", "epaint", @@ -1300,7 +1328,7 @@ dependencies = [ "epaint", "log", "profiling", - "thiserror 1.0.69", + "thiserror 2.0.17", "type-map", "web-time", "wgpu", @@ -1372,7 +1400,7 @@ dependencies = [ "egui_kittest", "image", "mimalloc", - "rand", + "rand 0.9.2", "serde", "unicode_names2", ] @@ -1550,14 +1578,15 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ - "humantime", - "is-terminal", + "anstream", + "anstyle", + "env_filter", + "jiff", "log", - "termcolor", ] [[package]] @@ -1651,12 +1680,13 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.11.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ - "bit-set 0.5.3", - "regex", + "bit-set", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1685,9 +1715,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", @@ -1896,9 +1926,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gl_generator" @@ -1929,7 +1959,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "cfg_aliases", "cgl", "dispatch2", @@ -1995,7 +2025,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "gpu-alloc-types", ] @@ -2005,7 +2035,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", ] [[package]] @@ -2016,7 +2046,7 @@ checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", - "thiserror 1.0.69", + "thiserror 1.0.66", "windows 0.58.0", ] @@ -2026,7 +2056,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "gpu-descriptor-types", "hashbrown 0.15.2", ] @@ -2037,7 +2067,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", ] [[package]] @@ -2134,12 +2164,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "iana-time-zone" version = "0.1.63" @@ -2273,9 +2297,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.4" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", @@ -2325,6 +2349,17 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -2334,17 +2369,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is-wsl" version = "0.4.0" @@ -2355,6 +2379,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -2379,6 +2409,30 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "jni" version = "0.21.1" @@ -2390,7 +2444,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.69", + "thiserror 1.0.66", "walkdir", "windows-sys 0.45.0", ] @@ -2412,9 +2466,9 @@ dependencies = [ [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -2515,7 +2569,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "libc", "redox_syscall 0.5.7", ] @@ -2552,19 +2606,18 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.22" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lz4_flex" @@ -2611,7 +2664,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block", "core-graphics-types 0.2.0", "foreign-types", @@ -2637,19 +2690,21 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess2" -version = "2.0.5" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" +checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca" dependencies = [ "mime", + "phf", + "phf_shared", "unicase", ] [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -2687,8 +2742,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b2e757b11b47345d44e7760e45458339bc490463d9548cd8651c53ae523153" dependencies = [ "arrayvec", - "bit-set 0.8.0", - "bitflags 2.9.0", + "bit-set", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "codespan-reporting", @@ -2712,13 +2767,13 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "jni-sys", "log", "ndk-sys", "num_enum", "raw-window-handle", - "thiserror 1.0.69", + "thiserror 1.0.66", ] [[package]] @@ -2742,7 +2797,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -2832,7 +2887,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -2848,7 +2903,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.6.2", "objc2 0.6.3", "objc2-core-foundation", @@ -2862,7 +2917,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -2886,7 +2941,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2898,7 +2953,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "dispatch2", "objc2 0.6.3", ] @@ -2909,7 +2964,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "dispatch2", "objc2 0.6.3", "objc2-core-foundation", @@ -2952,7 +3007,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "dispatch", "libc", @@ -2965,7 +3020,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "objc2 0.6.3", "objc2-core-foundation", ] @@ -2976,7 +3031,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "objc2 0.6.3", "objc2-core-foundation", ] @@ -2999,7 +3054,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3011,7 +3066,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3034,7 +3089,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -3066,7 +3121,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -3075,9 +3130,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -3088,6 +3143,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "oorandom" version = "11.1.4" @@ -3156,9 +3217,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -3166,15 +3227,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall 0.5.7", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -3191,9 +3252,63 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", + "unicase", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", + "unicase", +] [[package]] name = "pico-args" @@ -3371,9 +3486,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -3476,21 +3591,42 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -3500,7 +3636,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", ] [[package]] @@ -3559,7 +3704,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", ] [[package]] @@ -3610,9 +3755,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "resvg" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd43d1c474e9dadf09a8fdf22d713ba668b499b5117b9b9079500224e26b5b29" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" dependencies = [ "log", "pico-args", @@ -3676,7 +3821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64", - "bitflags 2.9.0", + "bitflags 2.9.4", "serde", "serde_derive", "unicode-ident", @@ -3712,7 +3857,7 @@ version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.4.14", @@ -3725,7 +3870,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.9.4", @@ -3776,7 +3921,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "bytemuck", "core_maths", "log", @@ -3839,18 +3984,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -3975,7 +4130,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "calloop", "calloop-wayland-source", "cursor-icon", @@ -3983,7 +4138,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.38", - "thiserror 1.0.69", + "thiserror 1.0.66", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -4016,12 +4171,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4030,7 +4185,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", ] [[package]] @@ -4072,9 +4227,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -4094,12 +4249,11 @@ dependencies = [ [[package]] name = "syntect" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" dependencies = [ "bincode", - "bitflags 1.3.2", "fancy-regex", "flate2", "fnv", @@ -4109,7 +4263,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.17", "walkdir", "yaml-rust", ] @@ -4179,11 +4333,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ - "thiserror-impl 1.0.69", + "thiserror-impl 1.0.66", ] [[package]] @@ -4197,9 +4351,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", @@ -4322,16 +4476,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4413,9 +4569,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" @@ -4437,9 +4593,9 @@ checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-properties" @@ -4473,9 +4629,23 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode_names2" -version = "0.6.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446c96c6dd42604779487f0a981060717156648c1706aa1f464677f03c6cc059" +checksum = "d189085656ca1203291e965444e7f6a2723fbdd1dd9f34f8482e79bafd8338a0" +dependencies = [ + "phf", + "unicode_names2_generator", +] + +[[package]] +name = "unicode_names2_generator" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1262662dc96937c71115228ce2e1d30f41db71a7a45d3459e98783ef94052214" +dependencies = [ + "phf_codegen", + "rand 0.8.5", +] [[package]] name = "untrusted" @@ -4527,9 +4697,9 @@ dependencies = [ [[package]] name = "usvg" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac8e0e3e4696253dc06167990b3fe9a2668ab66270adf949a464db4088cb354" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" dependencies = [ "base64", "data-url", @@ -4558,6 +4728,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" @@ -4618,12 +4794,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] @@ -4680,7 +4857,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "rustix 1.0.8", "wayland-backend", "wayland-scanner", @@ -4692,7 +4869,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "cursor-icon", "wayland-backend", ] @@ -4714,7 +4891,7 @@ version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4726,7 +4903,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4739,7 +4916,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4816,9 +4993,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" @@ -4827,7 +5004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" dependencies = [ "arrayvec", - "bitflags 2.9.0", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "document-features", @@ -4856,9 +5033,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "893764e276cdafec946c7f394f044e283bc8f1e445ab3fea8ad3b6dbc10c0322" dependencies = [ "arrayvec", - "bit-set 0.8.0", - "bit-vec 0.8.0", - "bitflags 2.9.0", + "bit-set", + "bit-vec", + "bitflags 2.9.4", "bytemuck", "cfg_aliases", "document-features", @@ -4927,8 +5104,8 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set 0.8.0", - "bitflags 2.9.0", + "bit-set", + "bitflags 2.9.4", "block", "bytemuck", "cfg-if", @@ -4973,7 +5150,7 @@ version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d67453b02f7adc33c452d17da1c2cad813448221df1547bce9dd4b02d3558538" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "bytemuck", "js-sys", "log", @@ -5031,7 +5208,7 @@ dependencies = [ "windows-collections", "windows-core 0.61.0", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -5065,7 +5242,7 @@ checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.2", "windows-strings 0.4.0", ] @@ -5077,7 +5254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" dependencies = [ "windows-core 0.61.0", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5130,6 +5307,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -5137,7 +5320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.0", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5155,7 +5338,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5174,7 +5357,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5213,6 +5396,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5250,7 +5442,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -5408,7 +5600,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.0", + "bitflags 2.9.4", "block2 0.5.1", "bytemuck", "calloop", @@ -5466,7 +5658,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", ] [[package]] @@ -5529,7 +5721,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.4", "dlib", "log", "once_cell", @@ -5800,9 +5992,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.13" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 10c9b58d5..7e5325bd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,78 +70,78 @@ eframe = { version = "0.32.3", path = "crates/eframe", default-features = false accesskit = "0.21.1" accesskit_consumer = "0.30.1" -accesskit_winit = "0.29" +accesskit_winit = "0.29.1" ab_glyph = "0.2.32" ahash = { version = "0.8.12", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", ] } -android_logger = "0.14" -arboard = { version = "3.6", default-features = false} -backtrace = "0.3" -bitflags = "2.9" +android_logger = "0.15.1" +arboard = { version = "3.6.1", default-features = false} +backtrace = "0.3.76" +bitflags = "2.9.4" bytemuck = "1.24.0" -chrono = { version = "0.4", default-features = false } +chrono = { version = "0.4.42", default-features = false } cint = "0.3.1" color-hex = "0.2.0" -criterion = { version = "0.7", default-features = false } -dify = { version = "0.7", default-features = false } -directories = "6" +criterion = { version = "0.7.0", default-features = false } +dify = { version = "0.7.4", default-features = false } +directories = "6.0.0" document-features = "0.2.11" -ehttp = { version = "0.5", default-features = false } -enum-map = "2" -env_logger = { version = "0.10", default-features = false } -glow = "0.16" +ehttp = { version = "0.5.0", default-features = false } +enum-map = "2.7.3" +env_logger = { version = "0.11.8", default-features = false } +glow = "0.16.0" glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } home = "0.5.11" -image = { version = "0.25", default-features = false } -js-sys = "0.3" +image = { version = "0.25.6", default-features = false } +js-sys = "0.3.81" kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } -log = { version = "0.4", features = ["std"] } -memoffset = "0.9" +log = { version = "0.4.28", features = ["std"] } +memoffset = "0.9.1" mimalloc = "0.1.48" -mime_guess2 = { version = "2", default-features = false } +mime_guess2 = { version = "2.3.1", default-features = false } mint = "0.5.9" -nohash-hasher = "0.2" +nohash-hasher = "0.2.0" objc2 = "0.5.2" objc2-app-kit = { version = "0.2.2", default-features = false } objc2-foundation = { version = "0.2.2", default-features = false } objc2-ui-kit = { version = "0.2.2", default-features = false } -open = "5" -parking_lot = "0.12" -percent-encoding = "2.3" -poll-promise = { version = "0.3", default-features = false } -pollster = "0.4" +open = "5.3.2" +parking_lot = "0.12.5" +percent-encoding = "2.3.2" +poll-promise = { version = "0.3.0", default-features = false } +pollster = "0.4.0" profiling = { version = "1.0.17", default-features = false } -puffin = "0.19" -puffin_http = "0.16" -rand = "0.9" +puffin = "0.19.1" +puffin_http = "0.16.1" +rand = "0.9.2" raw-window-handle = "0.6.2" -rayon = "1.11" -resvg = { version = "0.45", default-features = false } +rayon = "1.11.0" +resvg = { version = "0.45.1", default-features = false } rfd = "0.15.4" -ron = "0.11" -serde = { version = "1", features = ["derive"] } +ron = "0.11.0" +serde = { version = "1.0.228", features = ["derive"] } similar-asserts = "1.7.0" -smallvec = "1" +smallvec = "1.15.1" smithay-clipboard = "0.7.2" static_assertions = "1.1.0" -syntect = { version = "5", default-features = false } -tempfile = "3" -thiserror = "1.0.69" -tokio = "1" +syntect = { version = "5.3.0", default-features = false } +tempfile = "3.23.0" +thiserror = "2.0.17" +tokio = "1.47.1" type-map = "0.5.1" -unicode_names2 = { version = "0.6.0", default-features = false } +unicode_names2 = { version = "2.0.0", default-features = false } unicode-segmentation = "1.12.0" wasm-bindgen = "=0.2.104" -wasm-bindgen-futures = "0.4" +wasm-bindgen-futures = "0.4.54" wayland-cursor = { version = "0.31.11", default-features = false } web-sys = "0.3.81" web-time = "1.1.0" # Timekeeping for native and web webbrowser = "1.0.5" wgpu = { version = "27.0.1", default-features = false, features = ["std"] } -windows-sys = "0.60" +windows-sys = "0.61.2" winit = { version = "0.30.12", default-features = false } diff --git a/crates/egui/src/os.rs b/crates/egui/src/os.rs index bc76ff0d6..407a5335b 100644 --- a/crates/egui/src/os.rs +++ b/crates/egui/src/os.rs @@ -69,8 +69,7 @@ impl OperatingSystem { Self::Nix } else { log::warn!( - "egui: Failed to guess operating system from User-Agent {:?}. Please file an issue at https://github.com/emilk/egui/issues", - user_agent + "egui: Failed to guess operating system from User-Agent {user_agent:?}. Please file an issue at https://github.com/emilk/egui/issues" ); Self::Unknown diff --git a/deny.toml b/deny.toml index db42ae206..a2953d315 100644 --- a/deny.toml +++ b/deny.toml @@ -54,9 +54,10 @@ skip = [ { name = "quick-xml" }, # old version via wayland-scanner { name = "redox_syscall" }, # old version via winit { name = "rustc-hash" }, # Small enough - { name = "thiserror-impl" }, # same as above { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x + { name = "thiserror-impl" }, # same as above { name = "windows-sys" }, # mostly hopeless to avoid + { name = "zerocopy" }, # Small enough ] skip-tree = [ { name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15 From 9cb4e6a54ec123bc434d2c5ea4aac548902a29d6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 17:06:23 +0200 Subject: [PATCH 263/388] Create `egui_wgpu::RendererOptions` (#7601) --- crates/eframe/src/native/wgpu_integration.rs | 14 ++-- crates/eframe/src/web/web_painter_wgpu.rs | 20 +++-- crates/egui-wgpu/src/lib.rs | 12 +-- crates/egui-wgpu/src/renderer.rs | 88 +++++++++++++++----- crates/egui-wgpu/src/winit.rs | 31 +++---- crates/egui_kittest/src/wgpu.rs | 4 +- 6 files changed, 103 insertions(+), 66 deletions(-) diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 53269c51e..0fcfb3ea3 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -186,13 +186,15 @@ impl<'app> WgpuWinitApp<'app> { let mut painter = pollster::block_on(egui_wgpu::winit::Painter::new( egui_ctx.clone(), self.native_options.wgpu_options.clone(), - self.native_options.multisampling.max(1) as _, - egui_wgpu::depth_format_from_bits( - self.native_options.depth_buffer, - self.native_options.stencil_buffer, - ), self.native_options.viewport.transparent.unwrap_or(false), - self.native_options.dithering, + egui_wgpu::RendererOptions { + msaa_samples: self.native_options.multisampling as _, + depth_stencil_format: egui_wgpu::depth_format_from_bits( + self.native_options.depth_buffer, + self.native_options.stencil_buffer, + ), + dithering: self.native_options.dithering, + }, )); let window = Arc::new(window); diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 645f62507..9faba9dd7 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -14,7 +14,7 @@ pub(crate) struct WebPainterWgpu { surface_configuration: wgpu::SurfaceConfiguration, render_state: Option, on_surface_error: Arc SurfaceErrorAction>, - depth_format: Option, + depth_stencil_format: Option, depth_texture_view: Option, screen_capture_state: Option, capture_tx: CaptureSender, @@ -35,7 +35,7 @@ impl WebPainterWgpu { height_in_pixels: u32, ) -> Option { let device = &render_state.device; - self.depth_format.map(|depth_format| { + self.depth_stencil_format.map(|depth_stencil_format| { device .create_texture(&wgpu::TextureDescriptor { label: Some("egui_depth_texture"), @@ -47,9 +47,9 @@ impl WebPainterWgpu { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: depth_format, + format: depth_stencil_format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - view_formats: &[depth_format], + view_formats: &[depth_stencil_format], }) .create_view(&wgpu::TextureViewDescriptor::default()) }) @@ -68,15 +68,17 @@ impl WebPainterWgpu { .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .map_err(|err| format!("failed to create wgpu surface: {err}"))?; - let depth_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0); + let depth_stencil_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0); let render_state = RenderState::create( &options.wgpu_options, &instance, Some(&surface), - depth_format, - 1, - options.dithering, + egui_wgpu::RendererOptions { + dithering: options.dithering, + depth_stencil_format, + ..Default::default() + }, ) .await .map_err(|err| err.to_string())?; @@ -101,7 +103,7 @@ impl WebPainterWgpu { render_state: Some(render_state), surface, surface_configuration, - depth_format, + depth_stencil_format, depth_texture_view: None, on_surface_error: options.wgpu_options.on_surface_error.clone(), screen_capture_state: None, diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 2cd2235de..59e27e7ac 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -173,9 +173,7 @@ impl RenderState { config: &WgpuConfiguration, instance: &wgpu::Instance, compatible_surface: Option<&wgpu::Surface<'static>>, - depth_format: Option, - msaa_samples: u32, - dithering: bool, + options: RendererOptions, ) -> Result { profiling::scope!("RenderState::create"); // async yield give bad names using `profile_function` @@ -244,13 +242,7 @@ impl RenderState { }; let target_format = crate::preferred_framebuffer_format(&surface_formats)?; - let renderer = Renderer::new( - &device, - target_format, - depth_format, - msaa_samples, - dithering, - ); + let renderer = Renderer::new(&device, target_format, options); // On wasm, depending on feature flags, wgpu objects may or may not implement sync. // It doesn't make sense to switch to Rc for that special usecase, so simply disable the lint. diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 2cb5bda5b..815ee3e72 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, num::NonZeroU64, ops::Range}; use ahash::HashMap; +use bytemuck::Zeroable as _; use epaint::{PaintCallbackInfo, Primitive, Vertex, emath::NumExt as _}; use wgpu::util::DeviceExt as _; @@ -175,6 +176,57 @@ pub struct Texture { pub options: Option, } +/// Ways to configure [`Renderer`] during creation. +#[derive(Clone, Copy, Debug)] +pub struct RendererOptions { + /// Set the level of the multisampling anti-aliasing (MSAA). + /// + /// Must be a power-of-two. Higher = more smooth 3D. + /// + /// A value of `0` or `1` turns it off (default). + /// + /// `egui` already performs anti-aliasing via "feathering" + /// (controlled by [`egui::epaint::TessellationOptions`]), + /// but if you are embedding 3D in egui you may want to turn on multisampling. + pub msaa_samples: u32, + + /// What format to use for the depth and stencil buffers, + /// e.g. [`wgpu::TextureFormat::Depth32FloatStencil8`]. + /// + /// egui doesn't need depth/stencil, so the default value is `None` (no depth or stancil buffers). + pub depth_stencil_format: Option, + + /// Controls whether to apply dithering to minimize banding artifacts. + /// + /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between + /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space". + /// This means that only inputs from texture interpolation and vertex colors should be affected in practice. + /// + /// Defaults to true. + pub dithering: bool, +} + +impl RendererOptions { + /// Set options that produce the most predicatable output. + /// + /// Useful for image snapshot tests. + pub const PREDICTABLE: Self = Self { + msaa_samples: 1, + depth_stencil_format: None, + dithering: false, + }; +} + +impl Default for RendererOptions { + fn default() -> Self { + Self { + msaa_samples: 0, + depth_stencil_format: None, + dithering: true, + } + } +} + /// Renderer for a egui based GUI. pub struct Renderer { pipeline: wgpu::RenderPipeline, @@ -194,7 +246,7 @@ pub struct Renderer { next_user_texture_id: u64, samplers: HashMap, - dithering: bool, + options: RendererOptions, /// Storage for resources shared with all invocations of [`CallbackTrait`]'s methods. /// @@ -210,9 +262,7 @@ impl Renderer { pub fn new( device: &wgpu::Device, output_color_format: wgpu::TextureFormat, - output_depth_format: Option, - msaa_samples: u32, - dithering: bool, + options: RendererOptions, ) -> Self { profiling::function_scope!(); @@ -229,7 +279,7 @@ impl Renderer { label: Some("egui_uniform_buffer"), contents: bytemuck::cast_slice(&[UniformBuffer { screen_size_in_points: [0.0, 0.0], - dithering: u32::from(dithering), + dithering: u32::from(options.dithering), _padding: Default::default(), }]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, @@ -299,13 +349,15 @@ impl Renderer { push_constant_ranges: &[], }); - let depth_stencil = output_depth_format.map(|format| wgpu::DepthStencilState { - format, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::Always, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }); + let depth_stencil = options + .depth_stencil_format + .map(|format| wgpu::DepthStencilState { + format, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::Always, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }); let pipeline = { profiling::scope!("create_render_pipeline"); @@ -337,7 +389,7 @@ impl Renderer { depth_stencil, multisample: wgpu::MultisampleState { alpha_to_coverage_enabled: false, - count: msaa_samples, + count: options.msaa_samples.max(1), mask: !0, }, @@ -392,17 +444,13 @@ impl Renderer { }, uniform_buffer, // Buffers on wgpu are zero initialized, so this is indeed its current state! - previous_uniform_buffer_content: UniformBuffer { - screen_size_in_points: [0.0, 0.0], - dithering: 0, - _padding: 0, - }, + previous_uniform_buffer_content: UniformBuffer::zeroed(), uniform_bind_group, texture_bind_group_layout, textures: HashMap::default(), next_user_texture_id: 0, samplers: HashMap::default(), - dithering, + options, callback_resources: CallbackResources::default(), } } @@ -846,7 +894,7 @@ impl Renderer { let uniform_buffer_content = UniformBuffer { screen_size_in_points, - dithering: u32::from(self.dithering), + dithering: u32::from(self.options.dithering), _padding: Default::default(), }; if uniform_buffer_content != self.previous_uniform_buffer_content { diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 917e704fa..bbd19edb1 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -1,8 +1,11 @@ #![allow(clippy::missing_errors_doc)] #![allow(clippy::undocumented_unsafe_blocks)] -use crate::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}; use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer}; +use crate::{ + RendererOptions, + capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}, +}; use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet}; use std::{num::NonZeroU32, sync::Arc}; @@ -21,10 +24,8 @@ struct SurfaceState { pub struct Painter { context: Context, configuration: WgpuConfiguration, - msaa_samples: u32, + options: RendererOptions, support_transparent_backbuffer: bool, - dithering: bool, - depth_format: Option, screen_capture_state: Option, instance: wgpu::Instance, @@ -54,10 +55,8 @@ impl Painter { pub async fn new( context: Context, configuration: WgpuConfiguration, - msaa_samples: u32, - depth_format: Option, support_transparent_backbuffer: bool, - dithering: bool, + options: RendererOptions, ) -> Self { let (capture_tx, capture_rx) = capture_channel(); let instance = configuration.wgpu_setup.new_instance().await; @@ -65,10 +64,8 @@ impl Painter { Self { context, configuration, - msaa_samples, + options, support_transparent_backbuffer, - dithering, - depth_format, screen_capture_state: None, instance, @@ -204,9 +201,7 @@ impl Painter { &self.configuration, &self.instance, Some(&surface), - self.depth_format, - self.msaa_samples, - self.dithering, + self.options, ) .await?; self.render_state.get_or_insert(render_state) @@ -279,7 +274,7 @@ impl Painter { Self::configure_surface(surface_state, render_state, &self.configuration); - if let Some(depth_format) = self.depth_format { + if let Some(depth_format) = self.options.depth_stencil_format { self.depth_texture_view.insert( viewport_id, render_state @@ -292,7 +287,7 @@ impl Painter { depth_or_array_layers: 1, }, mip_level_count: 1, - sample_count: self.msaa_samples, + sample_count: self.options.msaa_samples.max(1), dimension: wgpu::TextureDimension::D2, format: depth_format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT @@ -303,7 +298,7 @@ impl Painter { ); } - if let Some(render_state) = (self.msaa_samples > 1) + if let Some(render_state) = (self.options.msaa_samples > 1) .then_some(self.render_state.as_ref()) .flatten() { @@ -320,7 +315,7 @@ impl Painter { depth_or_array_layers: 1, }, mip_level_count: 1, - sample_count: self.msaa_samples, + sample_count: self.options.msaa_samples.max(1), dimension: wgpu::TextureDimension::D2, format: texture_format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, @@ -450,7 +445,7 @@ impl Painter { }; let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default()); - let (view, resolve_target) = (self.msaa_samples > 1) + let (view, resolve_target) = (self.options.msaa_samples > 1) .then_some(self.msaa_texture_view.get(&viewport_id)) .flatten() .map_or((&target_view, None), |texture_view| { diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index d0a6e8482..ae773095d 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -67,9 +67,7 @@ pub fn create_render_state(setup: WgpuSetup) -> egui_wgpu::RenderState { }, &instance, None, - None, - 1, - false, + egui_wgpu::RendererOptions::PREDICTABLE, )) .expect("Failed to create render state") } From 683214bfe8cb63bc9b5cc9a754ab80aab836b3e1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Oct 2025 17:22:31 +0200 Subject: [PATCH 264/388] Add kittest snapshot test of italics (#7603) --- crates/egui_demo_lib/tests/misc.rs | 25 +++++++++++++++++++ .../image_blending/image_dark_x1.00.png | 3 +++ .../image_blending/image_dark_x1.41.png | 3 +++ .../image_blending/image_dark_x2.00.png | 3 +++ .../image_blending/image_light_x1.00.png | 3 +++ .../image_blending/image_light_x1.41.png | 3 +++ .../image_blending/image_light_x2.00.png | 3 +++ 7 files changed, 43 insertions(+) create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.41.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.00.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.00.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.41.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.00.png diff --git a/crates/egui_demo_lib/tests/misc.rs b/crates/egui_demo_lib/tests/misc.rs index 5a8ae2c29..ae16a8684 100644 --- a/crates/egui_demo_lib/tests/misc.rs +++ b/crates/egui_demo_lib/tests/misc.rs @@ -24,3 +24,28 @@ fn test_kerning() { } } } + +#[test] +fn test_italics() { + for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] { + for theme in [egui::Theme::Dark, egui::Theme::Light] { + let mut harness = Harness::builder() + .with_pixels_per_point(pixels_per_point) + .with_theme(theme) + .build_ui(|ui| { + ui.label(egui::RichText::new("Small italics").italics().small()); + ui.label(egui::RichText::new("Normal italics").italics()); + ui.label(egui::RichText::new("Large italics").italics().size(22.0)); + }); + harness.run(); + harness.fit_contents(); + harness.snapshot(format!( + "image_blending/image_{theme}_x{pixels_per_point:.2}", + theme = match theme { + egui::Theme::Dark => "dark", + egui::Theme::Light => "light", + } + )); + } + } +} diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png new file mode 100644 index 000000000..8792b9588 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23be15ddddc13f9628a4dc15cdc32132274a0db5e16c6b1df5cadae3d5ba55ce +size 7125 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.41.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.41.png new file mode 100644 index 000000000..505c764d5 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.41.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abfa00ef9385d380bbd188d6254f92d6839a94f368100e75a2780337438f969f +size 11068 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.00.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.00.png new file mode 100644 index 000000000..7c040d71a --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e5c9015e2005429ba83a407ed1f7d4dfbf30624f666152e82079c6ed3b3cda5 +size 17238 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.00.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.00.png new file mode 100644 index 000000000..b2291e9f1 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3319a8bca1213fc3a2dd91ead155be1e25045bc614701250bc961848cfc42176 +size 7327 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.41.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.41.png new file mode 100644 index 000000000..995789de1 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.41.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5172aa12f07b4abf4bb217b8952b4cf5cf61b688455751964a1b54433d8c05b1 +size 11709 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.00.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.00.png new file mode 100644 index 000000000..8c5e7ab03 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e3eedd952d4416af73179451c0c90bcb76635c9c3c94d37f42bdd228ddbdd03 +size 18802 From c3c08fa38a3ea99776f18448ebb978b7c54e3e84 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 8 Oct 2025 10:20:12 +0200 Subject: [PATCH 265/388] Fix preview build on contributor PRs (#7597) - I broke this in #7577 `pull_request` workflows don't have permission to comment, so we have to do this via a `pull_request_target` workflow. The point of this early comment is that the kitdiff link appears as soon as possible and isn't dependent on the preview build suceeding. --- .github/workflows/preview_build.yml | 14 -------------- .github/workflows/preview_comment.yml | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/preview_comment.yml diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 4d6447356..fe37eb8cb 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -17,20 +17,6 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 - env: - URL_SLUG: ${{ github.event.number }}-${{ github.head_ref }} - with: - message: | - Preview is being built... - - Preview will be available at https://egui-pr-preview.github.io/pr/${{ env.URL_SLUG }} - - View snapshot changes at [kitdiff](https://rerun-io.github.io/kitdiff/?url=${{ github.event.pull_request.html_url }}) - comment_tag: 'egui-preview' - - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/preview_comment.yml b/.github/workflows/preview_comment.yml new file mode 100644 index 000000000..0334e0c21 --- /dev/null +++ b/.github/workflows/preview_comment.yml @@ -0,0 +1,23 @@ +name: preview_comment.yml +on: + pull_request_target: + types: [opened] + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + env: + URL_SLUG: ${{ github.event.number }}-${{ github.head_ref }} + with: + message: | + Preview is being built... + + Preview will be available at https://egui-pr-preview.github.io/pr/${{ env.URL_SLUG }} + + View snapshot changes at [kitdiff](https://rerun-io.github.io/kitdiff/?url=${{ github.event.pull_request.html_url }}) + comment_tag: 'egui-preview' + From 0d47abcaa00110ed097f3451956ae613e1a81df1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Oct 2025 10:43:11 +0200 Subject: [PATCH 266/388] Better docs + deprecation for screen/content/viewport_rect (#7605) --- crates/egui/src/context.rs | 10 +++++++++- crates/egui/src/input_state/mod.rs | 29 ++++++++++++++++++----------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 44c4d8fa5..2e36bdde3 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2670,6 +2670,8 @@ impl Context { /// /// Returns [`Self::viewport_rect`] minus areas that might be partially covered by, for example, /// the OS status bar or display notches. + /// + /// If you want to render behind e.g. the dynamic island on iOS, use [`Self::viewport_rect`]. pub fn content_rect(&self) -> Rect { self.input(|i| i.content_rect()).round_ui() } @@ -2678,13 +2680,19 @@ impl Context { /// /// This includes reas that might be partially covered by, for example, the OS status bar or /// display notches. See [`Self::content_rect`] to get a rect that is safe for content. + /// + /// This rectangle includes e.g. the dynamic island on iOS. + /// If you want to only render _below_ the that (not behind), then you should use + /// [`Self::content_rect`] instead. + /// + /// See also [`RawInput::safe_area_insets`]. pub fn viewport_rect(&self) -> Rect { self.input(|i| i.viewport_rect()).round_ui() } /// Position and size of the egui area. #[deprecated( - note = "screen_rect has been renamed to viewport_rect. Consider switching to content_rect." + note = "screen_rect has been split into viewport_rect() and content_rect(). You likely should use content_rect()" )] pub fn screen_rect(&self) -> Rect { self.input(|i| i.content_rect()).round_ui() diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index e78342ace..b23a47828 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -583,28 +583,35 @@ impl InputState { self.raw.viewport() } - /// Returns the full area available to egui, including parts that might be partially covered, - /// for example, by the OS status bar or notches (see [`Self::safe_area_insets`]). - /// - /// Usually you want to use [`Self::content_rect`] instead. - pub fn viewport_rect(&self) -> Rect { - self.viewport_rect - } - /// Returns the region of the screen that is safe for content rendering /// /// Returns the `viewport_rect` with the `safe_area_insets` removed. + /// + /// If you want to render behind e.g. the dynamic island on iOS, use [`Self::viewport_rect`]. + /// + /// See also [`RawInput::safe_area_insets`]. #[inline(always)] pub fn content_rect(&self) -> Rect { self.viewport_rect - self.safe_area_insets } - /// Returns the full area available to egui, including parts that might be partially - /// covered, for example, by the OS status bar or notches. + /// Returns the full area available to egui, including parts that might be partially covered, + /// for example, by the OS status bar or notches (see [`Self::safe_area_insets`]). /// /// Usually you want to use [`Self::content_rect`] instead. + /// + /// This rectangle includes e.g. the dynamic island on iOS. + /// If you want to only render _below_ the that (not behind), then you should use + /// [`Self::content_rect`] instead. + /// + /// See also [`RawInput::safe_area_insets`]. + pub fn viewport_rect(&self) -> Rect { + self.viewport_rect + } + + /// Position and size of the egui area. #[deprecated( - note = "screen_rect has been renamed to viewport_rect. Consider switching to content_rect." + note = "screen_rect has been split into viewport_rect() and content_rect(). You likely should use content_rect()" )] pub fn screen_rect(&self) -> Rect { self.content_rect() From 6a49c9ad6b1ea39ce9d5f2dc9f529db556e5c7ea Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Oct 2025 10:44:17 +0200 Subject: [PATCH 267/388] Unwind minimum home version --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d503a6024..aba222cc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -888,7 +888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2157,11 +2157,11 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "home" -version = "0.5.11" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5180,7 +5180,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7e5325bd1..9a87fb1c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ env_logger = { version = "0.11.8", default-features = false } glow = "0.16.0" glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } -home = "0.5.11" +home = "0.5.9" # need to puse to an old version because newer version are incompatible with wasm (at least when building Rerun) image = { version = "0.25.6", default-features = false } js-sys = "0.3.81" kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } From 3fdc5641aa4dfc1c39245d2020ccf2374dfc523b Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 8 Oct 2025 11:30:32 +0200 Subject: [PATCH 268/388] Group AccessKit nodes by `Ui` (#7386) * closes https://github.com/emilk/egui/issues/5674 This changes egui to create an AccessKit node for each `Ui`. I'm not sure if this alone will directly improve accessibility, but it should make it easier to create the correct parent / child relations (e.g. grouping menus as children of menu buttons). Instead of having a global stack of parent ids, they are now passed via a parent_id field in `UiBuilder`. If having all these `GenericContainer` nodes somehow is bad for accessibility, the PR could also be changed to only create nodes if there is actually some accessibility info with it (the relevant is currently commented-out in the PR). But I think screen readers should just ignore these nodes, so it should be fine? We could also use this as motivation to git red of some unnecessary wrapped `Ui`s, e.g. CentralPanel creates 3 Uis when 2 should be enough (the initial Ui and a Frame, maybe we could even only show the `Frame` if we can give it an UiBuilder and somehow show the Frame with `Ui::new`). Here is a screenshot from the accessibility inspector (https://github.com/emilk/egui/pull/7368) with this PR: Screenshot 2025-07-24 at 12 09 55 Without this PR: https://github.com/user-attachments/assets/270e32fc-9c7a-4dad-8c90-7638c487a602 --- crates/egui/src/containers/area.rs | 1 + crates/egui/src/containers/panel.rs | 6 +- crates/egui/src/containers/window.rs | 202 +++++++++--------- crates/egui/src/context.rs | 72 +++---- crates/egui/src/data/output.rs | 1 + crates/egui/src/lib.rs | 2 + crates/egui/src/pass_state.rs | 2 +- crates/egui/src/response.rs | 1 + .../egui/src/text_selection/accesskit_text.rs | 110 +++++----- crates/egui/src/ui.rs | 31 +++ crates/egui/src/ui_builder.rs | 18 ++ .../src/accessibility_inspector.rs | 31 ++- crates/egui_kittest/tests/accesskit.rs | 59 +++-- tests/egui_tests/tests/test_widgets.rs | 15 +- 14 files changed, 302 insertions(+), 249 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 22ab67d3e..1c3e058b3 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -595,6 +595,7 @@ impl Prepared { .layer_id(self.layer_id) .max_rect(max_rect) .layout(self.layout) + .accessibility_parent(self.move_response.id) .closable(); if !self.enabled { diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 15d1b32b8..6e582b428 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -19,7 +19,8 @@ use emath::GuiRounding as _; use crate::{ Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef, - Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, lerp, vec2, + Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetType, lerp, + vec2, }; fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 { @@ -390,6 +391,9 @@ impl SidePanel { .max_rect(available_rect), ); panel_ui.set_clip_rect(ctx.content_rect()); + panel_ui + .response() + .widget_info(|| WidgetInfo::new(WidgetType::Panel)); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); let rect = inner_response.response.rect; diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index b5ac1cc40..da7e65c1b 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -505,15 +505,14 @@ impl Window<'_> { // First check for resize to avoid frame delay: let last_frame_outer_rect = area.state().rect(); - let resize_interaction = ctx.with_accessibility_parent(area.id(), || { - resize_interaction( - ctx, - possible, - area_layer_id, - last_frame_outer_rect, - window_frame, - ) - }); + let resize_interaction = resize_interaction( + ctx, + possible, + area.id(), + area_layer_id, + last_frame_outer_rect, + window_frame, + ); { let margins = window_frame.total_margin().sum() @@ -538,109 +537,107 @@ impl Window<'_> { } let content_inner = { - ctx.with_accessibility_parent(area.id(), || { - // BEGIN FRAME -------------------------------- - let mut frame = window_frame.begin(&mut area_content_ui); + // BEGIN FRAME -------------------------------- + let mut frame = window_frame.begin(&mut area_content_ui); - let show_close_button = open.is_some(); + let show_close_button = open.is_some(); - let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop); + let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop); - let title_bar = if with_title_bar { - let title_bar = TitleBar::new( - &frame.content_ui, - title, - show_close_button, - collapsible, - window_frame, - title_bar_height_with_margin, - ); - resize.min_size.x = resize.min_size.x.at_least(title_bar.inner_rect.width()); // Prevent making window smaller than title bar width - - frame.content_ui.set_min_size(title_bar.inner_rect.size()); - - // Skip the title bar (and separator): - if is_collapsed { - frame.content_ui.add_space(title_bar.inner_rect.height()); - } else { - frame.content_ui.add_space( - title_bar.inner_rect.height() - + title_content_spacing - + window_frame.inner_margin.sum().y, - ); - } - - Some(title_bar) - } else { - None - }; - - let (content_inner, content_response) = collapsing - .show_body_unindented(&mut frame.content_ui, |ui| { - resize.show(ui, |ui| { - if scroll.is_any_scroll_enabled() { - scroll.show(ui, add_contents).inner - } else { - add_contents(ui) - } - }) - }) - .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); - - let outer_rect = frame.end(&mut area_content_ui).rect; - paint_resize_corner( - &area_content_ui, - &possible, - outer_rect, - &window_frame, - resize_interaction, + let title_bar = if with_title_bar { + let title_bar = TitleBar::new( + &frame.content_ui, + title, + show_close_button, + collapsible, + window_frame, + title_bar_height_with_margin, ); + resize.min_size.x = resize.min_size.x.at_least(title_bar.inner_rect.width()); // Prevent making window smaller than title bar width - // END FRAME -------------------------------- + frame.content_ui.set_min_size(title_bar.inner_rect.size()); - if let Some(mut title_bar) = title_bar { - title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width); - title_bar.inner_rect.max.y = - title_bar.inner_rect.min.y + title_bar_height_with_margin; - - if on_top && area_content_ui.visuals().window_highlight_topmost { - let mut round = - window_frame.corner_radius - window_frame.stroke.width.round() as u8; - - if !is_collapsed { - round.se = 0; - round.sw = 0; - } - - area_content_ui.painter().set( - *where_to_put_header_background, - RectShape::filled(title_bar.inner_rect, round, header_color), - ); - } - - if false { - ctx.debug_painter().debug_rect( - title_bar.inner_rect, - Color32::LIGHT_BLUE, - "title_bar.rect", - ); - } - - title_bar.ui( - &mut area_content_ui, - &content_response, - open.as_deref_mut(), - &mut collapsing, - collapsible, + // Skip the title bar (and separator): + if is_collapsed { + frame.content_ui.add_space(title_bar.inner_rect.height()); + } else { + frame.content_ui.add_space( + title_bar.inner_rect.height() + + title_content_spacing + + window_frame.inner_margin.sum().y, ); } - collapsing.store(ctx); + Some(title_bar) + } else { + None + }; - paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction); + let (content_inner, content_response) = collapsing + .show_body_unindented(&mut frame.content_ui, |ui| { + resize.show(ui, |ui| { + if scroll.is_any_scroll_enabled() { + scroll.show(ui, add_contents).inner + } else { + add_contents(ui) + } + }) + }) + .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); - content_inner - }) + let outer_rect = frame.end(&mut area_content_ui).rect; + paint_resize_corner( + &area_content_ui, + &possible, + outer_rect, + &window_frame, + resize_interaction, + ); + + // END FRAME -------------------------------- + + if let Some(mut title_bar) = title_bar { + title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width); + title_bar.inner_rect.max.y = + title_bar.inner_rect.min.y + title_bar_height_with_margin; + + if on_top && area_content_ui.visuals().window_highlight_topmost { + let mut round = + window_frame.corner_radius - window_frame.stroke.width.round() as u8; + + if !is_collapsed { + round.se = 0; + round.sw = 0; + } + + area_content_ui.painter().set( + *where_to_put_header_background, + RectShape::filled(title_bar.inner_rect, round, header_color), + ); + } + + if false { + ctx.debug_painter().debug_rect( + title_bar.inner_rect, + Color32::LIGHT_BLUE, + "title_bar.rect", + ); + } + + title_bar.ui( + &mut area_content_ui, + &content_response, + open.as_deref_mut(), + &mut collapsing, + collapsible, + ); + } + + collapsing.store(ctx); + + paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction); + + content_inner }; let full_response = area.end(ctx, area_content_ui); @@ -882,6 +879,7 @@ fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Opt fn resize_interaction( ctx: &Context, possible: PossibleInteractions, + _accessibility_parent: Id, layer_id: LayerId, outer_rect: Rect, window_frame: Frame, @@ -901,6 +899,8 @@ fn resize_interaction( let rect = outer_rect.shrink(window_frame.stroke.width / 2.0); let side_response = |rect, id| { + #[cfg(feature = "accesskit")] + ctx.register_accesskit_parent(id, _accessibility_parent); let response = ctx.create_widget( WidgetRect { layer_id, diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 2e36bdde3..aab81ab2c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -519,7 +519,7 @@ impl ContextImpl { nodes.insert(id, root_node); viewport.this_pass.accesskit_state = Some(AccessKitPassState { nodes, - parent_stack: vec![id], + parent_map: IdMap::default(), }); } @@ -595,8 +595,28 @@ impl ContextImpl { let builders = &mut state.nodes; if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { entry.insert(Default::default()); - let parent_id = state.parent_stack.last().unwrap(); - let parent_builder = builders.get_mut(parent_id).unwrap(); + + /// Find the first ancestor that already has an accesskit node. + fn find_accesskit_parent( + parent_map: &IdMap, + node_map: &IdMap, + id: Id, + ) -> Option { + if let Some(parent_id) = parent_map.get(&id) { + if node_map.contains_key(parent_id) { + Some(*parent_id) + } else { + find_accesskit_parent(parent_map, node_map, *parent_id) + } + } else { + None + } + } + + let parent_id = find_accesskit_parent(&state.parent_map, builders, id) + .unwrap_or(crate::accesskit_root_id()); + + let parent_builder = builders.get_mut(&parent_id).unwrap(); parent_builder.push_child(id.accesskit_id()); } builders.get_mut(&id).unwrap() @@ -3464,43 +3484,10 @@ impl Context { /// ## Accessibility impl Context { - /// Call the provided function with the given ID pushed on the stack of - /// parent IDs for accessibility purposes. If the `accesskit` feature - /// is disabled or if AccessKit support is not active for this frame, - /// the function is still called, but with no other effect. - /// - /// No locks are held while the given closure is called. - #[allow(clippy::unused_self, clippy::let_and_return, clippy::allow_attributes)] - #[inline] - pub fn with_accessibility_parent(&self, _id: Id, f: impl FnOnce() -> R) -> R { - // TODO(emilk): this isn't thread-safe - another thread can call this function between the push/pop calls - #[cfg(feature = "accesskit")] - self.pass_state_mut(|fs| { - if let Some(state) = fs.accesskit_state.as_mut() { - state.parent_stack.push(_id); - } - }); - - let result = f(); - - #[cfg(feature = "accesskit")] - self.pass_state_mut(|fs| { - if let Some(state) = fs.accesskit_state.as_mut() { - assert_eq!( - state.parent_stack.pop(), - Some(_id), - "Mismatched push/pop in with_accessibility_parent" - ); - } - }); - - result - } - /// If AccessKit support is active for the current frame, get or create /// a node builder with the specified ID and return a mutable reference to it. - /// For newly created nodes, the parent is the node with the ID at the top - /// of the stack managed by [`Context::with_accessibility_parent`]. + /// For newly created nodes, the parent is the parent [`Ui`]s ID. + /// And an [`Ui`]s parent can be set with [`crate::UiBuilder::accessibility_parent`]. /// /// The `Context` lock is held while the given closure is called! /// @@ -3522,6 +3509,15 @@ impl Context { }) } + #[cfg(feature = "accesskit")] + pub(crate) fn register_accesskit_parent(&self, id: Id, parent_id: Id) { + self.write(|ctx| { + if let Some(state) = ctx.viewport().this_pass.accesskit_state.as_mut() { + state.parent_map.insert(id, parent_id); + } + }); + } + /// Enable generation of AccessKit tree updates in all future frames. #[cfg(feature = "accesskit")] pub fn enable_accesskit(&self) { diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index bd7f62913..deec5162d 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -682,6 +682,7 @@ impl WidgetInfo { WidgetType::ColorButton => "color button", WidgetType::Image => "image", WidgetType::CollapsingHeader => "collapsing header", + WidgetType::Panel => "panel", WidgetType::ProgressIndicator => "progress indicator", WidgetType::Window => "window", WidgetType::Label | WidgetType::Other => "", diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index c62d7c16c..960480b23 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -674,6 +674,8 @@ pub enum WidgetType { CollapsingHeader, + Panel, + ProgressIndicator, Window, diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index 66a1f6e10..2be7e5098 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -71,7 +71,7 @@ impl ScrollTarget { #[derive(Clone)] pub struct AccessKitPassState { pub nodes: IdMap, - pub parent_stack: Vec, + pub parent_map: IdMap, } #[cfg(debug_assertions)] diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 06ac7bc0c..e17c1aff5 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -858,6 +858,7 @@ impl Response { WidgetType::Slider => Role::Slider, WidgetType::DragValue => Role::SpinButton, WidgetType::ColorButton => Role::ColorWell, + WidgetType::Panel => Role::Pane, WidgetType::ProgressIndicator => Role::ProgressIndicator, WidgetType::Window => Role::Window, WidgetType::Other => Role::Unknown, diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index b18995542..4d64229c5 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -40,60 +40,60 @@ pub fn update_accesskit_for_text_widget( return; }; - ctx.with_accessibility_parent(parent_id, || { - for (row_index, row) in galley.rows.iter().enumerate() { - let row_id = parent_id.with(row_index); - ctx.accesskit_node_builder(row_id, |builder| { - builder.set_role(accesskit::Role::TextRun); - let rect = global_from_galley * row.rect_without_leading_space(); - builder.set_bounds(accesskit::Rect { - x0: rect.min.x.into(), - y0: rect.min.y.into(), - x1: rect.max.x.into(), - y1: rect.max.y.into(), - }); - builder.set_text_direction(accesskit::TextDirection::LeftToRight); - // TODO(mwcampbell): Set more node fields for the row - // once AccessKit adapters expose text formatting info. - - let glyph_count = row.glyphs.len(); - let mut value = String::new(); - value.reserve(glyph_count); - let mut character_lengths = Vec::::with_capacity(glyph_count); - let mut character_positions = Vec::::with_capacity(glyph_count); - let mut character_widths = Vec::::with_capacity(glyph_count); - let mut word_lengths = Vec::::new(); - let mut was_at_word_end = false; - let mut last_word_start = 0usize; - - for glyph in &row.glyphs { - let is_word_char = is_word_char(glyph.chr); - if is_word_char && was_at_word_end { - word_lengths.push((character_lengths.len() - last_word_start) as _); - last_word_start = character_lengths.len(); - } - was_at_word_end = !is_word_char; - let old_len = value.len(); - value.push(glyph.chr); - character_lengths.push((value.len() - old_len) as _); - character_positions.push(glyph.pos.x - row.pos.x); - character_widths.push(glyph.advance_width); - } - - if row.ends_with_newline { - value.push('\n'); - character_lengths.push(1); - character_positions.push(row.size.x); - character_widths.push(0.0); - } - word_lengths.push((character_lengths.len() - last_word_start) as _); - - builder.set_value(value); - builder.set_character_lengths(character_lengths); - builder.set_character_positions(character_positions); - builder.set_character_widths(character_widths); - builder.set_word_lengths(word_lengths); + for (row_index, row) in galley.rows.iter().enumerate() { + let row_id = parent_id.with(row_index); + #[cfg(feature = "accesskit")] + ctx.register_accesskit_parent(row_id, parent_id); + ctx.accesskit_node_builder(row_id, |builder| { + builder.set_role(accesskit::Role::TextRun); + let rect = global_from_galley * row.rect_without_leading_space(); + builder.set_bounds(accesskit::Rect { + x0: rect.min.x.into(), + y0: rect.min.y.into(), + x1: rect.max.x.into(), + y1: rect.max.y.into(), }); - } - }); + builder.set_text_direction(accesskit::TextDirection::LeftToRight); + // TODO(mwcampbell): Set more node fields for the row + // once AccessKit adapters expose text formatting info. + + let glyph_count = row.glyphs.len(); + let mut value = String::new(); + value.reserve(glyph_count); + let mut character_lengths = Vec::::with_capacity(glyph_count); + let mut character_positions = Vec::::with_capacity(glyph_count); + let mut character_widths = Vec::::with_capacity(glyph_count); + let mut word_lengths = Vec::::new(); + let mut was_at_word_end = false; + let mut last_word_start = 0usize; + + for glyph in &row.glyphs { + let is_word_char = is_word_char(glyph.chr); + if is_word_char && was_at_word_end { + word_lengths.push((character_lengths.len() - last_word_start) as _); + last_word_start = character_lengths.len(); + } + was_at_word_end = !is_word_char; + let old_len = value.len(); + value.push(glyph.chr); + character_lengths.push((value.len() - old_len) as _); + character_positions.push(glyph.pos.x - row.pos.x); + character_widths.push(glyph.advance_width); + } + + if row.ends_with_newline { + value.push('\n'); + character_lengths.push(1); + character_positions.push(row.size.x); + character_widths.push(0.0); + } + word_lengths.push((character_lengths.len() - last_word_start) as _); + + builder.set_value(value); + builder.set_character_lengths(character_lengths); + builder.set_character_positions(character_positions); + builder.set_character_widths(character_widths); + builder.set_word_lengths(word_lengths); + }); + } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index e2d89c0bc..d746b8fec 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -133,6 +133,8 @@ impl Ui { sizing_pass, style, sense, + #[cfg(feature = "accesskit")] + accessibility_parent, } = ui_builder; let layer_id = layer_id.unwrap_or(LayerId::background()); @@ -173,6 +175,12 @@ impl Ui { min_rect_already_remembered: false, }; + #[cfg(feature = "accesskit")] + if let Some(accessibility_parent) = accessibility_parent { + ui.ctx() + .register_accesskit_parent(ui.unique_id, accessibility_parent); + } + // Register in the widget stack early, to ensure we are behind all widgets we contain: let start_rect = Rect::NOTHING; // This will be overwritten when `remember_min_rect` is called ui.ctx().create_widget( @@ -194,6 +202,11 @@ impl Ui { ui.set_invisible(); } + #[cfg(feature = "accesskit")] + ui.ctx().accesskit_node_builder(ui.unique_id, |node| { + node.set_role(accesskit::Role::GenericContainer); + }); + ui } @@ -260,6 +273,8 @@ impl Ui { sizing_pass, style, sense, + #[cfg(feature = "accesskit")] + accessibility_parent, } = ui_builder; let mut painter = self.painter.clone(); @@ -328,6 +343,12 @@ impl Ui { child_ui.disable(); } + #[cfg(feature = "accesskit")] + child_ui.ctx().register_accesskit_parent( + child_ui.unique_id, + accessibility_parent.unwrap_or(self.unique_id), + ); + // Register in the widget stack early, to ensure we are behind all widgets we contain: let start_rect = Rect::NOTHING; // This will be overwritten when `remember_min_rect` is called child_ui.ctx().create_widget( @@ -342,6 +363,13 @@ impl Ui { true, ); + #[cfg(feature = "accesskit")] + child_ui + .ctx() + .accesskit_node_builder(child_ui.unique_id, |node| { + node.set_role(accesskit::Role::GenericContainer); + }); + child_ui } @@ -1101,6 +1129,9 @@ impl Ui { impl Ui { /// Check for clicks, drags and/or hover on a specific region of this [`Ui`]. pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response { + #[cfg(feature = "accesskit")] + self.ctx().register_accesskit_parent(id, self.unique_id); + self.ctx().create_widget( WidgetRect { id, diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index e83148da6..51b8ec8a5 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -24,6 +24,8 @@ pub struct UiBuilder { pub sizing_pass: bool, pub style: Option>, pub sense: Option, + #[cfg(feature = "accesskit")] + pub accessibility_parent: Option, } impl UiBuilder { @@ -180,4 +182,20 @@ impl UiBuilder { .insert(ClosableTag::NAME, Some(Arc::new(ClosableTag::default()))); self } + + /// Set the accessibility parent for this [`Ui`]. + /// + /// This will override the automatic parent assignment for accessibility purposes. + /// If not set, the parent [`Ui`]'s ID will be used as the accessibility parent. + /// + /// This does nothing if the `accesskit` feature is not enabled. + #[cfg_attr(not(feature = "accesskit"), expect(unused_mut, unused_variables))] + #[inline] + pub fn accessibility_parent(mut self, parent_id: Id) -> Self { + #[cfg(feature = "accesskit")] + { + self.accessibility_parent = Some(parent_id); + } + self + } } diff --git a/crates/egui_demo_app/src/accessibility_inspector.rs b/crates/egui_demo_app/src/accessibility_inspector.rs index df80149de..f721b710c 100644 --- a/crates/egui_demo_app/src/accessibility_inspector.rs +++ b/crates/egui_demo_app/src/accessibility_inspector.rs @@ -87,23 +87,21 @@ impl egui::Plugin for AccessibilityInspectorPlugin { ctx.enable_accesskit(); SidePanel::right(Self::id()).show(ctx, |ui| { - let response = ui.heading("🔎 AccessKit Inspector"); - ctx.with_accessibility_parent(response.id, || { - if let Some(selected_node) = self.selected_node { - TopBottomPanel::bottom(Self::id().with("details_panel")) - .frame(Frame::new()) - .show_separator_line(false) - .show_inside(ui, |ui| { - self.selection_ui(ui, selected_node); - }); - } + ui.heading("🔎 AccessKit Inspector"); + if let Some(selected_node) = self.selected_node { + TopBottomPanel::bottom(Self::id().with("details_panel")) + .frame(Frame::new()) + .show_separator_line(false) + .show_inside(ui, |ui| { + self.selection_ui(ui, selected_node); + }); + } - ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); - ScrollArea::vertical().show(ui, |ui| { - if let Some(tree) = &self.tree { - Self::node_ui(ui, &tree.state().root(), &mut self.selected_node); - } - }); + ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); + ScrollArea::vertical().show(ui, |ui| { + if let Some(tree) = &self.tree { + Self::node_ui(ui, &tree.state().root(), &mut self.selected_node); + } }); }); } @@ -157,6 +155,7 @@ impl AccessibilityInspectorPlugin { ui.label("Children"); ui.label(RichText::new(node.children().len().to_string()).strong()); + ui.end_row(); }); diff --git a/crates/egui_kittest/tests/accesskit.rs b/crates/egui_kittest/tests/accesskit.rs index 3f1f33ba9..08a96bd7a 100644 --- a/crates/egui_kittest/tests/accesskit.rs +++ b/crates/egui_kittest/tests/accesskit.rs @@ -18,9 +18,20 @@ fn empty_ui_should_return_tree_with_only_root_window() { assert_eq!( output.nodes.len(), - 1, - "Empty ui should produce only the root window." + 4, + "Expected the root node and two Uis and a Frame for the panel" ); + + assert_eq!( + output + .nodes + .iter() + .filter(|(_, n)| n.role() == Role::GenericContainer) + .count(), + 3, + "Expected two Uis and one Frame as GenericContainer nodes.", + ); + let (id, root) = &output.nodes[0]; assert_eq!(*id, output.tree.unwrap().root); @@ -35,12 +46,6 @@ fn button_node() { CentralPanel::default().show(ctx, |ui| ui.button(button_text)); }); - assert_eq!( - output.nodes.len(), - 2, - "Expected only the root node and the button." - ); - let (_, button) = output .nodes .iter() @@ -61,12 +66,6 @@ fn disabled_button_node() { }); }); - assert_eq!( - output.nodes.len(), - 2, - "Expected only the root node and the button." - ); - let (_, button) = output .nodes .iter() @@ -86,12 +85,6 @@ fn toggle_button_node() { CentralPanel::default().show(ctx, |ui| ui.toggle_value(&mut selected, button_text)); }); - assert_eq!( - output.nodes.len(), - 2, - "Expected only the root node and the button." - ); - let (_, toggle) = output .nodes .iter() @@ -114,12 +107,6 @@ fn multiple_disabled_widgets() { }); }); - assert_eq!( - output.nodes.len(), - 4, - "Expected the root node and all the child widgets." - ); - assert_eq!( output .nodes @@ -194,15 +181,25 @@ fn assert_window_exists(tree: &TreeUpdate, title: &str, parent: NodeId) -> NodeI } #[track_caller] -fn assert_parent_child(tree: &TreeUpdate, parent: NodeId, child: NodeId) { +fn assert_parent_child(tree: &TreeUpdate, parent_id: NodeId, child: NodeId) { + assert!( + has_child_recursively(tree, parent_id, child), + "Node is not a child of the given parent." + ); +} + +fn has_child_recursively(tree: &TreeUpdate, parent: NodeId, child: NodeId) -> bool { let (_, parent) = tree .nodes .iter() .find(|(id, _)| id == &parent) .expect("Parent does not exist."); - assert!( - parent.children().contains(&child), - "Node is not a child of the given parent." - ); + for &c in parent.children() { + if c == child || has_child_recursively(tree, c, child) { + return true; + } + } + + false } diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs index a6050e95b..6a75e36a3 100644 --- a/tests/egui_tests/tests/test_widgets.rs +++ b/tests/egui_tests/tests/test_widgets.rs @@ -1,3 +1,4 @@ +use egui::accesskit::Role; use egui::load::SizedTexture; use egui::{ Align, AtomExt as _, AtomLayout, Button, Color32, ColorImage, Direction, DragValue, Event, @@ -277,8 +278,8 @@ impl<'a> VisualTests<'a> { node.hover(); }); self.add("pressed", |harness| { - harness.get_next().hover(); - let rect = harness.get_next().rect(); + harness.get_next_widget().hover(); + let rect = harness.get_next_widget().rect(); harness.input_mut().events.push(Event::PointerButton { button: PointerButton::Primary, pos: rect.center(), @@ -329,7 +330,7 @@ impl<'a> VisualTests<'a> { pub fn add_node(&mut self, name: &str, test: impl FnOnce(&Node<'_>)) { self.add(name, |harness| { - let node = harness.get_next(); + let node = harness.get_next_widget(); test(&node); }); } @@ -375,11 +376,13 @@ impl<'a> VisualTests<'a> { } trait HarnessExt { - fn get_next(&self) -> Node<'_>; + fn get_next_widget(&self) -> Node<'_>; } impl HarnessExt for Harness<'_> { - fn get_next(&self) -> Node<'_> { - self.get_all(by()).next().unwrap() + fn get_next_widget(&self) -> Node<'_> { + self.get_all(by().predicate(|node| node.role() != Role::GenericContainer)) + .next() + .unwrap() } } From 4d4f90eb316ab05794097d13c1bb658abeba1b4e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Oct 2025 11:47:34 +0200 Subject: [PATCH 269/388] kittest: No `debug_open_snapshot` on wasm (#7606) --- Cargo.toml | 2 +- crates/egui_kittest/Cargo.toml | 7 +++++-- crates/egui_kittest/src/snapshot.rs | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a87fb1c8..419258f8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ env_logger = { version = "0.11.8", default-features = false } glow = "0.16.0" glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } -home = "0.5.9" # need to puse to an old version because newer version are incompatible with wasm (at least when building Rerun) +home = "0.5.9" image = { version = "0.25.6", default-features = false } js-sys = "0.3.81" kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index adb955e9f..56ba13b94 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -61,12 +61,15 @@ wgpu = { workspace = true, features = [ # snapshot dependencies dify = { workspace = true, optional = true } -open = { workspace = true, optional = true } -tempfile = { workspace = true, optional = true } # Enable this when generating docs. document-features = { workspace = true, optional = true } +# Native dependencies: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +open = { workspace = true, optional = true } +tempfile = { workspace = true, optional = true } + [dev-dependencies] egui = { workspace = true, features = ["default_fonts"] } image = { workspace = true, features = ["png"] } diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 07bea5c9c..9282edb55 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -641,6 +641,7 @@ impl Harness<'_, State> { /// This method is marked as deprecated to trigger errors in CI (so that it's not accidentally /// committed). #[deprecated = "Only for debugging, don't commit this."] + #[cfg(not(target_arch = "wasm32"))] pub fn debug_open_snapshot(&mut self) { let image = self .render() From 718a82b013a97ef0064868c8edb0a1e52a88aa8d Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 8 Oct 2025 13:47:00 +0200 Subject: [PATCH 270/388] `Harness`: Add `remove_cursor`, `event` and `event_modifiers` (#7607) * Closes https://github.com/emilk/egui/issues/7591 --- crates/egui_kittest/src/lib.rs | 18 ++++++++++++++-- crates/egui_kittest/tests/tests.rs | 33 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 60d689c18..ebeba65ac 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -460,11 +460,15 @@ impl<'a, State> Harness<'a, State> { &mut self.state } - fn event(&self, event: egui::Event) { + /// Queue an event to be processed in the next frame. + pub fn event(&self, event: egui::Event) { self.queued_events.lock().push(EventType::Event(event)); } - fn event_modifiers(&self, event: egui::Event, modifiers: Modifiers) { + /// Queue an event with modifiers. + /// + /// Queues the modifiers to be pressed, then the event, then the modifiers to be released. + pub 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)); @@ -584,6 +588,16 @@ impl<'a, State> Harness<'a, State> { self.key_combination_modifiers(modifiers, &[key]); } + /// Remove the cursor from the screen. + /// + /// Will fire a [`egui::Event::PointerGone`] event. + /// + /// If you click a button and then take a snapshot, the button will be shown as hovered. + /// If you don't want that, you can call this method after clicking. + pub fn remove_cursor(&self) { + self.event(egui::Event::PointerGone); + } + /// Mask something. Useful for snapshot tests. /// /// Call this _after_ [`Self::run`] and before [`Self::snapshot`]. diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index 04f02ba87..81f861451 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -181,3 +181,36 @@ fn test_masking() { harness.snapshot("test_masking"); } + +#[test] +fn test_remove_cursor() { + let hovered = false; + let mut harness = Harness::new_ui_state( + |ui, state| { + let response = ui.button("Click me"); + *state = response.hovered(); + }, + hovered, + ); + + harness.fit_contents(); + + harness.get_by_label("Click me").click(); + harness.run(); + + assert!(harness.state(), "The button should be hovered"); + let hovered_button_snapshot = harness.render().expect("Failed to render"); + + harness.remove_cursor(); + harness.run(); + assert!( + !harness.state(), + "The button should not be hovered after removing cursor" + ); + + let non_hovered_button_snapshot = harness.render().expect("Failed to render"); + assert_ne!( + hovered_button_snapshot, non_hovered_button_snapshot, + "The button appearance should change" + ); +} From 47a437403f573dbdb4aa18dcc89a545b1a5cf4a9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Oct 2025 16:24:02 +0200 Subject: [PATCH 271/388] Use software texture filtering in kittest (#7602) --- crates/eframe/src/native/wgpu_integration.rs | 1 + crates/egui-wgpu/src/egui.wgsl | 47 ++++++++++++++++--- crates/egui-wgpu/src/renderer.rs | 31 +++++++----- .../tests/snapshots/imageviewer.png | 4 +- .../tests/snapshots/demos/Clipboard Test.png | 2 +- .../tests/snapshots/demos/Scene.png | 4 +- .../snapshots/image_blending/image_x1.png | 2 +- .../snapshots/image_blending/image_x2.png | 2 +- .../snapshots/rendering_test/dpi_1.00.png | 4 +- .../snapshots/rendering_test/dpi_1.25.png | 4 +- .../snapshots/rendering_test/dpi_1.50.png | 4 +- .../snapshots/rendering_test/dpi_1.67.png | 4 +- .../snapshots/rendering_test/dpi_1.75.png | 4 +- .../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 +- .../tessellation_test/Thin filled.png | 4 +- .../tessellation_test/Thin stroked.png | 4 +- .../snapshots/widget_gallery_dark_x1.png | 4 +- .../snapshots/widget_gallery_dark_x2.png | 4 +- .../snapshots/widget_gallery_light_x1.png | 2 +- .../snapshots/widget_gallery_light_x2.png | 4 +- 26 files changed, 103 insertions(+), 60 deletions(-) diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 0fcfb3ea3..046340bdb 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -194,6 +194,7 @@ impl<'app> WgpuWinitApp<'app> { self.native_options.stencil_buffer, ), dithering: self.native_options.dithering, + ..Default::default() }, )); diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index 2921be74f..39210841b 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -8,10 +8,13 @@ struct VertexOutput { struct Locals { screen_size: vec2, - dithering: u32, // 1 if dithering is enabled, 0 otherwise - // Uniform buffers need to be at least 16 bytes in WebGL. - // See https://github.com/gfx-rs/wgpu/issues/2072 - _padding: u32, + + /// 1 if dithering is enabled, 0 otherwise + dithering: u32, + + /// 1 to do manual filtering for more predictable kittest snapshot images. + /// See also https://github.com/emilk/egui/issues/5295 + predictable_texture_filtering: u32, }; @group(0) @binding(0) var r_locals: Locals; @@ -95,10 +98,42 @@ fn vs_main( @group(1) @binding(0) var r_tex_color: texture_2d; @group(1) @binding(1) var r_tex_sampler: sampler; +fn sample_texture(in: VertexOutput) -> vec4 { + if r_locals.predictable_texture_filtering == 0 { + // Hardware filtering: fast, but varies across GPUs and drivers. + return textureSample(r_tex_color, r_tex_sampler, in.tex_coord); + } else { + // Manual bilinear filtering with four taps at pixel centers using textureLoad + let texture_size = vec2(textureDimensions(r_tex_color, 0)); + let texture_size_f = vec2(texture_size); + let pixel_coord = in.tex_coord * texture_size_f - 0.5; + let pixel_fract = fract(pixel_coord); + let pixel_floor = vec2(floor(pixel_coord)); + + // Manual texture clamping + let max_coord = texture_size - vec2(1, 1); + let p00 = clamp(pixel_floor + vec2(0, 0), vec2(0, 0), max_coord); + let p10 = clamp(pixel_floor + vec2(1, 0), vec2(0, 0), max_coord); + let p01 = clamp(pixel_floor + vec2(0, 1), vec2(0, 0), max_coord); + let p11 = clamp(pixel_floor + vec2(1, 1), vec2(0, 0), max_coord); + + // Load at pixel centers + let tl = textureLoad(r_tex_color, p00, 0); + let tr = textureLoad(r_tex_color, p10, 0); + let bl = textureLoad(r_tex_color, p01, 0); + let br = textureLoad(r_tex_color, p11, 0); + + // Manual bilinear interpolation + let top = mix(tl, tr, pixel_fract.x); + let bottom = mix(bl, br, pixel_fract.x); + return mix(top, bottom, pixel_fract.y); + } +} + @fragment fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { // We expect "normal" textures that are NOT sRGB-aware. - let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); + let tex_gamma = sample_texture(in); 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,7 +150,7 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { @fragment fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { // We expect "normal" textures that are NOT sRGB-aware. - let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); + let tex_gamma = sample_texture(in); 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 815ee3e72..108aa31c3 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -141,21 +141,16 @@ impl ScreenDescriptor { } /// Uniform buffer used when rendering. -#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] struct UniformBuffer { screen_size_in_points: [f32; 2], dithering: u32, - // Uniform buffers need to be at least 16 bytes in WebGL. - // See https://github.com/gfx-rs/wgpu/issues/2072 - _padding: u32, -} -impl PartialEq for UniformBuffer { - fn eq(&self, other: &Self) -> bool { - self.screen_size_in_points == other.screen_size_in_points - && self.dithering == other.dithering - } + /// 1 to do manual filtering for more predictable kittest snapshot images. + /// + /// See also . + predictable_texture_filtering: u32, } struct SlicedBuffer { @@ -204,6 +199,16 @@ pub struct RendererOptions { /// /// Defaults to true. pub dithering: bool, + + /// Perform texture filtering in software? + /// + /// This is useful when you want predictable rendering across + /// different hardware, e.g. for kittest snapshots. + /// + /// Default is `false`. + /// + /// See also . + pub predictable_texture_filtering: bool, } impl RendererOptions { @@ -214,6 +219,7 @@ impl RendererOptions { msaa_samples: 1, depth_stencil_format: None, dithering: false, + predictable_texture_filtering: true, }; } @@ -223,6 +229,7 @@ impl Default for RendererOptions { msaa_samples: 0, depth_stencil_format: None, dithering: true, + predictable_texture_filtering: false, } } } @@ -280,7 +287,7 @@ impl Renderer { contents: bytemuck::cast_slice(&[UniformBuffer { screen_size_in_points: [0.0, 0.0], dithering: u32::from(options.dithering), - _padding: Default::default(), + predictable_texture_filtering: u32::from(options.predictable_texture_filtering), }]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); @@ -895,7 +902,7 @@ impl Renderer { let uniform_buffer_content = UniformBuffer { screen_size_in_points, dithering: u32::from(self.options.dithering), - _padding: Default::default(), + predictable_texture_filtering: u32::from(self.options.predictable_texture_filtering), }; if uniform_buffer_content != self.previous_uniform_buffer_content { profiling::scope!("update uniforms"); diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index d16d7a2e9..e1d518a96 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:6b09dbb2e4038c57e28e946604b56123a01f63aa8aa029245448ed77c83ee910 -size 100070 +oid sha256:dc9c22567b76193a7f6753c4217adb3c92afa921c488ba1cf2e14b403814e7ac +size 99841 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 86e0a3237..373adc234 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:b418b7f9d94244fe355fd81866ab9dc9750ff4e0cb7db39d902dd9892eae5246 +oid sha256:cb944eca56724f6a2106ea8db2043dc94c0ea40bdd4cdeb0e520790f97cc9598 size 27049 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index 9bf999143..2344a3868 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:17c8a6bc3ea09fe5940011e6d96ad26e47aee6ee672b92bd4fa23b40e4a0f790 -size 34493 +oid sha256:2855bd95ab33b5232edada1f65684bbba2748025b6b64eb9ac68a5f2d10ad4bd +size 34491 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 7ef5676bb..e06a95c92 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:e057c0bba4ec4c30e890c39153bd6dd17c511f410bfb894e66ef3ef9973d8fd4 +oid sha256:614046db82ef103f65bec3c09cc6afd9ee2b3835e9d32e2adff98f6d56714b22 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 89fad98f9..42ee2f1dd 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:c8b573f58a41efe26a0bf335e27cc123ffd4c13b24576e46d96ddedfed68b606 +oid sha256:a6298072b162623cec47d268fed5f8aa6189a2cf69074924a6eba26994fc6330 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 5824e7a8e..640d84c2b 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:69442b230ab0eb5291b47290c6ec08eff21bb2032f164592b1b0205065a8b035 -size 621404 +oid sha256:f9980486c36a0242f3b043a172c411d4fc9573543d3cd7c1c43bf020c18868a9 +size 619816 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 20777b41a..df05ada25 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:d38bfa32246e60a408495101004c7346220e43e430440aba82737131206e5053 -size 826006 +oid sha256:040e2e486ae4773a084da99513a53c620e8e2bba215183ec26c6e489381d6254 +size 823086 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 80435c851..c6aa2914f 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:2fc6621f8a548991b198db4e1510f2aab04716c814fd5d7c4642c354dfb060f3 -size 1039325 +oid sha256:439a5f942a5f05b9c09685ef90be94c150a21a68d1d235af22372b9b6a7b7389 +size 1035734 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 1866afd60..1344edcfb 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:0f541e682b229cefc00220c07bb8fd929de98041403e3cfdbc1f45000fa96e32 -size 1209860 +oid sha256:9b7d7e290b97a8042af3af3cd9ceb274950cf607dd7e9cd6c71d5a113d3b57a5 +size 1206155 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 a678c7233..9804e2942 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:f3c9ede7cf36cb5704f80ac4544953db22c84c6b69b24d94c072f7d35ee79698 -size 1236290 +oid sha256:fedd5546e36a89121c0bb0a780b0bbe081611c2c04013064872801181503fb83 +size 1231599 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 e6a694435..04d4bb4ea 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:c36f92a5ced3be65e7b9b6364670f1f50702fcc330a0fab794d20efbc6367148 -size 1466940 +oid sha256:69a7040336fc92c6d7b158283aabbc5817980c2fa84a1120b788516cf437b985 +size 1461979 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 6a9f5b77e..50e84c900 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:07443de8d14d8179e10b82b82be76531e68b1367fdbfe06993bb2e068ca92b0d -size 46794 +oid sha256:1a32d361afa20fc8c20122a89b01fe14b45849da42663991e589579f70fb8577 +size 46790 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 e98af25c9..ca5a23f97 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:096baabe30a44b013a893eb6e529bc24cb7b6a205edd22c1319cf1f137ac83da -size 88561 +oid sha256:c8d55205cf4225123da33895ed45eb186e5e57184ef5928400a4bae3ab6092be +size 88548 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 19320ca47..41f9ef2f5 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:0d7fa14c81618b24b316df464d3a95954f94149e21cf2086acbaef7912ea2920 -size 120651 +oid sha256:77ab9e2e18c788f8cdbec171269afe4d0a90c52abeda7063cae3766dcaa5e93b +size 120612 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 e21e75fdf..85d844c79 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:3dfced2f1f04816366b7688614339d2e5aee4b655d399f02692125ecfb1b17df -size 53066 +oid sha256:36622a2f934503a7b60ded2f44b002e37eedde22d548dcf5a209f54c19548665 +size 53064 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 95d683c9e..6e6d9f0f2 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:2314aefa3f38b20600c918413a146c9d31e90b379b44cf53b378da845b8a1199 -size 56280 +oid sha256:4adeb7a77a0d0fe85097fcd190a99b49858dce11bde601d30335afcb6cc3d5f6 +size 56276 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 6de0d75d4..eefbac2bb 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:b258e241726f19d9b0842fbd0fe5c7b39ea61ecae9ee925c1c1f7b18be0bdea1 -size 56737 +oid sha256:f45249f7cc90433a64856905c727571c4ef20468e2c7d3ac2029e18a0477932d +size 56743 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 d4a3c94b0..08178eaa8 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:a8c7a56c6f29756cf49edbefe38ec2a6bd164b400dd458686405655e5e7f1c77 -size 37596 +oid sha256:d437f68c521f3e627a1b50d46605e8b0b343c5fc3716d9669e8c4436c8992b6f +size 37602 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 c329ebf32..13f057df9 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:c0c4a3625ba10777e0878bea1b26c8ac06d0558f777f9b82c9bbe987195c6d60 -size 37623 +oid sha256:08ba98437403a08cca825ed8e288c9f088a46d9a1081f0b0a4ed7fbb9b49bb85 +size 37640 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 c672710c6..ab2db5eb5 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:dae36303c3b75ae100a43511436a9598190f556af2ce7f48b97f02ec09a7d81c -size 66858 +oid sha256:e1ed0e40d08b2b9ea978a07a4b7bf282c9e2cba8c52896f12210f396241e1b78 +size 66859 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 b0cf0e18e..e84863bf4 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:82a2c649f039a7b6549391253bb20fbbe4dcb404fec98850488155a7297f4353 -size 158896 +oid sha256:28f9862dd6f16b99523f5880bf90346fd9455d0d44e7bdd530d523e0b2ef2d2c +size 158892 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 ac962e05e..87eb5ce70 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:c1fc06217640ee574a1cebd6c2351d7b8fb52ad263144175d0eed64ea46109d2 +oid sha256:98c40c99a237f8388d82259fba4388d7b1759fe7b4bf657d326532934135c934 size 61119 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 63c5e8b74..c2c4705e1 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:cb4408cd1654af081e9ffff1af2d6132dce9b395af3657cd2dc97cbcd3919310 -size 152219 +oid sha256:90d36311ce5b1dcf81cab22113620a3362f255059d1f52c6794c8249f960549e +size 152215 From 94f2ed6334bdcd080891f22fb4065a21849a41d7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Oct 2025 17:09:04 +0200 Subject: [PATCH 272/388] Do not lock wasm-bindgen version (#7609) --- .github/workflows/rust.yml | 1 + Cargo.toml | 2 +- scripts/setup_web.sh | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ac575e8db..37dab39a9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -92,6 +92,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: + # Keep wasm-bindgen in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml version: "0.2.104" - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.toml b/Cargo.toml index 419258f8b..f61bfc7a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,7 +134,7 @@ tokio = "1.47.1" type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } unicode-segmentation = "1.12.0" -wasm-bindgen = "=0.2.104" +wasm-bindgen = "0.2.104" # Keep wasm-bindgen in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml wasm-bindgen-futures = "0.4.54" wayland-cursor = { version = "0.31.11", default-features = false } web-sys = "0.3.81" diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index 34016992e..2c5f87c0b 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -9,6 +9,7 @@ set -x rustup target add wasm32-unknown-unknown # For generating JS bindings: +# Keep wasm-bindgen in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.104'; then cargo install --force --quiet wasm-bindgen-cli --version 0.2.104 fi From 917aaca99125c0c05e002a6f85c8754517506f6e Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 8 Oct 2025 17:49:29 +0200 Subject: [PATCH 273/388] Use custom github token for commiting snapshots (#7611) ... to ensure that workflows are run --- .github/workflows/update_kittest_snapshots.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update_kittest_snapshots.yml b/.github/workflows/update_kittest_snapshots.yml index e06382386..9c67c52a9 100644 --- a/.github/workflows/update_kittest_snapshots.yml +++ b/.github/workflows/update_kittest_snapshots.yml @@ -23,7 +23,9 @@ jobs: lfs: true # We can't use the workflow token since that would prevent our commit to cause further workflows. # See https://github.com/stefanzweifel/git-auto-commit-action#commits-made-by-this-action-do-not-trigger-new-workflow-runs - token: '${{ secrets.GITHUB_TOKEN }}' + # This token should be a personal access token with at least Read and write permission to `Contents`. + # The commit action below will use the token this the code was checked out with. + token: '${{ secrets.SNAPSHOT_COMMIT_GITHUB_TOKEN }}' - name: Accept snapshots env: From 9e138895894e78505a3d1517bed1fbb29004a460 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Oct 2025 08:56:33 +0200 Subject: [PATCH 274/388] Revert wasm-bindgen to 0.2.100 (#7612) --- .github/workflows/rust.yml | 3 +-- Cargo.lock | 41 +++++++++++++++++++------------------- Cargo.toml | 8 ++++---- scripts/setup_web.sh | 6 +++--- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 37dab39a9..85a059160 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -92,8 +92,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - # Keep wasm-bindgen in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml - version: "0.2.104" + version: "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.lock b/Cargo.lock index aba222cc7..c04f66c4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -888,7 +888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1184,7 +1184,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2472,9 +2472,9 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -4278,7 +4278,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.8", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4767,22 +4767,21 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", - "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -4794,9 +4793,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -4807,9 +4806,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4817,9 +4816,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -4830,9 +4829,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] @@ -4948,9 +4947,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -5180,7 +5179,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f61bfc7a6..35ca02fa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } home = "0.5.9" image = { version = "0.25.6", default-features = false } -js-sys = "0.3.81" +js-sys = "0.3.77" kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } log = { version = "0.4.28", features = ["std"] } memoffset = "0.9.1" @@ -134,10 +134,10 @@ tokio = "1.47.1" type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } unicode-segmentation = "1.12.0" -wasm-bindgen = "0.2.104" # Keep wasm-bindgen in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml -wasm-bindgen-futures = "0.4.54" +wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml +wasm-bindgen-futures = "0.4.0" wayland-cursor = { version = "0.31.11", default-features = false } -web-sys = "0.3.81" +web-sys = "0.3.77" web-time = "1.1.0" # Timekeeping for native and web webbrowser = "1.0.5" wgpu = { version = "27.0.1", default-features = false, features = ["std"] } diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index 2c5f87c0b..f51258855 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -9,7 +9,7 @@ set -x rustup target add wasm32-unknown-unknown # For generating JS bindings: -# Keep wasm-bindgen in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml -if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.104'; then - cargo install --force --quiet wasm-bindgen-cli --version 0.2.104 +# Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml +if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.100'; then + cargo install --force --quiet wasm-bindgen-cli --version 0.2.100 fi From 32336e260bb9199adc1d89e455474bfb97a81b9d Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 9 Oct 2025 09:15:18 +0200 Subject: [PATCH 275/388] Write .new.png file if snapshot is missing (#7610) We should write the .new.png whenever the test fails and snapshots aren't updated. Necessary for the kitdiff-snapshot-update-workflow to work --- crates/egui_kittest/src/snapshot.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 9282edb55..90a59857e 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -397,13 +397,24 @@ fn try_image_snapshot_options_impl( Ok(()) }; + let write_new_png = || { + new.save(&new_path) + .map_err(|err| SnapshotError::WriteSnapshot { + err, + path: new_path.clone(), + })?; + Ok(()) + }; + let previous = match image::open(&snapshot_path) { Ok(image) => image.to_rgba8(), Err(err) => { - // No previous snapshot - probablye a new test. + // No previous snapshot - probably a new test. if mode.is_update() { return update_snapshot(); } else { + write_new_png()?; + return Err(SnapshotError::OpenSnapshot { path: snapshot_path.clone(), err, @@ -416,6 +427,8 @@ fn try_image_snapshot_options_impl( if mode.is_update() { return update_snapshot(); } else { + write_new_png()?; + return Err(SnapshotError::SizeMismatch { name, expected: previous.dimensions(), @@ -454,11 +467,7 @@ fn try_image_snapshot_options_impl( if below_threshold { Ok(()) } else { - new.save(&new_path) - .map_err(|err| SnapshotError::WriteSnapshot { - err, - path: new_path.clone(), - })?; + write_new_png()?; Err(SnapshotError::Diff { name, From d1fcd740ded5d69016c993a502b52e67f5d492d7 Mon Sep 17 00:00:00 2001 From: "Anthony S." <29609366+anthony-hyo@users.noreply.github.com> Date: Thu, 9 Oct 2025 04:41:20 -0300 Subject: [PATCH 276/388] Include screenshot in popups README (#7562) Add missing screenshot to README for popups example Co-authored-by: Lucas Meurer --- examples/popups/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/popups/README.md b/examples/popups/README.md index ce32e895a..ba2faa2e2 100644 --- a/examples/popups/README.md +++ b/examples/popups/README.md @@ -3,3 +3,5 @@ Example of how to use menus, popups, context menus and tooltips. ```sh cargo run -p popups ``` + +![](screenshot.png) From cfca7ebd6d952545771b5985de867a6a20772ed8 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 9 Oct 2025 11:44:10 +0200 Subject: [PATCH 277/388] Fix black flash on start in glow eframe backend (#7616) Before: https://github.com/user-attachments/assets/b31b7a4f-a5f7-45af-bfe0-4c8e174e209f After: https://github.com/user-attachments/assets/47544987-ca81-4efb-b778-5ca59b7fc0ac I think to make things even nicer we could also try calling request_discard when a window is shown --- crates/eframe/src/native/glow_integration.rs | 6 ++++++ crates/egui/src/memory/mod.rs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index a087c9fab..4a3bee46a 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -563,6 +563,12 @@ impl GlowWinitRunning<'_> { (raw_input, viewport_ui_cb) }; + // HACK: In order to get the right clear_color, the system theme needs to be set, which + // usually only happens in the `update` call. So we call Options::begin_pass early + // to set the right theme. Without this there would be a black flash on the first frame. + self.integration + .egui_ctx + .options_mut(|opt| opt.begin_pass(&raw_input)); let clear_color = self .app .clear_color(&self.integration.egui_ctx.style().visuals); diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index cab041bff..ddc5a9ffe 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -318,7 +318,9 @@ impl Default for Options { } impl Options { - pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput) { + // Needs to be pub because we need to set the system_theme early in the eframe glow renderer. + #[doc(hidden)] + pub fn begin_pass(&mut self, new_raw_input: &RawInput) { self.system_theme = new_raw_input.system_theme; } From da39198142a363f79ff24b9373ad5fe910067436 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 9 Oct 2025 12:00:39 +0200 Subject: [PATCH 278/388] Some minor docs improvements (#7614) --- crates/egui/src/gui_zoom.rs | 2 +- crates/egui_kittest/README.md | 6 +++--- crates/egui_kittest/src/lib.rs | 1 + crates/egui_kittest/src/snapshot.rs | 1 + scripts/docs.sh | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index 5e24ad2d3..4242d927b 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -1,4 +1,4 @@ -//! Helpers for zooming the whole GUI of an app (changing [`Context::pixels_per_point`]. +//! Helpers for zooming the whole GUI of an app (changing [`Context::pixels_per_point`]). //! use crate::{Button, Context, Key, KeyboardShortcut, Modifiers, Ui}; diff --git a/crates/egui_kittest/README.md b/crates/egui_kittest/README.md index 64004a9ce..b774572f6 100644 --- a/crates/egui_kittest/README.md +++ b/crates/egui_kittest/README.md @@ -50,8 +50,8 @@ This is so that you can set `UPDATE_SNAPSHOTS=true` and update all tests, withou If you want to update all snapshot images, even those that are within error margins, run with `UPDATE_SNAPSHOTS=force`. -If you want to have multiple snapshots in the same test, it makes sense to collect the results in a `Vec` -([look here](https://github.com/emilk/egui/blob/70a01138b77f9c5724a35a6ef750b9ae1ab9f2dc/crates/egui_demo_lib/src/demo/demo_app_windows.rs#L388-L427) for an example). +If you want to have multiple snapshots in the same test, it makes sense to collect the results in a `SnapshotResults` +([look here](https://github.com/emilk/egui/blob/d1fcd740ded5d69016c993a502b52e67f5d492d7/crates/egui_demo_lib/src/demo/demo_app_windows.rs#L387-L420) for an example). This way they can all be updated at the same time. You should add the following to your `.gitignore`: @@ -67,7 +67,7 @@ You should add the following to your `.gitignore`: * …they are brittle since unrelated side effects (like a change in color) can cause the test to fail * …images take up repo space * images should… - * …be checked in or otherwise be available (egui use [git LFS](https://git-lfs.com/) files for this purpose) + * …be checked in or otherwise be available (egui uses [git LFS](https://git-lfs.com/) files for this purpose) * …depict exactly what's tested and nothing else * …have a low resolution to avoid growth in repo size * …have a low comparison threshold to avoid the test passing despite unwanted differences (the default threshold should be fine for most usecases!) diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index ebeba65ac..e331119e3 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -52,6 +52,7 @@ impl Display for ExceededMaxStepsError { } /// The test Harness. This contains everything needed to run the test. +/// /// Create a new Harness using [`Harness::new`] or [`Harness::builder`]. /// /// The [Harness] has a optional generic state that can be used to pass data to the app / ui closure. diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 90a59857e..c11533206 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -26,6 +26,7 @@ pub struct SnapshotOptions { } /// 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 diff --git a/scripts/docs.sh b/scripts/docs.sh index ee8f863fe..5d755ddc4 100755 --- a/scripts/docs.sh +++ b/scripts/docs.sh @@ -4,6 +4,6 @@ script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd "$script_path/.." cargo doc -p eframe --target wasm32-unknown-unknown --lib --no-deps -cargo doc -p emath -p epaint -p egui -p eframe -p egui-winit -p egui_extras -p egui_glow --lib --no-deps --all-features --open +cargo doc -p emath -p epaint -p egui -p eframe -p egui-winit -p egui_extras -p egui_glow -p egui_kittest --lib --no-deps --all-features --open # cargo watch -c -x 'doc -p emath -p epaint -p egui --lib --no-deps --all-features' From 0abeccebc9b3284ac2b0a4a6bd818f92da62aea4 Mon Sep 17 00:00:00 2001 From: Bryce Berger Date: Thu, 9 Oct 2025 06:47:59 -0400 Subject: [PATCH 279/388] Change Spinner widget to account for width as well as height (#7560) Previously, when `rect` was taller than it was wide, the spinner would render far outside the given rectangle. Now, it always renders inside the smaller of the two dimensions. I noticed this when upgrading from 0.30 to 0.32. I have an image that's significantly taller than it is wide. In 0.32, when the image is loading, it shows the spinner. Since the spinner radius is determined solely based on rectangle height, the spinner ends up far too wide and covers other elements. * [x] I have followed the instructions in the PR template --- crates/egui/src/widgets/spinner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index 573517dcb..8b5ab5e28 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -42,7 +42,7 @@ impl Spinner { let color = self .color .unwrap_or_else(|| ui.visuals().strong_text_color()); - let radius = (rect.height() / 2.0) - 2.0; + let radius = (rect.height().min(rect.width()) / 2.0) - 2.0; let n_points = (radius.round() as u32).clamp(8, 128); let time = ui.input(|i| i.time); let start_angle = time * std::f64::consts::TAU; From 82b6b3c98d5d01159d7b4387445034c73a9ed47a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Oct 2025 12:56:57 +0200 Subject: [PATCH 280/388] Tweak Rust Analyzer settings (#7617) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .vscode/settings.json | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 677cb3c4e..21d4bb4b0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,32 +12,14 @@ "target_wasm/**": true, "target/**": true, }, - // Tell Rust Analyzer to use its own target directory, so we don't need to wait for it to finish wen we want to `cargo run` - "rust-analyzer.check.overrideCommand": [ - "cargo", - "clippy", - "--target-dir=target_ra", - "--workspace", - "--message-format=json", - "--all-targets", - "--all-features", - ], - "rust-analyzer.cargo.buildScripts.overrideCommand": [ - "cargo", - "clippy", - "--quiet", - "--target-dir=target_ra", - "--workspace", - "--message-format=json", - "--all-targets", - "--all-features", - ], + "rust-analyzer.check.command": "clippy", + // Whether `--workspace` should be passed to `cargo clippy`. If false, `-p ` will be passed instead. + "rust-analyzer.check.workspace": false, + "rust-analyzer.cargo.allTargets": true, + "rust-analyzer.cargo.features": "all", + // Use a separate target directory for Rust Analyzer so it doesn't prevent cargo/clippy from doing things. + "rust-analyzer.cargo.targetDir": "target_ra", "rust-analyzer.showUnlinkedFileNotification": false, - "rust-analyzer.cargo.extraEnv": { - // rust-analyzer is only guaranteed to support the latest stable version of Rust. Use it instead of whatever is - // specified in rust-toolchain. - "RUSTUP_TOOLCHAIN": "stable" - }, // Uncomment the following options and restart rust-analyzer to get it to check code behind `cfg(target_arch=wasm32)`. // Don't forget to put it in a comment again before committing. // "rust-analyzer.cargo.target": "wasm32-unknown-unknown", From d50287b83c461f7ac3bc809558ed4b2b806e87b3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Oct 2025 15:38:00 +0200 Subject: [PATCH 281/388] Add `taplo.toml` for toml formatting (#7618) --- .typos.toml | 8 +-- Cargo.toml | 64 +++++++++---------- clippy.toml | 54 ++++++++-------- crates/ecolor/Cargo.toml | 5 +- crates/eframe/Cargo.toml | 33 ++-------- crates/egui-wgpu/Cargo.toml | 8 +-- crates/egui-winit/Cargo.toml | 4 +- crates/egui_demo_app/Cargo.toml | 17 +---- crates/egui_demo_lib/Cargo.toml | 10 +-- crates/egui_extras/Cargo.toml | 7 +- crates/egui_glow/Cargo.toml | 10 +-- crates/egui_kittest/Cargo.toml | 20 +----- crates/epaint/Cargo.toml | 11 +--- deny.toml | 58 ++++++++--------- examples/confirm_exit/Cargo.toml | 4 +- examples/custom_3d_glow/Cargo.toml | 4 +- examples/custom_font/Cargo.toml | 4 +- examples/custom_font_style/Cargo.toml | 4 +- examples/custom_keypad/Cargo.toml | 4 +- examples/custom_style/Cargo.toml | 4 +- examples/custom_window_frame/Cargo.toml | 4 +- examples/external_eventloop/Cargo.toml | 4 +- examples/external_eventloop_async/Cargo.toml | 4 +- examples/file_dialog/Cargo.toml | 4 +- examples/hello_android/Cargo.toml | 6 +- examples/hello_world/Cargo.toml | 4 +- examples/hello_world_par/Cargo.toml | 10 +-- examples/hello_world_simple/Cargo.toml | 4 +- examples/keyboard_events/Cargo.toml | 4 +- examples/multiple_viewports/Cargo.toml | 4 +- examples/popups/Cargo.toml | 4 +- examples/puffin_profiler/Cargo.toml | 4 +- examples/screenshot/Cargo.toml | 11 ++-- examples/serial_windows/Cargo.toml | 4 +- examples/user_attention/Cargo.toml | 4 +- lychee.toml | 2 +- taplo.toml | 13 ++++ tests/egui_tests/Cargo.toml | 2 +- tests/test_egui_extras_compilation/Cargo.toml | 5 +- tests/test_inline_glow_paint/Cargo.toml | 4 +- tests/test_size_pass/Cargo.toml | 4 +- tests/test_viewports/Cargo.toml | 4 +- 42 files changed, 185 insertions(+), 257 deletions(-) create mode 100644 taplo.toml diff --git a/.typos.toml b/.typos.toml index d4c716172..7a243a3fd 100644 --- a/.typos.toml +++ b/.typos.toml @@ -3,10 +3,10 @@ # run: typos [default.extend-words] -ime = "ime" # Input Method Editor +ime = "ime" # Input Method Editor nknown = "nknown" # part of @55nknown username -ro = "ro" # read-only, also part of the username @Phen-Ro -typ = "typ" # Often used because `type` is a keyword in Rust +ro = "ro" # read-only, also part of the username @Phen-Ro +typ = "typ" # Often used because `type` is a keyword in Rust # I mistype these so often tesalator = "tessellator" @@ -146,5 +146,5 @@ extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated [default] extend-ignore-re = [ - "#\\[doc\\(alias = .*", # We suggest "grey" in some doc + "#\\[doc\\(alias = .*", # We suggest "grey" in some doc ] diff --git a/Cargo.toml b/Cargo.toml index 35ca02fa3..46ae967a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,23 @@ [workspace] resolver = "2" members = [ - "crates/ecolor", - "crates/egui_demo_app", - "crates/egui_demo_lib", - "crates/egui_extras", - "crates/egui_glow", - "crates/egui_kittest", - "crates/egui-wgpu", - "crates/egui-winit", - "crates/egui", - "crates/emath", - "crates/epaint", - "crates/epaint_default_fonts", + "crates/ecolor", + "crates/egui_demo_app", + "crates/egui_demo_lib", + "crates/egui_extras", + "crates/egui_glow", + "crates/egui_kittest", + "crates/egui-wgpu", + "crates/egui-winit", + "crates/egui", + "crates/emath", + "crates/epaint", + "crates/epaint_default_fonts", - "examples/*", - "tests/*", + "examples/*", + "tests/*", - "xtask", + "xtask", ] [workspace.package] @@ -73,11 +73,11 @@ accesskit_consumer = "0.30.1" accesskit_winit = "0.29.1" ab_glyph = "0.2.32" ahash = { version = "0.8.12", default-features = false, features = [ - "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead - "std", + "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead + "std", ] } android_logger = "0.15.1" -arboard = { version = "3.6.1", default-features = false} +arboard = { version = "3.6.1", default-features = false } backtrace = "0.3.76" bitflags = "2.9.4" bytemuck = "1.24.0" @@ -125,7 +125,7 @@ ron = "0.11.0" serde = { version = "1.0.228", features = ["derive"] } similar-asserts = "1.7.0" smallvec = "1.15.1" -smithay-clipboard = "0.7.2" +smithay-clipboard = "0.7.2" static_assertions = "1.1.0" syntect = { version = "5.3.0", default-features = false } tempfile = "3.23.0" @@ -134,7 +134,7 @@ tokio = "1.47.1" type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } unicode-segmentation = "1.12.0" -wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml +wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml wasm-bindgen-futures = "0.4.0" wayland-cursor = { version = "0.31.11", default-features = false } web-sys = "0.3.77" @@ -156,7 +156,7 @@ rust_2021_prelude_collisions = "warn" semicolon_in_expressions_from_macros = "warn" trivial_numeric_casts = "warn" unexpected_cfgs = "warn" -unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 +unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 unused_extern_crates = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" @@ -186,11 +186,11 @@ dbg_macro = "warn" debug_assert_with_mut_call = "warn" default_union_representation = "warn" derive_partial_eq_without_eq = "warn" -disallowed_macros = "warn" # See clippy.toml -disallowed_methods = "warn" # See clippy.toml -disallowed_names = "warn" # See clippy.toml -disallowed_script_idents = "warn" # See clippy.toml -disallowed_types = "warn" # See clippy.toml +disallowed_macros = "warn" # See clippy.toml +disallowed_methods = "warn" # See clippy.toml +disallowed_names = "warn" # See clippy.toml +disallowed_script_idents = "warn" # See clippy.toml +disallowed_types = "warn" # See clippy.toml doc_comment_double_space_linebreaks = "warn" doc_link_with_quotes = "warn" doc_markdown = "warn" @@ -345,14 +345,14 @@ zero_sized_map_values = "warn" comparison_chain = "allow" should_panic_without_expect = "allow" too_many_lines = "allow" -unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one +unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one # These are meh: -assigning_clones = "allow" # No please +assigning_clones = "allow" # No please let_underscore_must_use = "allow" let_underscore_untyped = "allow" -manual_range_contains = "allow" # this one is just worse imho -map_unwrap_or = "allow" # so is this one -self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 +manual_range_contains = "allow" # this one is just worse imho +map_unwrap_or = "allow" # so is this one +self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 significant_drop_tightening = "allow" # Too many false positives -wildcard_imports = "allow" # `use crate::*` is useful to avoid merge conflicts when adding/removing imports +wildcard_imports = "allow" # `use crate::*` is useful to avoid merge conflicts when adding/removing imports diff --git a/clippy.toml b/clippy.toml index 9ce151034..6602fbc6c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -23,25 +23,25 @@ type-complexity-threshold = 350 # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros disallowed-macros = [ - 'std::dbg', - 'std::unimplemented', + 'std::dbg', + 'std::unimplemented', - # TODO(emilk): consider forbidding these to encourage the use of proper log stream, and then explicitly allow legitimate uses - # 'std::eprint', - # 'std::eprintln', - # 'std::print', - # 'std::println', + # TODO(emilk): consider forbidding these to encourage the use of proper log stream, and then explicitly allow legitimate uses + # 'std::eprint', + # 'std::eprintln', + # 'std::print', + # 'std::println', ] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods disallowed-methods = [ - # NOTE: There are many things that aren't allowed on wasm, - # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) - # so we do that in `clipppy_wasm.toml` instead. + # NOTE: There are many things that aren't allowed on wasm, + # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) + # so we do that in `clipppy_wasm.toml` instead. - { path = "std::env::temp_dir", readon = "Use the tempfile crate instead" }, - { path = "std::panic::catch_unwind", reason = "We compile with `panic = abort" }, - { path = "std::thread::spawn", readon = "Use `std::thread::Builder` and name the thread" }, + { path = "std::env::temp_dir", readon = "Use the tempfile crate instead" }, + { path = "std::panic::catch_unwind", reason = "We compile with `panic = abort" }, + { path = "std::thread::spawn", readon = "Use `std::thread::Builder` and name the thread" }, ] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names @@ -49,24 +49,24 @@ disallowed-names = [] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types disallowed-types = [ - { path = "std::sync::Condvar", reason = "Use parking_lot instead" }, - { path = "std::sync::Mutex", reason = "Use epaint::mutex instead" }, - { path = "std::sync::RwLock", reason = "Use epaint::mutex instead" }, - { path = "winit::dpi::LogicalPosition", reason = "We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account" }, - { path = "winit::dpi::LogicalSize", reason = "We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account" }, - # "std::sync::Once", # enabled for now as the `log_once` macro uses it internally + { path = "std::sync::Condvar", reason = "Use parking_lot instead" }, + { path = "std::sync::Mutex", reason = "Use epaint::mutex instead" }, + { path = "std::sync::RwLock", reason = "Use epaint::mutex instead" }, + { path = "winit::dpi::LogicalPosition", reason = "We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account" }, + { path = "winit::dpi::LogicalSize", reason = "We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account" }, + # "std::sync::Once", # enabled for now as the `log_once` macro uses it internally ] # ----------------------------------------------------------------------------- # Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ - # You must also update the same list in `scripts/clippy_wasm/clippy.toml`! - "AccessKit", - "WebGL", - "WebGL1", - "WebGL2", - "WebGPU", - "VirtualBox", - "..", + # You must also update the same list in `scripts/clippy_wasm/clippy.toml`! + "AccessKit", + "WebGL", + "WebGL1", + "WebGL2", + "WebGPU", + "VirtualBox", + "..", ] diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index e8db84b3e..f37aff109 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "ecolor" version.workspace = true -authors = [ - "Emil Ernerfeldt ", - "Andreas Reich ", -] +authors = ["Emil Ernerfeldt ", "Andreas Reich "] description = "Color structs and color conversion utilities" edition.workspace = true rust-version.workspace = true diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 7fd3b9e3a..ce2103cc8 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -11,13 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/eframe" categories = ["gui", "game-development"] keywords = ["egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", - "data/icon.png", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml", "data/icon.png"] [package.metadata.docs.rs] all-features = true @@ -35,7 +29,7 @@ default = [ "accesskit", "default_fonts", "glow", - "wayland", # Required for Linux support (including CI!) + "wayland", # Required for Linux support (including CI!) "web_screen_reader", "winit/default", "x11", @@ -66,13 +60,7 @@ default_fonts = ["egui/default_fonts"] glow = ["dep:egui_glow", "dep:glow", "dep:glutin-winit", "dep:glutin"] ## Enable saving app state to disk. -persistence = [ - "dep:home", - "egui-winit/serde", - "egui/persistence", - "ron", - "serde", -] +persistence = ["dep:home", "egui-winit/serde", "egui/persistence", "ron", "serde"] ## Enables wayland support and fixes clipboard issue. ## @@ -88,10 +76,7 @@ wayland = [ ## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web. ## ## For other platforms, use the `accesskit` feature instead. -web_screen_reader = [ - "web-sys/SpeechSynthesis", - "web-sys/SpeechSynthesisUtterance", -] +web_screen_reader = ["web-sys/SpeechSynthesis", "web-sys/SpeechSynthesisUtterance"] ## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu)). ## @@ -145,10 +130,7 @@ serde = { workspace = true, optional = true } # ------------------------------------------- # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -egui-winit = { workspace = true, default-features = false, features = [ - "clipboard", - "links", -] } +egui-winit = { workspace = true, default-features = false, features = ["clipboard", "links"] } image = { workspace = true, features = ["png"] } # Needed for app icon winit = { workspace = true, default-features = false, features = ["rwh_06"] } @@ -158,10 +140,7 @@ egui-wgpu = { workspace = true, optional = true, features = [ ] } # if wgpu is used, use it with winit pollster = { workspace = true, optional = true } # needed for wgpu -glutin = { workspace = true, optional = true, default-features = false, features = [ - "egl", - "wgl", -] } +glutin = { workspace = true, optional = true, default-features = false, features = ["egl", "wgl"] } glutin-winit = { workspace = true, optional = true, default-features = false, features = [ "egl", "wgl", diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 94189f904..88321e652 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -15,13 +15,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/egui-wgpu" categories = ["gui", "game-development"] keywords = ["wgpu", "egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "**/*.wgsl", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "**/*.wgsl", "Cargo.toml"] [lints] workspace = true diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 45e5fa515..a4c84b05f 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -100,6 +100,4 @@ smithay-clipboard = { workspace = true, optional = true } wayland-cursor = { workspace = true, optional = true } [target.'cfg(not(target_os = "android"))'.dependencies] -arboard = { workspace = true, optional = true, features = [ - "image-data", -] } +arboard = { workspace = true, optional = true, features = ["image-data"] } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index aac086e16..8eb441ac5 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -31,12 +31,7 @@ web_app = ["http", "persistence"] accessibility_inspector = ["dep:accesskit", "dep:accesskit_consumer", "eframe/accesskit"] http = ["ehttp", "image/jpeg", "poll-promise", "egui_extras/image"] image_viewer = ["image/jpeg", "egui_extras/all_loaders", "rfd"] -persistence = [ - "eframe/persistence", - "egui_extras/serde", - "egui/persistence", - "serde", -] +persistence = ["eframe/persistence", "egui_extras/serde", "egui/persistence", "serde"] puffin = ["dep:puffin", "dep:puffin_http", "profiling/profile-with-puffin"] serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"] syntect = ["egui_demo_lib/syntect"] @@ -48,9 +43,7 @@ x11 = ["eframe/x11"] [dependencies] chrono = { workspace = true, features = ["js-sys", "wasmbind"] } -eframe = { workspace = true, default-features = false, features = [ - "web_screen_reader", -] } +eframe = { workspace = true, default-features = false, features = ["web_screen_reader"] } egui = { workspace = true, features = ["callstack", "default"] } egui_demo_lib = { workspace = true, features = ["default", "chrono"] } egui_extras = { workspace = true, features = ["default", "image"] } @@ -69,11 +62,7 @@ puffin = { workspace = true, optional = true } puffin_http = { workspace = true, optional = true } # Enable both WebGL & WebGPU when targeting the web (these features have no effect when not targeting wasm32) # Also enable the default features so we have a supported backend for every platform. -wgpu = { workspace = true, features = [ - "default", - "webgpu", - "webgl", -], optional = true } +wgpu = { workspace = true, features = ["default", "webgpu", "webgl"], optional = true } # feature "http": diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index 6d1473ea6..3e61187d5 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -11,13 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/egui_demo_lib" categories = ["gui", "graphics"] keywords = ["glow", "egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", - "data/*", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml", "data/*"] [lints] workspace = true @@ -60,7 +54,7 @@ egui = { workspace = true, features = ["default_fonts"] } egui_extras = { workspace = true, features = ["image", "svg"] } egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } image = { workspace = true, features = ["png"] } -mimalloc.workspace = true # for benchmarks +mimalloc.workspace = true # for benchmarks rand.workspace = true [[bench]] diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index b53d3f078..6639126ef 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -83,12 +83,7 @@ profiling.workspace = true serde = { workspace = true, optional = true } # Date operations needed for datepicker widget -chrono = { workspace = true, optional = true, features = [ - "clock", - "js-sys", - "std", - "wasmbind", -] } +chrono = { workspace = true, optional = true, features = ["clock", "js-sys", "std", "wasmbind"] } ## Enable this when generating docs. document-features = { workspace = true, optional = true } diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 30e74d22c..4a083611b 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -11,13 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/egui_glow" categories = ["gui", "game-development"] keywords = ["glow", "egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", - "src/shader/*.glsl", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml", "src/shader/*.glsl"] [lints] workspace = true @@ -73,7 +67,7 @@ wasm-bindgen.workspace = true [dev-dependencies] -glutin = { workspace = true, default-features = true } # examples/pure_glow +glutin = { workspace = true, default-features = true } # examples/pure_glow glutin-winit = { workspace = true, default-features = true } [[example]] diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index 56ba13b94..c283d0734 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "egui_kittest" version.workspace = true -authors = [ - "Lucas Meurer ", - "Emil Ernerfeldt ", -] +authors = ["Lucas Meurer ", "Emil Ernerfeldt "] description = "Testing library for egui based on kittest and AccessKit" edition.workspace = true rust-version.workspace = true @@ -24,13 +21,7 @@ rustdoc-args = ["--generate-link-to-definition"] [features] ## Adds a wgpu-based test renderer. -wgpu = [ - "dep:egui-wgpu", - "dep:pollster", - "dep:image", - "dep:wgpu", - "eframe?/wgpu", -] +wgpu = ["dep:egui-wgpu", "dep:pollster", "dep:image", "dep:wgpu", "eframe?/wgpu"] ## Adds a dify-based image snapshot utility. snapshot = ["dep:dify", "dep:image", "dep:open", "dep:tempfile", "image/png"] @@ -52,12 +43,7 @@ egui-wgpu = { workspace = true, optional = true } pollster = { workspace = true, optional = true } image = { workspace = true, optional = true } # Enable DX12 because it always comes with a software rasterizer. -wgpu = { workspace = true, features = [ - "metal", - "dx12", - "vulkan", - "gles", -], optional = true } +wgpu = { workspace = true, features = ["metal", "dx12", "vulkan", "gles"], optional = true } # snapshot dependencies dify = { workspace = true, optional = true } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index d287ffb38..3ac99a97f 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -11,12 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/epaint" categories = ["graphics", "gui"] keywords = ["graphics", "gui", "egui"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml" -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [lints] workspace = true @@ -70,8 +65,8 @@ ab_glyph.workspace = true ahash.workspace = true log.workspace = true nohash-hasher.workspace = true -parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. -profiling = { workspace = true} +parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. +profiling = { workspace = true } #! ### Optional dependencies bytemuck = { workspace = true, optional = true, features = ["derive"] } diff --git a/deny.toml b/deny.toml index a2953d315..c7206ff26 100644 --- a/deny.toml +++ b/deny.toml @@ -45,24 +45,24 @@ deny = [ ] skip = [ - { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 - { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 - { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … - { name = "core-foundation" }, # version conflict between winit and wgpu ecosystems + { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 + { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 + { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "core-foundation" }, # version conflict between winit and wgpu ecosystems { name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems - { name = "getrandom" }, # ring / rustls (and thus ehttp) still depend on getrandom 0.2 - { name = "quick-xml" }, # old version via wayland-scanner - { name = "redox_syscall" }, # old version via winit - { name = "rustc-hash" }, # Small enough - { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x - { name = "thiserror-impl" }, # same as above - { name = "windows-sys" }, # mostly hopeless to avoid - { name = "zerocopy" }, # Small enough + { name = "getrandom" }, # ring / rustls (and thus ehttp) still depend on getrandom 0.2 + { name = "quick-xml" }, # old version via wayland-scanner + { name = "redox_syscall" }, # old version via winit + { name = "rustc-hash" }, # Small enough + { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x + { name = "thiserror-impl" }, # same as above + { name = "windows-sys" }, # mostly hopeless to avoid + { name = "zerocopy" }, # Small enough ] skip-tree = [ { name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15 - { name = "rfd" }, # example dependency - { name = "windows" }, # the ecosystem is currently transitioning from 0.58 to 0.61 + { name = "rfd" }, # example dependency + { name = "windows" }, # the ecosystem is currently transitioning from 0.58 to 0.61 ] @@ -72,21 +72,21 @@ private = { ignore = true } confidence-threshold = 0.93 # We want really high confidence when inferring licenses from text allow = [ "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html - "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) - "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) - "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) - "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained - "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ - "ISC", # https://www.tldrlegal.com/license/isc-license - "MIT-0", # https://choosealicense.com/licenses/mit-0/ - "MIT", # https://tldrlegal.com/license/mit-license - "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. - "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html - "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux - "Ubuntu-font-1.0", # https://ubuntu.com/legal/font-licence - "Unicode-3.0", # https://www.unicode.org/license.txt - "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html - "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) + "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) + "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) + "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) + "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained + "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ + "ISC", # https://www.tldrlegal.com/license/isc-license + "MIT-0", # https://choosealicense.com/licenses/mit-0/ + "MIT", # https://tldrlegal.com/license/mit-license + "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. + "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html + "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux + "Ubuntu-font-1.0", # https://ubuntu.com/legal/font-licence + "Unicode-3.0", # https://www.unicode.org/license.txt + "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html + "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) ] exceptions = [] diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 087f801af..2724ae079 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 9a172825b..8636b0f43 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index 4de7d16ba..b10bd12cb 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 1f3343397..373225f60 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index e5a7f6965..6c3cd0f2f 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } # For image support: diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index 5bbd93c8e..d4f874e96 100644 --- a/examples/custom_style/Cargo.toml +++ b/examples/custom_style/Cargo.toml @@ -16,8 +16,8 @@ ignored = ["image"] # We need the .png feature [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } egui_demo_lib.workspace = true diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index 7dc1c7017..453893583 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/external_eventloop/Cargo.toml b/examples/external_eventloop/Cargo.toml index c26d5ea98..d54d1b1c5 100644 --- a/examples/external_eventloop/Cargo.toml +++ b/examples/external_eventloop/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/external_eventloop_async/Cargo.toml b/examples/external_eventloop_async/Cargo.toml index ad9d30990..c0537b107 100644 --- a/examples/external_eventloop_async/Cargo.toml +++ b/examples/external_eventloop_async/Cargo.toml @@ -19,8 +19,8 @@ required-features = ["linux-example"] [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 1fe1f64c4..a3b5318b0 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } rfd.workspace = true diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index 880a7d7cd..c4b5a93f3 100644 --- a/examples/hello_android/Cargo.toml +++ b/examples/hello_android/Cargo.toml @@ -18,9 +18,9 @@ crate-type = ["cdylib", "lib"] [dependencies] eframe = { workspace = true, default-features = false, features = [ - "default_fonts", - "glow", - "android-native-activity", + "default_fonts", + "glow", + "android-native-activity", ] } egui_demo_lib = { workspace = true, features = ["chrono"] } diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 924157ef5..00e279898 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } # For image support: diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 11693e985..84f1fe7e1 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -17,11 +17,11 @@ ignored = ["winit"] # Just enable some features of it; see below [dependencies] eframe = { workspace = true, default-features = false, features = [ - # accesskit struggles with threading - "default_fonts", - "wayland", - "x11", - "wgpu", + # accesskit struggles with threading + "default_fonts", + "wayland", + "x11", + "wgpu", ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } # This is normally enabled by eframe/default, which is not being used here diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 2c60803f5..954db50aa 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 325b7c28d..f3786152c 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 9810b0241..3187bf16d 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -15,7 +15,7 @@ wgpu = ["eframe/wgpu"] [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/popups/Cargo.toml b/examples/popups/Cargo.toml index 85038c00c..d1f199918 100644 --- a/examples/popups/Cargo.toml +++ b/examples/popups/Cargo.toml @@ -9,8 +9,8 @@ version.workspace = true [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 046e33a4c..2fd92d2ab 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -20,8 +20,8 @@ wgpu = ["eframe/wgpu"] [dependencies] eframe = { workspace = true, features = [ - "default", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } log.workspace = true diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index ca90a7c01..983963469 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "screenshot" version = "0.1.0" -authors = [ - "René Rössler ", - "Andreas Faber ", "Andreas Faber Date: Thu, 9 Oct 2025 15:50:36 +0200 Subject: [PATCH 282/388] Fix kerning snapshot test directory --- crates/egui_demo_lib/tests/misc.rs | 4 +++- .../tests/snapshots/image_blending/image_dark_x1.00.png | 2 +- .../tests/snapshots/image_blending/image_dark_x1.png | 3 --- .../tests/snapshots/image_blending/image_dark_x2.png | 3 --- .../tests/snapshots/image_blending/image_light_x1.png | 3 --- .../tests/snapshots/image_blending/image_light_x2.png | 3 --- .../tests/snapshots/image_kerning/image_dark_x1.png | 3 +++ .../tests/snapshots/image_kerning/image_dark_x2.png | 3 +++ .../tests/snapshots/image_kerning/image_light_x1.png | 3 +++ .../tests/snapshots/image_kerning/image_light_x2.png | 3 +++ 10 files changed, 16 insertions(+), 14 deletions(-) delete mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png delete mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png delete mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png delete mode 100644 crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png create mode 100644 crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png diff --git a/crates/egui_demo_lib/tests/misc.rs b/crates/egui_demo_lib/tests/misc.rs index ae16a8684..395baceb6 100644 --- a/crates/egui_demo_lib/tests/misc.rs +++ b/crates/egui_demo_lib/tests/misc.rs @@ -8,6 +8,8 @@ fn test_kerning() { .with_pixels_per_point(pixels_per_point) .with_theme(theme) .build_ui(|ui| { + ui.label("Hello world!"); + ui.label("Repeated characters: iiiiiiiiiiiii lllllllll mmmmmmmmmmmmmmmm"); ui.label("Thin spaces: −123 456 789"); ui.label("Ligature: fi :)"); ui.label("\ttabbed"); @@ -15,7 +17,7 @@ fn test_kerning() { harness.run(); harness.fit_contents(); harness.snapshot(format!( - "image_blending/image_{theme}_x{pixels_per_point}", + "image_kerning/image_{theme}_x{pixels_per_point}", theme = match theme { egui::Theme::Dark => "dark", egui::Theme::Light => "light", diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png index 8792b9588..0d70d6973 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png +++ b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23be15ddddc13f9628a4dc15cdc32132274a0db5e16c6b1df5cadae3d5ba55ce +oid sha256:44fc7d745b478fe937fa7c131871a00b26712a0317aaa027a088782533be6136 size 7125 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png deleted file mode 100644 index 069d48198..000000000 --- a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b619e8dbedbfc017513111dc26144d795ce97352631ae561c1c336c3e9e0fd4 -size 5557 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png deleted file mode 100644 index 020f9e370..000000000 --- a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3827cbd75a015ab9d03d9f47ba40fcadb71a2ba3a312d0892ae22a8e379103bc -size 12539 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png deleted file mode 100644 index f248a753a..000000000 --- a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:afd019fc23aa4b8a899e2df92138b2b3e69b7cb1d20e038a5e841c84e9095fe1 -size 5740 diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png b/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png deleted file mode 100644 index f5024f7a5..000000000 --- a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6129567eaf7d77c6656d9fe6984f1667b0817099492a24f6622da0f1d636e0e8 -size 13646 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png new file mode 100644 index 000000000..3d6c3f55c --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67b709c116f56fba7e4e9f182018e84f46f6c6dd33a51f9d0524125dc2056b8c +size 12950 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png new file mode 100644 index 000000000..e28eaeda9 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b0a70c0d66306edbbc6f77d03ea624aa68b846656811d4cc7d76d28572d177b +size 30723 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png new file mode 100644 index 000000000..391257018 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ba69ac73a5e9aacc6bf3bbd589e8640b41c50ccfb49edcda4e2d6efed6c09 +size 13384 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png new file mode 100644 index 000000000..f5e700ee3 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f798c666ad21d3f9bb57826e30f2f6ef044543bc05af8c185e0e63c8297e824 +size 33181 From 96470fabee60afaf3f89116e4bc9992d879e2b56 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 9 Oct 2025 19:14:14 +0200 Subject: [PATCH 283/388] Release 0.33.0 - `egui::Plugin`, better kerning, kitdiff viewer (#7622) ## Short bluesky announcement: We just released egui 0.33.0! Highlights: - `egui::Plugin` a improved way to create and access egui plugins - [kitdiff](https://github.com/rerun-io/kitdiff), a viewer for egui_kittest image snapshots (and a general image diff tool) - better kerning (check the diff on [kitdiff](https://rerun-io.github.io/kitdiff/?url=https://github.com/emilk/egui/pull/7431)) https://github.com/user-attachments/assets/971f0493-6dae-42e5-8019-58b74cf5d203 ## Relaese Changelog: egui is an easy-to-use immediate mode GUI for Rust that runs on both web and native. Try it now: egui development is sponsored by [Rerun](https://www.rerun.io/), a startup building an SDK for visualizing streams of multimodal data. # egui 0.33.0 changelog Highlights from this release: - `egui::Plugin` a improved way to create and access egui plugins - [kitdiff](https://github.com/rerun-io/kitdiff), a viewer for egui_kittest image snapshots (and a general image diff tool) - better kerning ### Improved kerning As a step towards using [parley](https://github.com/linebender/parley) for font rendering, @valadaptive has refactored the font loading and rendering code. A result of this (next to the font rendering code being much nicer now) is improved kerning. Notice how the c moved away from the k: ![Oct-09-2025 16-21-58](https://github.com/user-attachments/assets/d4a17e87-5e98-40db-a85a-fa77fa77aceb) ### `egui::Plugin` trait We've added a new trait-based plugin api, meant to replace `Context::on_begin_pass` and `Context::on_end_pass`. This makes it a lot easier to handle state in your plugins. Instead of having to write to egui memory it can live right on your plugin struct. The trait based api also makes easier to add new hooks that plugins can use. In addition to `on_begin_pass` and `on_end_pass`, the `Plugin` trait now has a `input_hook` and `output_hook` which you can use to inspect / modify the `RawInput` / `FullOutput`. ### kitdiff, a image diff viewer At rerun we have a ton of snapshots. Some PRs will change most of them (e.g. [the](https://github.com/rerun-io/rerun/pull/11253/files) [one](https://rerun-io.github.io/kitdiff/?url=https://github.com/rerun-io/rerun/pull/11253/files) that updated egui and introduced the kerning improvements, ~500 snapshots changed!). If you really want to look at every changed snapshot it better be as efficient as possible, and the experience on github, fiddeling with the sliders, is kind of frustrating. In order to fix this, we've made [kitdiff](https://rerun-io.github.io/kitdiff/). You can use it locally via - `kitdiff files .` will search for .new.png and .diff.png files - `kitdiff git` will compare the current files to the default branch (main/master) Or in the browser via - going to https://rerun-io.github.io/kitdiff/ and pasting a PR or github artifact url - linking to kitdiff via e.g. a github workflow `https://rerun-io.github.io/kitdiff/?url=` To install kitdiff run `cargo install --git https://github.com/rerun-io/kitdiff` Here is a video showing the kerning changes in kitdiff ([try it yourself](https://rerun-io.github.io/kitdiff/?url=https://github.com/rerun-io/rerun/pull/11253/files)): https://github.com/user-attachments/assets/74640af1-09ba-435a-9d0c-2cbeee140c8f ### Migration guide - `egui::Mutex` now has a timeout as a simple deadlock detection - If you use a `egui::Mutex` in some place where it's held for longer than a single frame, you should switch to the std mutex or parking_lot instead (egui mutexes are wrappers around parking lot) - `screen_rect` is deprecated - In order to support safe areas, egui now has `viewport_rect` and `content_rect`. - Update all usages of `screen_rect` to `content_rect`, unless you are sure that you want to draw outside the `safe area` (which would mean your Ui may be covered by notches, system ui, etc.) --- .typos.toml | 2 + CHANGELOG.md | 77 +++++++++++++++++++++ Cargo.lock | 37 +++++----- Cargo.toml | 28 ++++---- RELEASES.md | 1 + crates/ecolor/CHANGELOG.md | 6 ++ crates/eframe/CHANGELOG.md | 18 +++++ crates/egui-wgpu/CHANGELOG.md | 10 +++ crates/egui-winit/CHANGELOG.md | 15 ++++ crates/egui_extras/CHANGELOG.md | 7 ++ crates/egui_glow/CHANGELOG.md | 4 ++ crates/egui_kittest/CHANGELOG.md | 18 +++++ crates/emath/CHANGELOG.md | 13 ++++ crates/epaint/CHANGELOG.md | 8 +++ 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 | 4 +- examples/file_dialog/screenshot.png | 4 +- examples/hello_world_simple/screenshot.png | 4 +- examples/images/screenshot.png | 4 +- examples/keyboard_events/screenshot.png | 4 +- examples/popups/screenshot.png | 4 +- examples/puffin_profiler/screenshot.png | 4 +- examples/user_attention/screenshot.png | 4 +- scripts/publish_crates.sh | 2 +- 29 files changed, 243 insertions(+), 59 deletions(-) create mode 100644 crates/emath/CHANGELOG.md diff --git a/.typos.toml b/.typos.toml index 7a243a3fd..3ae860ad9 100644 --- a/.typos.toml +++ b/.typos.toml @@ -5,6 +5,8 @@ [default.extend-words] ime = "ime" # Input Method Editor nknown = "nknown" # part of @55nknown username +isse = "isse" # part of @IsseW username +tye = "tye" # part of @tye-exe username ro = "ro" # read-only, also part of the username @Phen-Ro typ = "typ" # Often used because `type` is a keyword in Rust diff --git a/CHANGELOG.md b/CHANGELOG.md index 579121cda..2fc31e57b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,83 @@ 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.33.0 - 2025-10-09 - `egui::Plugin`, better kerning, kitdiff viewer +Highlights from this release: +- `egui::Plugin` a improved way to create and access egui plugins +- [kitdiff](https://github.com/rerun-io/kitdiff), a viewer for egui_kittest image snapshots (and a general image diff tool) +- better kerning + + +### Improved kerning +As a step towards using [parley](https://github.com/linebender/parley) for font rendering, @valadaptive has refactored the font loading and rendering code. A result of this (next to the font rendering code being much nicer now) is improved kerning. +Notice how the c moved away from the k: + +![Oct-09-2025 16-21-58](https://github.com/user-attachments/assets/d4a17e87-5e98-40db-a85a-fa77fa77aceb) + + +### `egui::Plugin` trait +We've added a new trait-based plugin api, meant to replace `Context::on_begin_pass` and `Context::on_end_pass`. +This makes it a lot easier to handle state in your plugins. Instead of having to write to egui memory it can live right on your plugin struct. +The trait based api also makes easier to add new hooks that plugins can use. In addition to `on_begin_pass` and `on_end_pass`, the `Plugin` trait now has a `input_hook` and `output_hook` which you can use to inspect / modify the `RawInput` / `FullOutput`. + +### kitdiff, a image diff viewer +At rerun we have a ton of snapshots. Some PRs will change most of them (e.g. [the](https://github.com/rerun-io/rerun/pull/11253/files) [one](https://rerun-io.github.io/kitdiff/?url=https://github.com/rerun-io/rerun/pull/11253/files) that updated egui and introduced the kerning improvements, ~500 snapshots changed!). +If you really want to look at every changed snapshot it better be as efficient as possible, and the experience on github, fiddeling with the sliders, is kind of frustrating. +In order to fix this, we've made [kitdiff](https://rerun-io.github.io/kitdiff/). +You can use it locally via +- `kitdiff files .` will search for .new.png and .diff.png files +- `kitdiff git` will compare the current files to the default branch (main/master) + Or in the browser via +- going to https://rerun-io.github.io/kitdiff/ and pasting a PR or github artifact url +- linking to kitdiff via e.g. a github workflow `https://rerun-io.github.io/kitdiff/?url=` + +To install kitdiff run `cargo install --git https://github.com/rerun-io/kitdiff` + +Here is a video showing the kerning changes in kitdiff ([try it yourself](https://rerun-io.github.io/kitdiff/?url=https://github.com/rerun-io/rerun/pull/11253/files)): + +https://github.com/user-attachments/assets/74640af1-09ba-435a-9d0c-2cbeee140c8f + +### Migration guide +- `egui::Mutex` now has a timeout as a simple deadlock detection + - If you use a `egui::Mutex` in some place where it's held for longer than a single frame, you should switch to the std mutex or parking_lot instead (egui mutexes are wrappers around parking lot) +- `screen_rect` is deprecated + - In order to support safe areas, egui now has `viewport_rect` and `content_rect`. + - Update all usages of `screen_rect` to `content_rect`, unless you are sure that you want to draw outside the `safe area` (which would mean your Ui may be covered by notches, system ui, etc.) + + +### ⭐ Added +* New Plugin trait [#7385](https://github.com/emilk/egui/pull/7385) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `Ui::take_available_space()` helper function, which sets the Ui's minimum size to the available space [#7573](https://github.com/emilk/egui/pull/7573) by [@IsseW](https://github.com/IsseW) +* Add support for the safe area on iOS [#7578](https://github.com/emilk/egui/pull/7578) by [@irh](https://github.com/irh) +* Add `UiBuilder::global_scope` and `UiBuilder::id` [#7372](https://github.com/emilk/egui/pull/7372) by [@Icekey](https://github.com/Icekey) +* Add `emath::fast_midpoint` [#7435](https://github.com/emilk/egui/pull/7435) by [@emilk](https://github.com/emilk) +* Make the `hex_color` macro `const` [#7444](https://github.com/emilk/egui/pull/7444) by [@YgorSouza](https://github.com/YgorSouza) +* Add `SurrenderFocusOn` option [#7471](https://github.com/emilk/egui/pull/7471) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `Memory::move_focus` [#7476](https://github.com/emilk/egui/pull/7476) by [@darkwater](https://github.com/darkwater) +* Support on hover tooltip that is noninteractable even with interactable content [#5543](https://github.com/emilk/egui/pull/5543) by [@PPakalns](https://github.com/PPakalns) +* Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101) + +### 🔧 Changed +* Document platform compatibility on `viewport::WindowLevel` and dependents [#7432](https://github.com/emilk/egui/pull/7432) by [@lkdm](https://github.com/lkdm) +* Deprecated `ImageButton` and removed `WidgetType::ImageButton` [#7483](https://github.com/emilk/egui/pull/7483) by [@Stelios-Kourlis](https://github.com/Stelios-Kourlis) +* More even text kerning [#7431](https://github.com/emilk/egui/pull/7431) by [@valadaptive](https://github.com/valadaptive) +* Increase default text size from 12.5 to 13.0 [#7521](https://github.com/emilk/egui/pull/7521) by [@emilk](https://github.com/emilk) +* Update accesskit to 0.21.0 [#7550](https://github.com/emilk/egui/pull/7550) by [@fundon](https://github.com/fundon) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) +* Group AccessKit nodes by `Ui` [#7386](https://github.com/emilk/egui/pull/7386) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🔥 Removed +* Remove the `deadlock_detection` feature [#7497](https://github.com/emilk/egui/pull/7497) by [@lucasmerlin](https://github.com/lucasmerlin) +* Remove deprecated fields from `PlatformOutput` [#7523](https://github.com/emilk/egui/pull/7523) by [@emilk](https://github.com/emilk) +* Remove `log` feature [#7583](https://github.com/emilk/egui/pull/7583) by [@emilk](https://github.com/emilk) + +### 🐛 Fixed +* Enable `clippy::iter_over_hash_type` lint [#7421](https://github.com/emilk/egui/pull/7421) by [@emilk](https://github.com/emilk) +* Fixes sense issues in TextEdit when vertical alignment is used [#7436](https://github.com/emilk/egui/pull/7436) by [@RndUsr123](https://github.com/RndUsr123) +* Fix stuck menu when submenu vanishes [#7589](https://github.com/emilk/egui/pull/7589) by [@lucasmerlin](https://github.com/lucasmerlin) +* Change Spinner widget to account for width as well as height [#7560](https://github.com/emilk/egui/pull/7560) by [@bryceberger](https://github.com/bryceberger) + + ## 0.32.3 - 2025-09-12 * Preserve text format in truncated label tooltip [#7514](https://github.com/emilk/egui/pull/7514) [#7535](https://github.com/emilk/egui/pull/7535) by [@lucasmerlin](https://github.com/lucasmerlin) * Fix `TextEdit`'s in RTL layouts [#5547](https://github.com/emilk/egui/pull/5547) by [@zakarumych](https://github.com/zakarumych) diff --git a/Cargo.lock b/Cargo.lock index c04f66c4b..e6cd1533c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1248,7 +1248,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.32.3" +version = "0.33.0" dependencies = [ "bytemuck", "cint", @@ -1260,7 +1260,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.32.3" +version = "0.33.0" dependencies = [ "ahash", "bytemuck", @@ -1299,7 +1299,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.32.3" +version = "0.33.0" dependencies = [ "accesskit", "ahash", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.32.3" +version = "0.33.0" dependencies = [ "ahash", "bytemuck", @@ -1337,7 +1337,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.32.3" +version = "0.33.0" dependencies = [ "accesskit_winit", "arboard", @@ -1360,7 +1360,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.32.3" +version = "0.33.0" dependencies = [ "accesskit", "accesskit_consumer", @@ -1390,7 +1390,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.32.3" +version = "0.33.0" dependencies = [ "chrono", "criterion", @@ -1407,7 +1407,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.32.3" +version = "0.33.0" dependencies = [ "ahash", "chrono", @@ -1426,7 +1426,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.32.3" +version = "0.33.0" dependencies = [ "bytemuck", "document-features", @@ -1445,7 +1445,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.32.3" +version = "0.33.0" dependencies = [ "dify", "document-features", @@ -1463,7 +1463,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.32.3" +version = "0.33.0" dependencies = [ "egui", "egui_extras", @@ -1493,7 +1493,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.32.3" +version = "0.33.0" dependencies = [ "bytemuck", "document-features", @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.32.3" +version = "0.33.0" dependencies = [ "ab_glyph", "ahash", @@ -1613,7 +1613,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.32.3" +version = "0.33.0" [[package]] name = "equivalent" @@ -2507,8 +2507,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kittest" -version = "0.2.0" -source = "git+https://github.com/rerun-io/kittest.git#028d5311f475e64839bcbc04f259a0d20532d2c1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fd6dd2cce251a360101038acb9334e3a50cd38cd02fefddbf28aa975f043c8" dependencies = [ "accesskit", "accesskit_consumer", @@ -3424,7 +3425,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.32.3" +version = "0.33.0" dependencies = [ "eframe", "env_logger", @@ -5747,7 +5748,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.32.3" +version = "0.33.0" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index 46ae967a2..7aaa81139 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.88" -version = "0.32.3" +version = "0.33.0" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.32.3", path = "crates/emath", default-features = false } -ecolor = { version = "0.32.3", path = "crates/ecolor", default-features = false } -epaint = { version = "0.32.3", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.32.3", path = "crates/epaint_default_fonts" } -egui = { version = "0.32.3", path = "crates/egui", default-features = false } -egui-winit = { version = "0.32.3", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.32.3", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.32.3", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.32.3", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.32.3", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.32.3", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.32.3", path = "crates/eframe", default-features = false } +emath = { version = "0.33.0", path = "crates/emath", default-features = false } +ecolor = { version = "0.33.0", path = "crates/ecolor", default-features = false } +epaint = { version = "0.33.0", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.33.0", path = "crates/epaint_default_fonts" } +egui = { version = "0.33.0", path = "crates/egui", default-features = false } +egui-winit = { version = "0.33.0", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.33.0", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.33.0", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.33.0", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.33.0", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.33.0", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.33.0", path = "crates/eframe", default-features = false } accesskit = "0.21.1" accesskit_consumer = "0.30.1" @@ -97,7 +97,7 @@ glutin-winit = { version = "0.5.0", default-features = false } home = "0.5.9" image = { version = "0.25.6", default-features = false } js-sys = "0.3.77" -kittest = { version = "0.2.0", git = "https://github.com/rerun-io/kittest.git" } +kittest = { version = "0.3.0" } log = { version = "0.4.28", features = ["std"] } memoffset = "0.9.1" mimalloc = "0.1.48" diff --git a/RELEASES.md b/RELEASES.md index 1634ad13d..cb75c3da8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -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 +* [ ] Ensure we don't have any patch/git dependencies (e.g. kittest) * [ ] 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 diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 1c83db637..6996d838f 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.33.0 - 2025-10-09 +* Align `Color32` to 4 bytes [#7318](https://github.com/emilk/egui/pull/7318) by [@anti-social](https://github.com/anti-social) +* Make the `hex_color` macro `const` [#7444](https://github.com/emilk/egui/pull/7444) by [@YgorSouza](https://github.com/YgorSouza) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) + + ## 0.32.3 - 2025-09-12 Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index fec24aa79..142634d02 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,24 @@ 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.33.0 - 2025-10-09 +### ⭐ Added +* Add an option to limit the repaint rate in the web runner [#7482](https://github.com/emilk/egui/pull/7482) by [@s-nie](https://github.com/s-nie) +* Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101) +* Add support for the safe area on iOS [#7578](https://github.com/emilk/egui/pull/7578) by [@irh](https://github.com/irh) + +### 🔧 Changed +* Replace `winapi` with `windows-sys` crate [#7416](https://github.com/emilk/egui/pull/7416) by [@unlimitedsola](https://github.com/unlimitedsola) +* Prevent default action on command-comma in eframe web [#7547](https://github.com/emilk/egui/pull/7547) by [@emilk](https://github.com/emilk) +* Warn if `DYLD_LIBRARY_PATH` is set and we find no wgpu adapter [#7572](https://github.com/emilk/egui/pull/7572) by [@emilk](https://github.com/emilk) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) + +### 🐛 Fixed +* Properly end winit event loop [#7565](https://github.com/emilk/egui/pull/7565) by [@tye-exe](https://github.com/tye-exe) +* Fix eframe window not being focused on mac on startup [#7593](https://github.com/emilk/egui/pull/7593) by [@emilk](https://github.com/emilk) +* Fix black flash on start in glow eframe backend [#7616](https://github.com/emilk/egui/pull/7616) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 0.32.3 - 2025-09-12 Nothing new diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 2b50ce27d..5f4fd78c1 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,16 @@ 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.33.0 - 2025-10-09 +### 🔧 Changed +* Update wgpu to 26 and wasm-bindgen to 0.2.100 [#7540](https://github.com/emilk/egui/pull/7540) by [@Kumpelinus](https://github.com/Kumpelinus) +* Warn if `DYLD_LIBRARY_PATH` is set and we find no wgpu adapter [#7572](https://github.com/emilk/egui/pull/7572) by [@emilk](https://github.com/emilk) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) +* Update wgpu to 27.0.0 [#7580](https://github.com/emilk/egui/pull/7580) by [@Wumpf](https://github.com/Wumpf) +* Create `egui_wgpu::RendererOptions` [#7601](https://github.com/emilk/egui/pull/7601) by [@emilk](https://github.com/emilk) +* Use software texture filtering in kittest [#7602](https://github.com/emilk/egui/pull/7602) by [@emilk](https://github.com/emilk) + + ## 0.32.3 - 2025-09-12 Nothing new diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 5e06ddedf..e6b094502 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,21 @@ 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.33.0 - 2025-10-09 +### ⭐ Added +* Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101) +* Add support for the safe area on iOS [#7578](https://github.com/emilk/egui/pull/7578) by [@irh](https://github.com/irh) + +### 🔧 Changed +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) +* Create `egui_wgpu::RendererOptions` [#7601](https://github.com/emilk/egui/pull/7601) by [@emilk](https://github.com/emilk) + +### 🐛 Fixed +* Fix build error in egui-winit with profiling enabled [#7557](https://github.com/emilk/egui/pull/7557) by [@torokati44](https://github.com/torokati44) +* Properly end winit event loop [#7565](https://github.com/emilk/egui/pull/7565) by [@tye-exe](https://github.com/tye-exe) +* Fix eframe window not being focused on mac on startup [#7593](https://github.com/emilk/egui/pull/7593) by [@emilk](https://github.com/emilk) + + ## 0.32.3 - 2025-09-12 Nothing new diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index cdabaffd4..726a1759c 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,13 @@ 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.33.0 - 2025-10-09 +* Fix: use unique id for resize columns in `Table` [#7414](https://github.com/emilk/egui/pull/7414) by [@zezic](https://github.com/zezic) +* Feat: Add serde serialization to SyntectSettings [#7506](https://github.com/emilk/egui/pull/7506) by [@bircni](https://github.com/bircni) +* Make individual egui_extras image loaders public [#7551](https://github.com/emilk/egui/pull/7551) by [@lucasmerlin](https://github.com/lucasmerlin) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) + + ## 0.32.3 - 2025-09-12 * Fix deadlock in `FileLoader` and `EhttpLoader` [#7515](https://github.com/emilk/egui/pull/7515) by [@emilk](https://github.com/emilk) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 1cd8dee7d..34c2133e9 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.33.0 - 2025-10-09 +### ⭐ Added +* Kittest: Add `UPDATE_SNAPSHOTS=force` [#7508](https://github.com/emilk/egui/pull/7508) by [@emilk](https://github.com/emilk) +* Add `egui_kittest::HarnessBuilder::with_os` and set the default to `Nix` [#7493](https://github.com/emilk/egui/pull/7493) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `Harness::debug_open_snapshot` helper [#7590](https://github.com/emilk/egui/pull/7590) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🔧 Changed +* Include popups and tooltips in `Harness::fit_contents` [#7556](https://github.com/emilk/egui/pull/7556) by [@oxkitsune](https://github.com/oxkitsune) +* Adjust when we write .diff and .new snapshot images [#7571](https://github.com/emilk/egui/pull/7571) by [@emilk](https://github.com/emilk) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) +* `Harness`: Add `remove_cursor`, `event` and `event_modifiers` [#7607](https://github.com/emilk/egui/pull/7607) by [@lucasmerlin](https://github.com/lucasmerlin) +* Use software texture filtering in kittest [#7602](https://github.com/emilk/egui/pull/7602) by [@emilk](https://github.com/emilk) +* Write .new.png file if snapshot is missing [#7610](https://github.com/emilk/egui/pull/7610) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🔥 Removed +* Remove deprecated `Harness::wgpu_snapshot` and related fns [#7504](https://github.com/emilk/egui/pull/7504) by [@bircni](https://github.com/bircni) + + ## 0.32.3 - 2025-09-12 Nothing new diff --git a/crates/emath/CHANGELOG.md b/crates/emath/CHANGELOG.md new file mode 100644 index 000000000..b7e0fec1f --- /dev/null +++ b/crates/emath/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog for emath +All notable changes to the `emath` crate will be noted in this file. + + +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.33.0 - 2025-10-09 +* Add `emath::fast_midpoint` [#7435](https://github.com/emilk/egui/pull/7435) by [@emilk](https://github.com/emilk) +* Generate changelogs for emath [#7513](https://github.com/emilk/egui/pull/7513) by [@lucasmerlin](https://github.com/lucasmerlin) +* Improve `OrderedFloat` hash performance [#7512](https://github.com/emilk/egui/pull/7512) by [@valadaptive](https://github.com/valadaptive) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) + diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 3949eb59d..0524b87c0 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/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.33.0 - 2025-10-09 +* Remove the `deadlock_detection` feature [#7497](https://github.com/emilk/egui/pull/7497) by [@lucasmerlin](https://github.com/lucasmerlin) +* More even text kerning [#7431](https://github.com/emilk/egui/pull/7431) by [@valadaptive](https://github.com/valadaptive) +* Return `0.0` if font not found in `glyph_width` instead of panic [#7559](https://github.com/emilk/egui/pull/7559) by [@lucasmerlin](https://github.com/lucasmerlin) +* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf) +* Remove `log` feature [#7583](https://github.com/emilk/egui/pull/7583) by [@emilk](https://github.com/emilk) + + ## 0.32.3 - 2025-09-12 * Optimize `Mesh::add_rect_with_uv` [#7511](https://github.com/emilk/egui/pull/7511) by [@valadaptive](https://github.com/valadaptive) diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index 8fe164f66..bb39e4784 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.33.0 - 2025-10-09 +Nothing new + + ## 0.32.3 - 2025-09-12 Nothing new diff --git a/examples/confirm_exit/screenshot.png b/examples/confirm_exit/screenshot.png index 33debffe2..372b6abc1 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:0175461bbd86fffaad3538ea8dcec5001c7511aa201aa37b7736e7d2010b1522 -size 3483 +oid sha256:79ee820be9374c44e684373acb62fe7bcfaa2347346d1aed8083a706f6cae8c9 +size 3798 diff --git a/examples/custom_3d_glow/screenshot.png b/examples/custom_3d_glow/screenshot.png index 19bf33d64..1840bca3f 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:b5f36e1df27007b19cf56771145a667b4410dc9f4697afe629a2b42518640d62 -size 26531 +oid sha256:c02ea6a9b0f2d215255b5550237f3d9aa43b257849591cc8a1cd206f401c2747 +size 28455 diff --git a/examples/custom_font/screenshot.png b/examples/custom_font/screenshot.png index e7a20348d..9939d8756 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:a5bdb725a17bb6f8871ee95ae8cf46e55055083b0be14716cccd17615c863c81 -size 6179 +oid sha256:c13f5b5f70c194316bbb116700619fd1611f423ae86244d1668f21ba622aaa45 +size 7104 diff --git a/examples/custom_font_style/screenshot.png b/examples/custom_font_style/screenshot.png index 09f76bd13..7e7e8f709 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:044417e2259f58b8b71c44c49a60fe928e7faac1616d76eba7e156764c1bc2b2 -size 88583 +oid sha256:3d705d757b1689f3730a673988ffd2b3f8c1729c8c84b4236cd72593420ff0bd +size 109694 diff --git a/examples/custom_window_frame/screenshot.png b/examples/custom_window_frame/screenshot.png index bb327544a..81e28f07c 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:1eba63346816dfbcf90c6b87e8df8b2de989d33359fda91041a813c474f85cc1 -size 17981 +oid sha256:8d153d2468d7099f2da099c5ea65329196a83baf112b292de1c5a6ce758dfced +size 17635 diff --git a/examples/external_eventloop/screenshot.png b/examples/external_eventloop/screenshot.png index 1236f4f84..b5b2a568c 100644 --- a/examples/external_eventloop/screenshot.png +++ b/examples/external_eventloop/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f2630b0cfe5b044698e9f9533752e23a130e1984dfa0123645bc040d412dba5 -size 8636 +oid sha256:52c47927d0c4caace5e5489cb3c6e4880f4f29b34378e072222ad8492225fef0 +size 9366 diff --git a/examples/file_dialog/screenshot.png b/examples/file_dialog/screenshot.png index 7a1cc0417..2858a9d55 100644 --- a/examples/file_dialog/screenshot.png +++ b/examples/file_dialog/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb8e0b6d09f6d4c598f4a4d91f849a6221acbfaab55711f890b03d551967d3a8 -size 3996 +oid sha256:93689f54b621ab0e978a123492ba64615e67815c39df7e3869f233cdd16f6ded +size 5558 diff --git a/examples/hello_world_simple/screenshot.png b/examples/hello_world_simple/screenshot.png index 546366aea..0d27bc056 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:7be893df63405f3e86d1eb280083914a7ac63bb21fb8dce47e0476fb48ec9bd8 -size 8293 +oid sha256:7cde6a351fbcf5a5d3b5474353c627a9c93801563880b7ff41dfa025da3c6d37 +size 8587 diff --git a/examples/images/screenshot.png b/examples/images/screenshot.png index 8dd58f2bd..688345247 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:329972caa792f9e3a7caf207f41c35e1e26f0d09067e9282bf6538d560f13f7c -size 79617 +oid sha256:aab48522ee4e3382982a0ee558a78e7b501ab4205c793a66078c6e74631bd7a5 +size 79718 diff --git a/examples/keyboard_events/screenshot.png b/examples/keyboard_events/screenshot.png index 3048a2c5c..973827481 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:45fce5a660dbca5a2ecca2fa93fa99b67dce7b03ad583fb5d44d2642d052a80c -size 8603 +oid sha256:c4b314fc336e6cf027594b3ca123318bc4772fb611486170fd370cfa6a56edc5 +size 9599 diff --git a/examples/popups/screenshot.png b/examples/popups/screenshot.png index 54b1df8c7..c650c1466 100644 --- a/examples/popups/screenshot.png +++ b/examples/popups/screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e082670ac9daaee83e593c5dab0e3fccc6f6a4b824071f4983f7f51da144cad9 -size 17893 +oid sha256:fb79ed9f98a4e5b452f52c9d5a857126481aad7c98e871895b8eea90d89c61c7 +size 20053 diff --git a/examples/puffin_profiler/screenshot.png b/examples/puffin_profiler/screenshot.png index fba55018a..616b95e09 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:0787ac28dc8a0ca979f9be5f20c3fb1e2e1c5733add61d201bfe965590afe062 -size 25999 +oid sha256:bfc5dcdf4e4a606cc5b656cfe61d9def9ce1a2eb515003a6a339fefc9335079e +size 29711 diff --git a/examples/user_attention/screenshot.png b/examples/user_attention/screenshot.png index eaaeda498..e38a84958 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:1d723a89d05be6e254846b8999041c54d5142baefcb8ad8c1effa5a50bae7791 -size 6578 +oid sha256:d3b56bdd917a8f789f5bad954bbd37fc27ddec404025dd0608894e5d50e9f37f +size 7375 diff --git a/scripts/publish_crates.sh b/scripts/publish_crates.sh index c2f2fc24e..a8fb54c53 100755 --- a/scripts/publish_crates.sh +++ b/scripts/publish_crates.sh @@ -9,6 +9,6 @@ (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_kittest && cargo publish --quiet) && echo "✅ egui_kittest" (cd crates/egui_demo_lib && cargo publish --quiet) && echo "✅ egui_demo_lib" From c79096ecc4e1a2fb66c30bd15692688ae098dcff Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 15 Oct 2025 11:42:52 +0200 Subject: [PATCH 284/388] Add `egui_kittest::Harness::set_options` (#7638) Makes it easier to set the same options for many tests --------- Co-authored-by: Lucas Meurer --- crates/egui_kittest/src/builder.rs | 12 ++++++++++++ crates/egui_kittest/src/lib.rs | 9 +++++++++ crates/egui_kittest/src/snapshot.rs | 19 ++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index 75ebd54b2..cc686914f 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -14,6 +14,9 @@ pub struct HarnessBuilder { pub(crate) state: PhantomData, pub(crate) renderer: Box, pub(crate) wait_for_pending_images: bool, + + #[cfg(feature = "snapshot")] + pub(crate) default_snapshot_options: crate::SnapshotOptions, } impl Default for HarnessBuilder { @@ -28,6 +31,9 @@ impl Default for HarnessBuilder { step_dt: 1.0 / 4.0, wait_for_pending_images: true, os: egui::os::OperatingSystem::Nix, + + #[cfg(feature = "snapshot")] + default_snapshot_options: crate::SnapshotOptions::default(), } } } @@ -56,6 +62,12 @@ impl HarnessBuilder { self } + /// Set the default options used for snapshot tests on this harness. + #[cfg(feature = "snapshot")] + pub fn with_options(&mut self, options: crate::SnapshotOptions) { + self.default_snapshot_options = options; + } + /// Override the [`egui::os::OperatingSystem`] reported to egui. /// /// This affects e.g. the way shortcuts are displayed. So for snapshot tests, diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index e331119e3..c8112f47b 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -75,6 +75,9 @@ pub struct Harness<'a, State = ()> { step_dt: f32, wait_for_pending_images: bool, queued_events: EventQueue, + + #[cfg(feature = "snapshot")] + default_snapshot_options: SnapshotOptions, } impl Debug for Harness<'_, State> { @@ -100,6 +103,9 @@ impl<'a, State> Harness<'a, State> { state: _, mut renderer, wait_for_pending_images, + + #[cfg(feature = "snapshot")] + default_snapshot_options, } = builder; let ctx = ctx.unwrap_or_default(); ctx.set_theme(theme); @@ -147,6 +153,9 @@ impl<'a, State> Harness<'a, State> { step_dt, wait_for_pending_images, queued_events: Default::default(), + + #[cfg(feature = "snapshot")] + default_snapshot_options, }; // Run the harness until it is stable, ensuring that all Areas are shown and animations are done harness.run_ok(); diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index c11533206..f6511c451 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; pub type SnapshotResult = Result<(), SnapshotError>; #[non_exhaustive] +#[derive(Clone, Debug)] pub struct SnapshotOptions { /// The threshold for the image comparison. /// The default is `0.6` (which is enough for most egui tests to pass across different @@ -556,9 +557,17 @@ pub fn image_snapshot(current: &image::RgbaImage, name: impl Into) { #[cfg(any(feature = "wgpu", feature = "snapshot"))] impl Harness<'_, State> { + /// The default options used for snapshot tests. + /// set by [`crate::HarnessBuilder::with_options`]. + pub fn options(&self) -> &SnapshotOptions { + &self.default_snapshot_options + } + /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot /// with custom options. /// + /// These options will override the ones set by [`crate::HarnessBuilder::with_options`]. + /// /// If you want to change the default options for your whole project, you could create an /// [extension trait](http://xion.io/post/code/rust-extension-traits.html) to create a /// new `my_image_snapshot` function on the Harness that calls this function with the desired options. @@ -586,6 +595,9 @@ impl Harness<'_, State> { } /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot. + /// + /// This is like [`Self::try_snapshot_options`] but will use the options set by [`crate::HarnessBuilder::with_options`]. + /// /// The snapshot will be saved under `tests/snapshots/{name}.png`. /// The new image from the last test run will be saved under `tests/snapshots/{name}.new.png`. /// If the new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`. @@ -597,12 +609,14 @@ impl Harness<'_, State> { let image = self .render() .map_err(|err| SnapshotError::RenderError { err })?; - try_image_snapshot(&image, name) + try_image_snapshot_options(&image, name.into(), &self.default_snapshot_options) } /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot /// with custom options. /// + /// These options will override the ones set by [`crate::HarnessBuilder::with_options`]. + /// /// If you want to change the default options for your whole project, you could create an /// [extension trait](http://xion.io/post/code/rust-extension-traits.html) to create a /// new `my_image_snapshot` function on the Harness that calls this function with the desired options. @@ -629,6 +643,9 @@ impl Harness<'_, State> { } /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot. + /// + /// This is like [`Self::snapshot_options`] but will use the options set by [`crate::HarnessBuilder::with_options`]. + /// /// The snapshot will be saved under `tests/snapshots/{name}.png`. /// The new image from the last test run will be saved under `tests/snapshots/{name}.new.png`. /// If the new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`. From bf5604b3c7c342ba6f1dec5a9f3720ce95aeafbb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 15 Oct 2025 12:08:49 +0200 Subject: [PATCH 285/388] Release egui_kittest 0.33.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/egui_kittest/CHANGELOG.md | 4 ++++ crates/egui_kittest/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6cd1533c..cecaf6935 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1445,7 +1445,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.33.0" +version = "0.33.1" dependencies = [ "dify", "document-features", diff --git a/Cargo.toml b/Cargo.toml index 7aaa81139..dfdb595c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ egui_extras = { version = "0.33.0", path = "crates/egui_extras", default-feature egui-wgpu = { version = "0.33.0", path = "crates/egui-wgpu", default-features = false } egui_demo_lib = { version = "0.33.0", path = "crates/egui_demo_lib", default-features = false } egui_glow = { version = "0.33.0", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.33.0", path = "crates/egui_kittest", default-features = false } +egui_kittest = { version = "0.33.1", path = "crates/egui_kittest", default-features = false } eframe = { version = "0.33.0", path = "crates/eframe", default-features = false } accesskit = "0.21.1" diff --git a/crates/egui_kittest/CHANGELOG.md b/crates/egui_kittest/CHANGELOG.md index 9716c726d..aab4d9d27 100644 --- a/crates/egui_kittest/CHANGELOG.md +++ b/crates/egui_kittest/CHANGELOG.md @@ -6,6 +6,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.33.1 - 2025-10-15 +* Add `egui_kittest::HarnessBuilder::with_options` [#7638](https://github.com/emilk/egui/pull/7638) by [@emilk](https://github.com/emilk) + + ## 0.33.0 - 2025-10-09 ### ⭐ Added * Kittest: Add `UPDATE_SNAPSHOTS=force` [#7508](https://github.com/emilk/egui/pull/7508) by [@emilk](https://github.com/emilk) diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index c283d0734..a81413840 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egui_kittest" -version.workspace = true +version = "0.33.1" authors = ["Lucas Meurer ", "Emil Ernerfeldt "] description = "Testing library for egui based on kittest and AccessKit" edition.workspace = true From 30eb38ef451bb115572e7672adb51bcf31ab5cc3 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 23 Oct 2025 10:06:37 +0200 Subject: [PATCH 286/388] Fix kitdiff links in pr comments (#7639) The pr data is not accessible in this workflow so I have to hardcode the url with the pr number --- .github/workflows/preview_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preview_deploy.yml b/.github/workflows/preview_deploy.yml index 8fbe8bae7..d29e7f4b0 100644 --- a/.github/workflows/preview_deploy.yml +++ b/.github/workflows/preview_deploy.yml @@ -62,6 +62,6 @@ jobs: Preview available at https://egui-pr-preview.github.io/pr/${{ env.URL_SLUG }} Note that it might take a couple seconds for the update to show up after the preview_build workflow has completed. - View snapshot changes at [kitdiff](https://rerun-io.github.io/kitdiff/?url=${{ github.event.pull_request.html_url }}) + View snapshot changes at [kitdiff](https://rerun-io.github.io/kitdiff/?url=https://github.com/emilk/egui/pull/${{ env.PR_NUMBER }}) pr_number: ${{ env.PR_NUMBER }} comment_tag: 'egui-preview' From f6fa74c66578be17c1a2a80eb33b1704f17a3d5f Mon Sep 17 00:00:00 2001 From: Ian Hobson Date: Thu, 23 Oct 2025 10:24:06 +0200 Subject: [PATCH 287/388] Don't enable `arboard` on iOS (#7663) `arboard` [doesn't support support iOS yet](https://github.com/1Password/arboard/pull/103), so this PR adds iOS to the conditions that prevent `arboard` from being enabled. Launching an app on a physical device results in a long timeout (~8s) while trying to connect to the X11 server (the timeout is immediate when launching on a simulator), with the following trace: ``` egui_winit::clipboard: Failed to initialize arboard clipboard: Unknown error while interacting with the clipboard: X11 server connection timed out because it was unreachable ``` * [x] I have followed the instructions in the PR template --- crates/egui-winit/src/clipboard.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index cec4b43c2..fc7334388 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -5,7 +5,10 @@ use raw_window_handle::RawDisplayHandle; /// If the "clipboard" feature is off, or we cannot connect to the OS clipboard, /// then a fallback clipboard that just works within the same app is used instead. pub struct Clipboard { - #[cfg(all(feature = "arboard", not(target_os = "android")))] + #[cfg(all( + not(any(target_os = "android", target_os = "ios")), + feature = "arboard", + ))] arboard: Option, #[cfg(all( @@ -28,7 +31,10 @@ impl Clipboard { /// Construct a new instance pub fn new(_raw_display_handle: Option) -> Self { Self { - #[cfg(all(feature = "arboard", not(target_os = "android")))] + #[cfg(all( + not(any(target_os = "android", target_os = "ios")), + feature = "arboard", + ))] arboard: init_arboard(), #[cfg(all( @@ -68,7 +74,10 @@ impl Clipboard { }; } - #[cfg(all(feature = "arboard", not(target_os = "android")))] + #[cfg(all( + not(any(target_os = "android", target_os = "ios")), + feature = "arboard", + ))] if let Some(clipboard) = &mut self.arboard { return match clipboard.get_text() { Ok(text) => Some(text), @@ -98,7 +107,10 @@ impl Clipboard { return; } - #[cfg(all(feature = "arboard", not(target_os = "android")))] + #[cfg(all( + not(any(target_os = "android", target_os = "ios")), + feature = "arboard", + ))] if let Some(clipboard) = &mut self.arboard { if let Err(err) = clipboard.set_text(text) { log::error!("arboard copy/cut error: {err}"); @@ -110,7 +122,10 @@ impl Clipboard { } pub fn set_image(&mut self, image: &egui::ColorImage) { - #[cfg(all(feature = "arboard", not(target_os = "android")))] + #[cfg(all( + not(any(target_os = "android", target_os = "ios")), + feature = "arboard", + ))] if let Some(clipboard) = &mut self.arboard { if let Err(err) = clipboard.set_image(arboard::ImageData { width: image.width(), @@ -130,7 +145,10 @@ impl Clipboard { } } -#[cfg(all(feature = "arboard", not(target_os = "android")))] +#[cfg(all( + not(any(target_os = "android", target_os = "ios")), + feature = "arboard", +))] fn init_arboard() -> Option { profiling::function_scope!(); From 999e943e594ccb60846b98d1376f61c82d638e78 Mon Sep 17 00:00:00 2001 From: Isse Date: Mon, 27 Oct 2025 11:46:51 +0100 Subject: [PATCH 288/388] Add `is_scrolling`/`is_smooth_scrolling` util, checking for active scroll action. (#7669) * Closes #7657 * [x] I have followed the instructions in the PR template On native this uses a new "touch phase" parameter of the mouse wheel event to know if a scroll action is done. --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/web/events.rs | 1 + crates/egui-winit/src/lib.rs | 32 +++++-- crates/egui/src/data/input.rs | 5 + crates/egui/src/input_state/mod.rs | 144 ++++++++++++++++++++--------- 4 files changed, 127 insertions(+), 55 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index eb0d848e0..c61a80012 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -831,6 +831,7 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV unit, delta, modifiers, + phase: egui::TouchPhase::Move, } }; let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event); diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index ed293e39a..d72c44245 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -312,8 +312,8 @@ impl State { consumed: self.egui_ctx.wants_pointer_input(), } } - WindowEvent::MouseWheel { delta, .. } => { - self.on_mouse_wheel(window, *delta); + WindowEvent::MouseWheel { delta, phase, .. } => { + self.on_mouse_wheel(window, *delta, *phase); EventResponse { repaint: true, consumed: self.egui_ctx.wants_pointer_input(), @@ -545,12 +545,13 @@ impl State { } } - WindowEvent::PanGesture { delta, .. } => { + WindowEvent::PanGesture { delta, phase, .. } => { let pixels_per_point = pixels_per_point(&self.egui_ctx, window); self.egui_input.events.push(egui::Event::MouseWheel { unit: egui::MouseWheelUnit::Point, delta: Vec2::new(delta.x, delta.y) / pixels_per_point, + phase: to_egui_touch_phase(*phase), modifiers: self.egui_input.modifiers, }); EventResponse { @@ -680,12 +681,7 @@ impl State { self.egui_input.events.push(egui::Event::Touch { device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)), id: egui::TouchId::from(touch.id), - phase: match touch.phase { - winit::event::TouchPhase::Started => egui::TouchPhase::Start, - winit::event::TouchPhase::Moved => egui::TouchPhase::Move, - winit::event::TouchPhase::Ended => egui::TouchPhase::End, - winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel, - }, + phase: to_egui_touch_phase(touch.phase), pos: egui::pos2( touch.location.x as f32 / pixels_per_point, touch.location.y as f32 / pixels_per_point, @@ -738,7 +734,12 @@ impl State { } } - fn on_mouse_wheel(&mut self, window: &Window, delta: winit::event::MouseScrollDelta) { + fn on_mouse_wheel( + &mut self, + window: &Window, + delta: winit::event::MouseScrollDelta, + phase: winit::event::TouchPhase, + ) { let pixels_per_point = pixels_per_point(&self.egui_ctx, window); { @@ -754,10 +755,12 @@ impl State { egui::vec2(x as f32, y as f32) / pixels_per_point, ), }; + let phase = to_egui_touch_phase(phase); let modifiers = self.egui_input.modifiers; self.egui_input.events.push(egui::Event::MouseWheel { unit, delta, + phase, modifiers, }); } @@ -970,6 +973,15 @@ impl State { } } +fn to_egui_touch_phase(phase: winit::event::TouchPhase) -> egui::TouchPhase { + match phase { + winit::event::TouchPhase::Started => egui::TouchPhase::Start, + winit::event::TouchPhase::Moved => egui::TouchPhase::Move, + winit::event::TouchPhase::Ended => egui::TouchPhase::End, + winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel, + } +} + fn to_egui_theme(theme: winit::window::Theme) -> Theme { match theme { winit::window::Theme::Dark => Theme::Dark, diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 8eecd979d..869945ece 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -535,6 +535,11 @@ pub enum Event { /// as when swiping down on a touch-screen or track-pad with natural scrolling. delta: Vec2, + /// The phase of the scroll, useful for trackpads. + /// + /// If unknown set this to [`TouchPhase::Move`]. + phase: TouchPhase, + /// The state of the modifier keys at the time of the event. modifiers: Modifiers, }, diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index b23a47828..bca53c721 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -225,6 +225,16 @@ pub struct InputState { /// Time of the last scroll event. last_scroll_time: f64, + /// If we are currently in a scroll action. + /// + /// This is not the same as checking if [`Self::smooth_scroll_delta`], or + /// [`Self::raw_scroll_delta`] are zero. This instead relies on the + /// current touch phase received from the mouse wheel event. + /// + /// This value is only `Some` if we have ever received a [`crate::TouchPhase::Start`] event and then + /// know that the current platform supports it. + is_in_scroll_action: Option, + /// Used for smoothing the scroll delta. unprocessed_scroll_delta: Vec2, @@ -357,6 +367,7 @@ impl Default for InputState { pointer: Default::default(), touch_states: Default::default(), + is_in_scroll_action: None, last_scroll_time: f64::NEG_INFINITY, unprocessed_scroll_delta: Vec2::ZERO, unprocessed_scroll_delta_for_zoom: 0.0, @@ -440,53 +451,68 @@ impl InputState { Event::MouseWheel { unit, delta, + phase, modifiers, } => { - let mut delta = match unit { - MouseWheelUnit::Point => *delta, - MouseWheelUnit::Line => options.line_scroll_speed * *delta, - MouseWheelUnit::Page => viewport_rect.height() * *delta, - }; - - let is_horizontal = modifiers.matches_any(options.horizontal_scroll_modifier); - let is_vertical = modifiers.matches_any(options.vertical_scroll_modifier); - - if is_horizontal && !is_vertical { - // Treat all scrolling as horizontal scrolling. - // Note: one Mac we already get horizontal scroll events when shift is down. - delta = vec2(delta.x + delta.y, 0.0); - } - if !is_horizontal && is_vertical { - // Treat all scrolling as vertical scrolling. - delta = vec2(0.0, delta.x + delta.y); - } - - raw_scroll_delta += delta; - - // Mouse wheels often go very large steps. - // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta. - // So we smooth it out over several frames for a nicer user experience when scrolling in egui. - // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing, - // because it adds latency. - let is_smooth = match unit { - MouseWheelUnit::Point => delta.length() < 8.0, // a bit arbitrary here - MouseWheelUnit::Line | MouseWheelUnit::Page => false, - }; - - let is_zoom = modifiers.matches_any(options.zoom_modifier); - - #[expect(clippy::collapsible_else_if)] - if is_zoom { - if is_smooth { - smooth_scroll_delta_for_zoom += delta.x + delta.y; - } else { - unprocessed_scroll_delta_for_zoom += delta.x + delta.y; + match phase { + crate::TouchPhase::Start => { + self.is_in_scroll_action = Some(true); } - } else { - if is_smooth { - smooth_scroll_delta += delta; - } else { - unprocessed_scroll_delta += delta; + crate::TouchPhase::Move => { + let mut delta = match unit { + MouseWheelUnit::Point => *delta, + MouseWheelUnit::Line => options.line_scroll_speed * *delta, + MouseWheelUnit::Page => viewport_rect.height() * *delta, + }; + + let is_horizontal = + modifiers.matches_any(options.horizontal_scroll_modifier); + let is_vertical = + modifiers.matches_any(options.vertical_scroll_modifier); + + if is_horizontal && !is_vertical { + // Treat all scrolling as horizontal scrolling. + // Note: one Mac we already get horizontal scroll events when shift is down. + delta = vec2(delta.x + delta.y, 0.0); + } + if !is_horizontal && is_vertical { + // Treat all scrolling as vertical scrolling. + delta = vec2(0.0, delta.x + delta.y); + } + + raw_scroll_delta += delta; + + // Mouse wheels often go very large steps. + // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta. + // So we smooth it out over several frames for a nicer user experience when scrolling in egui. + // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing, + // because it adds latency. + let is_smooth = match unit { + MouseWheelUnit::Point => delta.length() < 8.0, // a bit arbitrary here + MouseWheelUnit::Line | MouseWheelUnit::Page => false, + }; + + let is_zoom = modifiers.matches_any(options.zoom_modifier); + + #[expect(clippy::collapsible_else_if)] + if is_zoom { + if is_smooth { + smooth_scroll_delta_for_zoom += delta.x + delta.y; + } else { + unprocessed_scroll_delta_for_zoom += delta.x + delta.y; + } + } else { + if is_smooth { + smooth_scroll_delta += delta; + } else { + unprocessed_scroll_delta += delta; + } + } + } + crate::TouchPhase::End | crate::TouchPhase::Cancel => { + if let Some(is_in_scroll_action) = &mut self.is_in_scroll_action { + *is_in_scroll_action = false; + } } } } @@ -542,7 +568,7 @@ impl InputState { } let is_scrolling = raw_scroll_delta != Vec2::ZERO || smooth_scroll_delta != Vec2::ZERO; - let last_scroll_time = if is_scrolling { + let last_scroll_time = if is_scrolling || self.is_in_scroll_action.is_some_and(|b| b) { time } else { self.last_scroll_time @@ -552,6 +578,7 @@ impl InputState { pointer, touch_states: self.touch_states, + is_in_scroll_action: self.is_in_scroll_action, last_scroll_time, unprocessed_scroll_delta, unprocessed_scroll_delta_for_zoom, @@ -708,6 +735,27 @@ impl InputState { .map_or(self.smooth_scroll_delta, |touch| touch.translation_delta) } + /// True if there is an active scroll action that might scroll more when using [`Self::smooth_scroll_delta`]. + pub fn is_smooth_scrolling(&self) -> bool { + self.is_raw_scrolling() || self.smooth_scroll_delta != Vec2::ZERO + } + + /// True if there is an active scroll action that might scroll more. + /// + /// You probably want to use [`Self::is_smooth_scrolling`]. + pub fn is_raw_scrolling(&self) -> bool { + if let Some(is_in_scroll_action) = self.is_in_scroll_action { + is_in_scroll_action + } else { + // On certain platforms, like web, we don't get the start & stop scrolling events, so + // we rely on a timer there. + // + // Tested on a mac touchpad 2025, where the largest observed gap between scroll events + // was 68 ms. So 100 ms should most likely be good here. + self.time_since_last_scroll() < 0.1 + } + } + /// How long has it been (in seconds) since the use last scrolled? #[inline(always)] pub fn time_since_last_scroll(&self) -> f32 { @@ -1599,6 +1647,7 @@ impl InputState { pointer, touch_states, + is_in_scroll_action: _, last_scroll_time, unprocessed_scroll_delta, unprocessed_scroll_delta_for_zoom, @@ -1642,6 +1691,11 @@ impl InputState { }); } + ui.label(format!( + "is_scrolling: raw: {}, smooth: {}", + self.is_raw_scrolling(), + self.is_smooth_scrolling() + )); ui.label(format!( "Time since last scroll: {:.1} s", time - last_scroll_time From 2669344d5c03e3d92050f5de9936f2d85e6f65d1 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Mon, 27 Oct 2025 09:52:42 -0400 Subject: [PATCH 289/388] Add `Plugin::on_widget_under_pointer` to support widget inspector (#7652) This PR adds `Plugin::on_widget_under_pointer` which gets called whenever a widget is created whose rect contains the pointer. The point of the hook is to capture a stack trace which can be used to map widgets to their corresponding source code so it must be called while the widget is being created. The obvious concern is performance impact. However, since it's only called for rects under the cursor, the effect seems negligible afaict. It's under `debug_assertions` just in case. This change is needed so we can publish the widget inspector we've been working on. Basically a plugin that allows us to jump from any widget back to their corresponding source code. This video shows the plugin configured to open the corresponding code in github, but normally it would open your local editor. Update: [Live demo](https://membrane-io.github.io/egui/) (Firefox/Safari not yet supported. `Cmd-I` to inspect. `Tab` to cycle filters. `Click` to open). It will try to open a file under `/home/runner/work/egui/egui/` so it won't work, but you get the idea. https://github.com/user-attachments/assets/afe4d6af-7f67-44b5-be25-44f7564d9a3a ## What's next After this gets merged I plan to publish the above plugin as its own crate, that way we can iterate and release quickly while things are still changing. I agree it would make sense to eventually merge it into the main egui repo (like @emilk suggested in #4650). * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/context.rs | 6 ++++++ crates/egui/src/plugin.rs | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index aab81ab2c..5a868dd75 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1198,6 +1198,12 @@ impl Context { #[allow(clippy::let_and_return, clippy::allow_attributes)] let res = self.get_response(w); + #[cfg(debug_assertions)] + if res.contains_pointer() { + let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); + plugins.on_widget_under_pointer(self, &w); + } + #[cfg(feature = "accesskit")] if allow_focus && w.sense.is_focusable() { // Make sure anything that can receive focus has an AccessKit node. diff --git a/crates/egui/src/plugin.rs b/crates/egui/src/plugin.rs index bebcf892e..3e078d21c 100644 --- a/crates/egui/src/plugin.rs +++ b/crates/egui/src/plugin.rs @@ -34,14 +34,21 @@ pub trait Plugin: Send + Sync + std::any::Any + 'static { /// Called just before the input is processed. /// /// Useful to inspect or modify the input. - /// Since this is called outside a pass, don't show ui here. + /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though. fn input_hook(&mut self, input: &mut RawInput) {} /// Called just before the output is passed to the backend. /// /// Useful to inspect or modify the output. - /// Since this is called outside a pass, don't show ui here. + /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though. fn output_hook(&mut self, output: &mut FullOutput) {} + + /// Called when a widget is created and is under the pointer. + /// + /// Useful for capturing a stack trace so that widgets can be mapped back to their source code. + /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though. + #[cfg(debug_assertions)] + fn on_widget_under_pointer(&mut self, ctx: &Context, widget: &crate::WidgetRect) {} } pub(crate) struct PluginHandle { @@ -167,6 +174,14 @@ impl PluginsOrdered { plugin.output_hook(output); }); } + + #[cfg(debug_assertions)] + pub fn on_widget_under_pointer(&self, ctx: &Context, widget: &crate::WidgetRect) { + profiling::scope!("plugins", "on_widget_under_pointer"); + self.for_each_dyn(|plugin| { + plugin.on_widget_under_pointer(ctx, widget); + }); + } } impl Plugins { From e861c8ec79e6af6e2e5623ea5b16f39397aaeeba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20G=C5=82uchowski?= Date: Fri, 31 Oct 2025 09:45:32 +0000 Subject: [PATCH 290/388] Avoid cloning `Row`s during `Galley::concat` (#7649) Moves `ends_with_newline` into `PlacedRow` to avoid clones during layout. I don't think there was a rationale stronger than "don't change too much" for not doing this in https://github.com/emilk/egui/pull/5411, so I should've just done this from the start. This was a significant part of the profile for text layout (as it cloned almost every `Row`, even though it only needed to change a single boolean). Before: image After: image (note that these profiles focus solely on the top-level `Galley::layout_inline` subtree, also don't compare sample count as the duration of these tests was completely arbitrary) egui_demo_lib `*text_layout*` benches: image * [X] I have followed the instructions in the PR template (As usual, the tests fail for me even on master but the failures on master and with these changes seem the same :)) --- crates/egui/src/text_selection/visuals.rs | 5 +-- crates/epaint/src/shapes/text_shape.rs | 8 +++-- crates/epaint/src/text/text_layout.rs | 13 ++++--- crates/epaint/src/text/text_layout_types.rs | 38 +++++++++++---------- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index e3054b19d..0f6d54abd 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -31,7 +31,8 @@ pub fn paint_text_selection( let max = galley.layout_from_cursor(max); for ri in min.row..=max.row { - let row = Arc::make_mut(&mut galley.rows[ri].row); + let placed_row = &mut galley.rows[ri]; + let row = Arc::make_mut(&mut placed_row.row); let left = if ri == min.row { row.x_offset(min.column) @@ -41,7 +42,7 @@ pub fn paint_text_selection( let right = if ri == max.row { row.x_offset(max.column) } else { - let newline_size = if row.ends_with_newline { + let newline_size = if placed_row.ends_with_newline { row.height() / 2.0 // visualize that we select the newline } else { 0.0 diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index 349707eac..92a0a0514 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -137,7 +137,12 @@ impl TextShape { *mesh_bounds = transform.scaling * *mesh_bounds; *intrinsic_size = transform.scaling * *intrinsic_size; - for text::PlacedRow { pos, row } in rows { + for text::PlacedRow { + pos, + row, + ends_with_newline: _, + } in rows + { *pos *= transform.scaling; let text::Row { @@ -145,7 +150,6 @@ impl TextShape { glyphs: _, // TODO(emilk): would it make sense to transform these? size, visuals, - ends_with_newline: _, } = Arc::make_mut(row); *size *= transform.scaling; diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index cf791351a..b1fe895da 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -296,8 +296,8 @@ fn rows_from_paragraphs( glyphs: vec![], visuals: Default::default(), size: vec2(0.0, paragraph.empty_paragraph_height), - ends_with_newline: !is_last_paragraph, }), + ends_with_newline: !is_last_paragraph, }); } else { let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x(); @@ -310,14 +310,13 @@ fn rows_from_paragraphs( glyphs: paragraph.glyphs, visuals: Default::default(), size: vec2(paragraph_max_x, 0.0), - ends_with_newline: !is_last_paragraph, }), + ends_with_newline: !is_last_paragraph, }); } else { line_break(¶graph, job, &mut rows, elided); let placed_row = rows.last_mut().unwrap(); - let row = Arc::make_mut(&mut placed_row.row); - row.ends_with_newline = !is_last_paragraph; + placed_row.ends_with_newline = !is_last_paragraph; } } } @@ -363,8 +362,8 @@ fn line_break( glyphs: vec![], visuals: Default::default(), size: Vec2::ZERO, - ends_with_newline: false, }), + ends_with_newline: false, }); row_start_x += first_row_indentation; first_row_indentation = 0.0; @@ -389,8 +388,8 @@ fn line_break( glyphs, visuals: Default::default(), size: vec2(paragraph_max_x, 0.0), - ends_with_newline: false, }), + ends_with_newline: false, }); // Start a new row: @@ -431,8 +430,8 @@ fn line_break( glyphs, visuals: Default::default(), size: vec2(paragraph_max_x - paragraph_min_x, 0.0), - ends_with_newline: false, }), + ends_with_newline: false, }); } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 1adcc515e..d87f9a579 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -572,6 +572,13 @@ pub struct PlacedRow { /// The underlying unpositioned [`Row`]. pub row: Arc, + + /// If true, this [`PlacedRow`] came from a paragraph ending with a `\n`. + /// The `\n` itself is omitted from row's [`Row::glyphs`]. + /// A `\n` in the input text always creates a new [`PlacedRow`] below it, + /// so that text that ends with `\n` has an empty [`PlacedRow`] last. + /// This also implies that the last [`PlacedRow`] in a [`Galley`] always has `ends_with_newline == false`. + pub ends_with_newline: bool, } impl PlacedRow { @@ -617,13 +624,6 @@ pub struct Row { /// The mesh, ready to be rendered. pub visuals: RowVisuals, - - /// If true, this [`Row`] came from a paragraph ending with a `\n`. - /// The `\n` itself is omitted from [`Self::glyphs`]. - /// A `\n` in the input text always creates a new [`Row`] below it, - /// so that text that ends with `\n` has an empty [`Row`] last. - /// This also implies that the last [`Row`] in a [`Galley`] always has `ends_with_newline == false`. - pub ends_with_newline: bool, } /// The tessellated output of a row. @@ -735,12 +735,6 @@ impl Row { self.glyphs.len() } - /// Includes the implicit `\n` after the [`Row`], if any. - #[inline] - pub fn char_count_including_newline(&self) -> usize { - self.glyphs.len() + (self.ends_with_newline as usize) - } - /// Closest char at the desired x coordinate in row-relative coordinates. /// Returns something in the range `[0, char_count_excluding_newline()]`. pub fn char_at(&self, desired_x: f32) -> usize { @@ -776,6 +770,12 @@ impl PlacedRow { pub fn max_y(&self) -> f32 { self.rect().bottom() } + + /// Includes the implicit `\n` after the [`PlacedRow`], if any. + #[inline] + pub fn char_count_including_newline(&self) -> usize { + self.row.glyphs.len() + (self.ends_with_newline as usize) + } } impl Galley { @@ -867,13 +867,15 @@ impl Galley { 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 mut ends_with_newline = placed_row.ends_with_newline; 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; + // Since we remove the `\n` when splitting rows, we need to add it back here + ends_with_newline |= !is_last_galley && is_last_row_in_galley; + super::PlacedRow { + pos: new_pos, + row: placed_row.row.clone(), + ends_with_newline, } - super::PlacedRow { pos: new_pos, row } })); merged_galley.num_vertices += galley.num_vertices; From c5347f28e40298345945c81846ac4cca87cb00d8 Mon Sep 17 00:00:00 2001 From: ASPCartman Date: Sat, 1 Nov 2025 14:55:56 +0300 Subject: [PATCH 291/388] Fix jittering during window resize on MacOS for WGPU/Metal (#7641) Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/wgpu_integration.rs | 38 ++++++++++---- crates/egui-wgpu/Cargo.toml | 5 +- crates/egui-wgpu/src/winit.rs | 55 ++++++++++++++++++++ 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 046340bdb..b21b62aca 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -71,6 +71,7 @@ pub struct SharedState { painter: egui_wgpu::winit::Painter, viewport_from_window: HashMap, focused_viewport: Option, + resized_viewport: Option, } pub type Viewports = egui::OrderedViewportIdMap; @@ -302,6 +303,7 @@ impl<'app> WgpuWinitApp<'app> { viewports, painter, focused_viewport: Some(ViewportId::ROOT), + resized_viewport: None, })); { @@ -763,20 +765,34 @@ impl WgpuWinitRunning<'_> { let viewport_id = shared.viewport_from_window.get(&window_id).copied(); // On Windows, if a window is resized by the user, it should repaint synchronously, inside the - // event handler. - // - // If this is not done, the compositor will assume that the window does not want to redraw, - // and continue ahead. + // event handler. If this is not done, the compositor will assume that the window does not want + // to redraw and continue ahead. // // In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver - // new frames to the compositor in time. - // - // The flickering is technically glutin or glow's fault, but we should be responding properly + // new frames to the compositor in time. The flickering is technically glutin or glow's fault, but we should be responding properly // to resizes anyway, as doing so avoids dropping frames. // // See: https://github.com/emilk/egui/issues/903 let mut repaint_asap = false; + // On MacOS the asap repaint is not enough. The drawn frames must be synchronized with + // the CoreAnimation transactions driving the window resize process. + // + // Thus, Painter, responsible for wgpu surfaces and their resize, has to be notified of the + // resize lifecycle, yet winit does not provide any events for that. To work around, + // the last resized viewport is tracked until any next non-resize event is received. + // + // Accidental state change during the resize process due to an unexpected event fire + // is ok, state will switch back upon next resize event. + // + // See: https://github.com/emilk/egui/issues/903 + if let Some(id) = viewport_id + && shared.resized_viewport == viewport_id + { + shared.painter.on_window_resize_state_change(id, false); + shared.resized_viewport = None; + } + match event { winit::event::WindowEvent::Focused(focused) => { let focused = if cfg!(target_os = "macos") @@ -799,14 +815,18 @@ impl WgpuWinitRunning<'_> { // Resize with 0 width and height is used by winit to signal a minimize event on Windows. // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. - if let Some(viewport_id) = viewport_id + if let Some(id) = viewport_id && let (Some(width), Some(height)) = ( NonZeroU32::new(physical_size.width), NonZeroU32::new(physical_size.height), ) { + if shared.resized_viewport != viewport_id { + shared.resized_viewport = viewport_id; + shared.painter.on_window_resize_state_change(id, true); + } + shared.painter.on_window_resized(id, width, height); repaint_asap = true; - shared.painter.on_window_resized(viewport_id, width, height); } } diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 88321e652..cd897b63e 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -25,7 +25,7 @@ all-features = true rustdoc-args = ["--generate-link-to-definition"] [features] -default = ["fragile-send-sync-non-atomic-wasm", "wgpu/default"] +default = ["fragile-send-sync-non-atomic-wasm", "macos-window-resize-jitter-fix", "wgpu/default"] ## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11` winit = ["dep:winit", "winit/rwh_06"] @@ -43,6 +43,9 @@ x11 = ["winit?/x11"] ## Thus that usage is guarded against with compiler errors in wgpu. fragile-send-sync-non-atomic-wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"] +## Enables `present_with_transaction` surface flag temporary during window resize on MacOS. +macos-window-resize-jitter-fix = ["wgpu/metal"] + [dependencies] egui = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false, features = ["bytemuck"] } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index bbd19edb1..3a286cc9e 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -14,6 +14,7 @@ struct SurfaceState { alpha_mode: wgpu::CompositeAlphaMode, width: u32, height: u32, + resizing: bool, } /// Everything you need to paint egui with [`wgpu`] on [`winit`]. @@ -230,6 +231,7 @@ impl Painter { width: size.width, height: size.height, alpha_mode, + resizing: false, }, ); let Some(width) = NonZeroU32::new(size.width) else { @@ -326,6 +328,59 @@ impl Painter { } } + /// Handles changes of the resizing state. + /// + /// Should be called prior to the first [`Painter::on_window_resized`] call and after the last in + /// the chain. Used to apply platform-specific logic, e.g. OSX Metal window resize jitter fix. + pub fn on_window_resize_state_change(&mut self, viewport_id: ViewportId, resizing: bool) { + profiling::function_scope!(); + + let Some(state) = self.surfaces.get_mut(&viewport_id) else { + return; + }; + if state.resizing == resizing { + if resizing { + log::debug!( + "Painter::on_window_resize_state_change() redundant call while resizing" + ); + } else { + log::debug!( + "Painter::on_window_resize_state_change() redundant call after resizing" + ); + } + return; + } + + // Resizing is a bit tricky on macOS. + // It requires enabling ["present_with_transaction"](https://developer.apple.com/documentation/quartzcore/cametallayer/presentswithtransaction) + // flag to avoid jittering during the resize. Even though resize jittering on macOS + // is common across rendering backends, the solution for wgpu/metal is known. + // + // See https://github.com/emilk/egui/issues/903 + #[cfg(all(target_os = "macos", feature = "macos-window-resize-jitter-fix"))] + { + // SAFETY: The cast is checked with if condition. If the used backend is not metal + // it gracefully fails. The pointer casts are valid as it's 1-to-1 type mapping. + // This is how wgpu currently exposes this backend-specific flag. + unsafe { + if let Some(hal_surface) = state.surface.as_hal::() { + let raw = + std::ptr::from_ref::(&*hal_surface).cast_mut(); + + (*raw).present_with_transaction = resizing; + + Self::configure_surface( + state, + self.render_state.as_ref().unwrap(), + &self.configuration, + ); + } + } + } + + state.resizing = resizing; + } + pub fn on_window_resized( &mut self, viewport_id: ViewportId, From fa3457f21c3c404f375e6ff949db3660bd42227c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Pakalns?= Date: Sun, 2 Nov 2025 12:28:33 +0200 Subject: [PATCH 292/388] Fix profiling::scope compile error when profiling using tracing backend (#7646) * Closes https://github.com/emilk/egui/issues/7645 * [x] I have followed the instructions in the PR template --- crates/egui/src/plugin.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/plugin.rs b/crates/egui/src/plugin.rs index 3e078d21c..80afc4510 100644 --- a/crates/egui/src/plugin.rs +++ b/crates/egui/src/plugin.rs @@ -231,7 +231,7 @@ impl Plugin for CallbackPlugin { profiling::function_scope!(); for (_debug_name, cb) in &self.on_begin_plugins { - profiling::scope!(*_debug_name); + profiling::scope!("on_begin_pass", *_debug_name); (cb)(ctx); } } @@ -240,7 +240,7 @@ impl Plugin for CallbackPlugin { profiling::function_scope!(); for (_debug_name, cb) in &self.on_end_plugins { - profiling::scope!(*_debug_name); + profiling::scope!("on_end_pass", *_debug_name); (cb)(ctx); } } From 1b77d7047e72e825593be838c6b9ce2b17611f79 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 3 Nov 2025 11:53:55 +0100 Subject: [PATCH 293/388] Improve modifier handling when scrolling (#7678) ### Problem Letting go of the modifier key before the last momentum-scroll events arrive will cause the scroll direction to change. This problem can be seen by going to egui.rs and opening the "Scene" example. Hold down shift, start a momentum-scroll (on a Mac trackpad), then quickly let go of shift: you'll see the scroll direction change, which feels wrong. ### Solution Store the modifiers at the start of the event, thanks to the new `phase` info added in * https://github.com/emilk/egui/pull/7669 Note that this solution only works on native; not on web. ### Other * Break out wheel/scroll handling into own file * Simplify it a lot by deciding late on wether an input is a scroll or a zoom * Assume input is already smooth if there are `phase` events --- crates/egui/src/containers/scene.rs | 2 +- crates/egui/src/containers/scroll_area.rs | 10 +- crates/egui/src/containers/tooltip.rs | 2 +- crates/egui/src/input_state/mod.rs | 261 +++++---------------- crates/egui/src/input_state/wheel_state.rs | 233 ++++++++++++++++++ 5 files changed, 302 insertions(+), 206 deletions(-) create mode 100644 crates/egui/src/input_state/wheel_state.rs diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index 58739ba2a..36222b138 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -245,7 +245,7 @@ impl Scene { { let pointer_in_scene = to_global.inverse() * mouse_pos; let zoom_delta = ui.ctx().input(|i| i.zoom_delta()); - let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta); + let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta()); // Most of the time we can return early. This is also important to // avoid `ui_from_scene` to change slightly due to floating point errors. diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index ff2542d8d..db64ba03b 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1127,9 +1127,9 @@ impl Prepared { let scroll_delta = ui.ctx().input(|input| { if always_scroll_enabled_direction { // no bidirectional scrolling; allow horizontal scrolling without pressing shift - input.smooth_scroll_delta[0] + input.smooth_scroll_delta[1] + input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1] } else { - input.smooth_scroll_delta[d] + input.smooth_scroll_delta()[d] } }); let scroll_delta = scroll_delta * wheel_scroll_multiplier[d]; @@ -1143,10 +1143,10 @@ impl Prepared { // Clear scroll delta so no parent scroll will use it: ui.ctx().input_mut(|input| { if always_scroll_enabled_direction { - input.smooth_scroll_delta[0] = 0.0; - input.smooth_scroll_delta[1] = 0.0; + input.smooth_scroll_delta()[0] = 0.0; + input.smooth_scroll_delta()[1] = 0.0; } else { - input.smooth_scroll_delta[d] = 0.0; + input.smooth_scroll_delta()[d] = 0.0; } }); diff --git a/crates/egui/src/containers/tooltip.rs b/crates/egui/src/containers/tooltip.rs index 682b11fd8..c46e21d57 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -358,7 +358,7 @@ impl Tooltip<'_> { // We only show the tooltip when the mouse pointer is still. if !response .ctx - .input(|i| i.pointer.is_still() && i.smooth_scroll_delta == Vec2::ZERO) + .input(|i| i.pointer.is_still() && !i.is_scrolling()) { // wait for mouse to stop response.ctx.request_repaint(); diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index bca53c721..7b163a90f 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -1,14 +1,18 @@ mod touch_state; +mod wheel_state; -use crate::data::input::{ - Event, EventFilter, KeyboardShortcut, Modifiers, MouseWheelUnit, NUM_POINTER_BUTTONS, - PointerButton, RawInput, TouchDeviceId, ViewportInfo, -}; use crate::{ SafeAreaInsets, emath::{NumExt as _, Pos2, Rect, Vec2, vec2}, util::History, }; +use crate::{ + data::input::{ + Event, EventFilter, KeyboardShortcut, Modifiers, NUM_POINTER_BUTTONS, PointerButton, + RawInput, TouchDeviceId, ViewportInfo, + }, + input_state::wheel_state::WheelState, +}; use std::{ collections::{BTreeMap, HashSet}, time::Duration, @@ -221,41 +225,8 @@ pub struct InputState { // ---------------------------------------------- // Scrolling: - // - /// Time of the last scroll event. - last_scroll_time: f64, - - /// If we are currently in a scroll action. - /// - /// This is not the same as checking if [`Self::smooth_scroll_delta`], or - /// [`Self::raw_scroll_delta`] are zero. This instead relies on the - /// current touch phase received from the mouse wheel event. - /// - /// This value is only `Some` if we have ever received a [`crate::TouchPhase::Start`] event and then - /// know that the current platform supports it. - is_in_scroll_action: Option, - - /// Used for smoothing the scroll delta. - unprocessed_scroll_delta: Vec2, - - /// Used for smoothing the scroll delta when zooming. - unprocessed_scroll_delta_for_zoom: f32, - - /// You probably want to use [`Self::smooth_scroll_delta`] instead. - /// - /// The raw input of how many points the user scrolled. - /// - /// The delta dictates how the _content_ should move. - /// - /// A positive X-value indicates the content is being moved right, - /// as when swiping right on a touch-screen or track-pad with natural scrolling. - /// - /// A positive Y-value indicates the content is being moved down, - /// as when swiping down on a touch-screen or track-pad with natural scrolling. - /// - /// When using a notched scroll-wheel this will spike very large for one frame, - /// then drop to zero. For a smoother experience, use [`Self::smooth_scroll_delta`]. - pub raw_scroll_delta: Vec2, + #[cfg_attr(feature = "serde", serde(skip))] + wheel: WheelState, /// How many points the user scrolled, smoothed over a few frames. /// @@ -367,11 +338,7 @@ impl Default for InputState { pointer: Default::default(), touch_states: Default::default(), - is_in_scroll_action: None, - last_scroll_time: f64::NEG_INFINITY, - unprocessed_scroll_delta: Vec2::ZERO, - unprocessed_scroll_delta_for_zoom: 0.0, - raw_scroll_delta: Vec2::ZERO, + wheel: Default::default(), smooth_scroll_delta: Vec2::ZERO, zoom_factor_delta: 1.0, rotation_radians: 0.0, @@ -426,12 +393,8 @@ impl InputState { let mut keys_down = self.keys_down; let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor let mut rotation_radians = 0.0; - let mut raw_scroll_delta = Vec2::ZERO; - let mut unprocessed_scroll_delta = self.unprocessed_scroll_delta; - let mut unprocessed_scroll_delta_for_zoom = self.unprocessed_scroll_delta_for_zoom; - let mut smooth_scroll_delta = Vec2::ZERO; - let mut smooth_scroll_delta_for_zoom = 0.0; + self.wheel.smooth_wheel_delta = Vec2::ZERO; for event in &mut new.events { match event { @@ -454,67 +417,15 @@ impl InputState { phase, modifiers, } => { - match phase { - crate::TouchPhase::Start => { - self.is_in_scroll_action = Some(true); - } - crate::TouchPhase::Move => { - let mut delta = match unit { - MouseWheelUnit::Point => *delta, - MouseWheelUnit::Line => options.line_scroll_speed * *delta, - MouseWheelUnit::Page => viewport_rect.height() * *delta, - }; - - let is_horizontal = - modifiers.matches_any(options.horizontal_scroll_modifier); - let is_vertical = - modifiers.matches_any(options.vertical_scroll_modifier); - - if is_horizontal && !is_vertical { - // Treat all scrolling as horizontal scrolling. - // Note: one Mac we already get horizontal scroll events when shift is down. - delta = vec2(delta.x + delta.y, 0.0); - } - if !is_horizontal && is_vertical { - // Treat all scrolling as vertical scrolling. - delta = vec2(0.0, delta.x + delta.y); - } - - raw_scroll_delta += delta; - - // Mouse wheels often go very large steps. - // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta. - // So we smooth it out over several frames for a nicer user experience when scrolling in egui. - // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing, - // because it adds latency. - let is_smooth = match unit { - MouseWheelUnit::Point => delta.length() < 8.0, // a bit arbitrary here - MouseWheelUnit::Line | MouseWheelUnit::Page => false, - }; - - let is_zoom = modifiers.matches_any(options.zoom_modifier); - - #[expect(clippy::collapsible_else_if)] - if is_zoom { - if is_smooth { - smooth_scroll_delta_for_zoom += delta.x + delta.y; - } else { - unprocessed_scroll_delta_for_zoom += delta.x + delta.y; - } - } else { - if is_smooth { - smooth_scroll_delta += delta; - } else { - unprocessed_scroll_delta += delta; - } - } - } - crate::TouchPhase::End | crate::TouchPhase::Cancel => { - if let Some(is_in_scroll_action) = &mut self.is_in_scroll_action { - *is_in_scroll_action = false; - } - } - } + self.wheel.on_wheel_event( + viewport_rect, + &options, + time, + *unit, + *delta, + *phase, + *modifiers, + ); } Event::Zoom(factor) => { zoom_factor_delta *= *factor; @@ -534,55 +445,28 @@ impl InputState { } } + let mut smooth_scroll_delta = Vec2::ZERO; + { let dt = stable_dt.at_most(0.1); - let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO(emilk): parameterize + self.wheel.after_events(time, dt); - if unprocessed_scroll_delta != Vec2::ZERO { - for d in 0..2 { - if unprocessed_scroll_delta[d].abs() < 1.0 { - smooth_scroll_delta[d] += unprocessed_scroll_delta[d]; - unprocessed_scroll_delta[d] = 0.0; - } else { - let applied = t * unprocessed_scroll_delta[d]; - smooth_scroll_delta[d] += applied; - unprocessed_scroll_delta[d] -= applied; - } - } - } + let is_zoom = self.wheel.modifiers.matches_any(options.zoom_modifier); - { - // Smooth scroll-to-zoom: - if unprocessed_scroll_delta_for_zoom.abs() < 1.0 { - smooth_scroll_delta_for_zoom += unprocessed_scroll_delta_for_zoom; - unprocessed_scroll_delta_for_zoom = 0.0; - } else { - let applied = t * unprocessed_scroll_delta_for_zoom; - smooth_scroll_delta_for_zoom += applied; - unprocessed_scroll_delta_for_zoom -= applied; - } - - zoom_factor_delta *= - (options.scroll_zoom_speed * smooth_scroll_delta_for_zoom).exp(); + if is_zoom { + zoom_factor_delta *= (options.scroll_zoom_speed + * (self.wheel.smooth_wheel_delta.x + self.wheel.smooth_wheel_delta.y)) + .exp(); + } else { + smooth_scroll_delta = self.wheel.smooth_wheel_delta; } } - let is_scrolling = raw_scroll_delta != Vec2::ZERO || smooth_scroll_delta != Vec2::ZERO; - let last_scroll_time = if is_scrolling || self.is_in_scroll_action.is_some_and(|b| b) { - time - } else { - self.last_scroll_time - }; - Self { pointer, touch_states: self.touch_states, - is_in_scroll_action: self.is_in_scroll_action, - last_scroll_time, - unprocessed_scroll_delta, - unprocessed_scroll_delta_for_zoom, - raw_scroll_delta, + wheel: self.wheel, smooth_scroll_delta, zoom_factor_delta, rotation_radians, @@ -654,6 +538,22 @@ impl InputState { self.safe_area_insets } + /// How many points the user scrolled, smoothed over a few frames. + /// + /// The delta dictates how the _content_ should move. + /// + /// A positive X-value indicates the content is being moved right, + /// as when swiping right on a touch-screen or track-pad with natural scrolling. + /// + /// A positive Y-value indicates the content is being moved down, + /// as when swiping down on a touch-screen or track-pad with natural scrolling. + /// + /// [`crate::ScrollArea`] will both read and write to this field, so that + /// at the end of the frame this will be zero if a scroll-area consumed the delta. + pub fn smooth_scroll_delta(&self) -> Vec2 { + self.smooth_scroll_delta + } + /// Uniform zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture). /// * `zoom = 1`: no change /// * `zoom < 1`: pinch together @@ -732,34 +632,18 @@ impl InputState { #[inline(always)] pub fn translation_delta(&self) -> Vec2 { self.multi_touch() - .map_or(self.smooth_scroll_delta, |touch| touch.translation_delta) + .map_or(self.smooth_scroll_delta(), |touch| touch.translation_delta) } /// True if there is an active scroll action that might scroll more when using [`Self::smooth_scroll_delta`]. - pub fn is_smooth_scrolling(&self) -> bool { - self.is_raw_scrolling() || self.smooth_scroll_delta != Vec2::ZERO + pub fn is_scrolling(&self) -> bool { + self.wheel.is_scrolling() } - /// True if there is an active scroll action that might scroll more. - /// - /// You probably want to use [`Self::is_smooth_scrolling`]. - pub fn is_raw_scrolling(&self) -> bool { - if let Some(is_in_scroll_action) = self.is_in_scroll_action { - is_in_scroll_action - } else { - // On certain platforms, like web, we don't get the start & stop scrolling events, so - // we rely on a timer there. - // - // Tested on a mac touchpad 2025, where the largest observed gap between scroll events - // was 68 ms. So 100 ms should most likely be good here. - self.time_since_last_scroll() < 0.1 - } - } - - /// How long has it been (in seconds) since the use last scrolled? + /// How long has it been (in seconds) since the last scroll event? #[inline(always)] pub fn time_since_last_scroll(&self) -> f32 { - (self.time - self.last_scroll_time) as f32 + (self.time - self.wheel.last_wheel_event) as f32 } /// The [`crate::Context`] will call this at the beginning of each frame to see if we need a repaint. @@ -771,8 +655,7 @@ impl InputState { /// cause a repaint. pub(crate) fn wants_repaint_after(&self) -> Option { if self.pointer.wants_repaint() - || self.unprocessed_scroll_delta.abs().max_elem() > 0.2 - || self.unprocessed_scroll_delta_for_zoom.abs() > 0.2 + || self.wheel.unprocessed_wheel_delta.abs().max_elem() > 0.2 || !self.events.is_empty() { // Immediate repaint @@ -1646,15 +1529,9 @@ impl InputState { raw, pointer, touch_states, - - is_in_scroll_action: _, - last_scroll_time, - unprocessed_scroll_delta, - unprocessed_scroll_delta_for_zoom, - raw_scroll_delta, + wheel, smooth_scroll_delta, rotation_radians, - zoom_factor_delta, viewport_rect, safe_area_insets, @@ -1691,27 +1568,13 @@ impl InputState { }); } - ui.label(format!( - "is_scrolling: raw: {}, smooth: {}", - self.is_raw_scrolling(), - self.is_smooth_scrolling() - )); - ui.label(format!( - "Time since last scroll: {:.1} s", - time - last_scroll_time - )); - if cfg!(debug_assertions) { - ui.label(format!( - "unprocessed_scroll_delta: {unprocessed_scroll_delta:?} points" - )); - ui.label(format!( - "unprocessed_scroll_delta_for_zoom: {unprocessed_scroll_delta_for_zoom:?} points" - )); - } - ui.label(format!("raw_scroll_delta: {raw_scroll_delta:?} points")); - ui.label(format!( - "smooth_scroll_delta: {smooth_scroll_delta:?} points" - )); + crate::containers::CollapsingHeader::new("⬍ Scroll") + .default_open(false) + .show(ui, |ui| { + wheel.ui(ui); + }); + + ui.label(format!("smooth_scroll_delta: {smooth_scroll_delta:4.1}x")); ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x")); ui.label(format!("rotation_radians: {rotation_radians:.3} radians")); diff --git a/crates/egui/src/input_state/wheel_state.rs b/crates/egui/src/input_state/wheel_state.rs new file mode 100644 index 000000000..2efbbc1ff --- /dev/null +++ b/crates/egui/src/input_state/wheel_state.rs @@ -0,0 +1,233 @@ +use emath::{Rect, Vec2, vec2}; + +use crate::{InputOptions, Modifiers, MouseWheelUnit, TouchPhase}; + +/// The current state of scrolling. +/// +/// There are two important types of scroll input deviced: +/// * Discreen scroll wheels on a mouse +/// * Smooth scroll input from a trackpad +/// +/// Scroll wheels will usually fire one single scroll event, +/// so it is important that egui smooths it out over time. +/// +/// On the contrary, trackpads usually provide smooth scroll input, +/// and with kinetic scrolling (which on Mac is implemented by the OS) +/// scroll events can arrive _after_ the user lets go of the trackpad. +/// +/// In either case, we consider use to be scrolling until there is no more +/// scroll events expected. +/// +/// This means there are a few different states we can be in: +/// * Not scrolling +/// * "Smooth scrolling" (low-pass filter of discreet scroll events) +/// * Trackpad-scrolling (we receive begin/end phases for these) +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Status { + /// Not scrolling, + Static, + + /// We're smoothing out previous scroll events + Smoothing, + + // We're in-between [`TouchPhase::Start`] and [`TouchPhase::End`] of a trackpad scroll. + InTouch, +} + +/// Keeps track of wheel (scroll) input. +#[derive(Clone, Debug)] +pub struct WheelState { + /// Are we currently in a scroll action? + /// + /// This may be true even if no scroll events came in this frame, + /// but we are in a kinetic scroll or in a smoothed scroll. + pub status: Status, + + /// The modifiers at the start of the scroll. + pub modifiers: Modifiers, + + /// Time of the last scroll event. + pub last_wheel_event: f64, + + /// Used for smoothing the scroll delta. + pub unprocessed_wheel_delta: Vec2, + + /// How many points the user scrolled, smoothed over a few frames. + /// + /// The delta dictates how the _content_ should move. + /// + /// A positive X-value indicates the content is being moved right, + /// as when swiping right on a touch-screen or track-pad with natural scrolling. + /// + /// A positive Y-value indicates the content is being moved down, + /// as when swiping down on a touch-screen or track-pad with natural scrolling. + /// + /// [`crate::ScrollArea`] will both read and write to this field, so that + /// at the end of the frame this will be zero if a scroll-area consumed the delta. + pub smooth_wheel_delta: Vec2, +} + +impl Default for WheelState { + fn default() -> Self { + Self { + status: Status::Static, + modifiers: Default::default(), + last_wheel_event: f64::NEG_INFINITY, + unprocessed_wheel_delta: Vec2::ZERO, + smooth_wheel_delta: Vec2::ZERO, + } + } +} + +impl WheelState { + #[expect(clippy::too_many_arguments)] + pub fn on_wheel_event( + &mut self, + viewport_rect: Rect, + options: &InputOptions, + time: f64, + unit: MouseWheelUnit, + delta: Vec2, + phase: TouchPhase, + latest_modifiers: Modifiers, + ) { + self.last_wheel_event = time; + match phase { + crate::TouchPhase::Start => { + self.status = Status::InTouch; + self.modifiers = latest_modifiers; + } + crate::TouchPhase::Move => { + match self.status { + Status::Static | Status::Smoothing => { + self.modifiers = latest_modifiers; + self.status = Status::Smoothing; + } + Status::InTouch => { + // If the user lets go of a modifier - ignore it. + // More kinematic scrolling may arrive. + // But if the users presses down new modifiers - heed it! + self.modifiers |= latest_modifiers; + } + } + + let mut delta = match unit { + MouseWheelUnit::Point => delta, + MouseWheelUnit::Line => options.line_scroll_speed * delta, + MouseWheelUnit::Page => viewport_rect.height() * delta, + }; + + let is_horizontal = self + .modifiers + .matches_any(options.horizontal_scroll_modifier); + let is_vertical = self.modifiers.matches_any(options.vertical_scroll_modifier); + + if is_horizontal && !is_vertical { + // Treat all scrolling as horizontal scrolling. + // Note: one Mac we already get horizontal scroll events when shift is down. + delta = vec2(delta.x + delta.y, 0.0); + } + if !is_horizontal && is_vertical { + // Treat all scrolling as vertical scrolling. + delta = vec2(0.0, delta.x + delta.y); + } + + // Mouse wheels often go very large steps. + // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw scroll delta. + // So we smooth it out over several frames for a nicer user experience when scrolling in egui. + // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing, + // because it adds latency. + let is_smooth = self.status == Status::InTouch + || match unit { + MouseWheelUnit::Point => delta.length() < 8.0, // a bit arbitrary here + MouseWheelUnit::Line | MouseWheelUnit::Page => false, + }; + + if is_smooth { + self.smooth_wheel_delta += delta; + } else { + self.unprocessed_wheel_delta += delta; + } + } + crate::TouchPhase::End | crate::TouchPhase::Cancel => { + self.status = Status::Static; + self.modifiers = Default::default(); + self.unprocessed_wheel_delta = Default::default(); + self.smooth_wheel_delta = Default::default(); + } + } + } + + pub fn after_events(&mut self, time: f64, dt: f32) { + let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO(emilk): parameterize + + if self.unprocessed_wheel_delta != Vec2::ZERO { + for d in 0..2 { + if self.unprocessed_wheel_delta[d].abs() < 1.0 { + self.smooth_wheel_delta[d] += self.unprocessed_wheel_delta[d]; + self.unprocessed_wheel_delta[d] = 0.0; + } else { + let applied = t * self.unprocessed_wheel_delta[d]; + self.smooth_wheel_delta[d] += applied; + self.unprocessed_wheel_delta[d] -= applied; + } + } + } + + let time_since_last_scroll = time - self.last_wheel_event; + + if self.status == Status::Smoothing + && self.smooth_wheel_delta == Vec2::ZERO + && 0.150 < time_since_last_scroll + { + // On certain platforms, like web, we don't get the start & stop scrolling events, so + // we rely on a timer there. + // + // Tested on a mac touchpad 2025, where the largest observed gap between scroll events + // was 68 ms. But we add some margin to be safe + self.status = Status::Static; + self.modifiers = Default::default(); + } + } + + /// True if there is an active scroll action that might scroll more when using [`Self::smooth_wheel_delta`]. + pub fn is_scrolling(&self) -> bool { + self.status != Status::Static + } + + pub fn ui(&self, ui: &mut crate::Ui) { + let Self { + status, + modifiers, + last_wheel_event, + unprocessed_wheel_delta, + smooth_wheel_delta, + } = self; + + let time = ui.input(|i| i.time); + + crate::Grid::new("ScrollState") + .num_columns(2) + .show(ui, |ui| { + ui.label("status"); + ui.monospace(format!("{status:?}")); + ui.end_row(); + + ui.label("modifiers"); + ui.monospace(format!("{modifiers:?}")); + ui.end_row(); + + ui.label("last_wheel_event"); + ui.monospace(format!("{:.1}s ago", time - *last_wheel_event)); + ui.end_row(); + + ui.label("unprocessed_wheel_delta"); + ui.monospace(unprocessed_wheel_delta.to_string()); + ui.end_row(); + + ui.label("smooth_wheel_delta"); + ui.monospace(smooth_wheel_delta.to_string()); + ui.end_row(); + }); + } +} From 706ce10abdbeab1cf1a27f9f680cd7340301aa2d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 3 Nov 2025 18:56:18 +0100 Subject: [PATCH 294/388] Fix edge cases in "smart aiming" in sliders (#7680) When dragging slider, we try to pick nice, round values. There were a couple edge cases there that were handled wrong. This is now fixed. --- crates/emath/src/smart_aim.rs | 158 +++++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 43 deletions(-) diff --git a/crates/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index ebcd68321..c1b96ec7b 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -31,6 +31,8 @@ pub fn best_in_range_f64(min: f64, max: f64) -> f64 { return -best_in_range_f64(-max, -min); } + debug_assert!(0.0 < min && min < max, "Logic bug"); + // Prefer finite numbers: if !max.is_finite() { return min; @@ -44,7 +46,8 @@ pub fn best_in_range_f64(min: f64, max: f64) -> f64 { let max_exponent = max.log10(); if min_exponent.floor() != max_exponent.floor() { - // pick the geometric center of the two: + // Different orders of magnitude. + // Pick the geometric center of the two: let exponent = fast_midpoint(min_exponent, max_exponent); return 10.0_f64.powi(exponent.round() as i32); } @@ -56,65 +59,85 @@ pub fn best_in_range_f64(min: f64, max: f64) -> f64 { return 10.0_f64.powf(max_exponent); } - let exp_factor = 10.0_f64.powi(max_exponent.floor() as i32); + // Find the proper scale, and then convert to integers: - let min_str = to_decimal_string(min / exp_factor); - let max_str = to_decimal_string(max / exp_factor); + let scale = NUM_DECIMALS as i32 - max_exponent.floor() as i32 - 1; + let scale_factor = 10.0_f64.powi(scale); + + let min_str = to_decimal_string((min * scale_factor).round() as u64); + let max_str = to_decimal_string((max * scale_factor).round() as u64); + + // We now have two positive integers of the same length. + // We want to find the first non-matching digit, + // which we will call the "deciding digit". + // Everything before it will be the same, + // everything after will be zero, + // and the deciding digit itself will be picked as a "smart average" + // min: 12345 + // max: 12780 + // output: 12500 let mut ret_str = [0; NUM_DECIMALS]; - // Select the common prefix: - let mut i = 0; - while i < NUM_DECIMALS && max_str[i] == min_str[i] { - ret_str[i] = max_str[i]; - i += 1; + for i in 0..NUM_DECIMALS { + if min_str[i] == max_str[i] { + ret_str[i] = min_str[i]; + } else { + // Found the deciding digit at index `i` + let mut deciding_digit_min = min_str[i]; + let deciding_digit_max = max_str[i]; + + debug_assert!( + deciding_digit_min < deciding_digit_max, + "Bug in smart aim code" + ); + + let rest_of_min_is_zeroes = min_str[i + 1..].iter().all(|&c| c == 0); + + if !rest_of_min_is_zeroes { + // There are more digits coming after `deciding_digit_min`, so we cannot pick it. + // So the true min of what we can pick is one greater: + deciding_digit_min += 1; + } + + let deciding_digit = if deciding_digit_min == 0 { + 0 + } else if deciding_digit_min <= 5 && 5 <= deciding_digit_max { + 5 // 5 is the roundest number in the range + } else { + deciding_digit_min.midpoint(deciding_digit_max) + }; + + ret_str[i] = deciding_digit; + + return from_decimal_string(ret_str) as f64 / scale_factor; + } } - if i < NUM_DECIMALS { - // Pick the deciding digit. - // Note that "to_decimal_string" rounds down, so we that's why we add 1 here - ret_str[i] = simplest_digit_closed_range(min_str[i] + 1, max_str[i]); - } - - from_decimal_string(&ret_str) * exp_factor + min // All digits are the same. Already handled earlier, but better safe than sorry } fn is_integer(f: f64) -> bool { f.round() == f } -fn to_decimal_string(v: f64) -> [i32; NUM_DECIMALS] { - debug_assert!(v < 10.0, "{v:?}"); - let mut digits = [0; NUM_DECIMALS]; - let mut v = v.abs(); - for r in &mut digits { - let digit = v.floor(); - *r = digit as i32; - v -= digit; - v *= 10.0; - } - digits -} - -fn from_decimal_string(s: &[i32]) -> f64 { - let mut ret: f64 = 0.0; - for (i, &digit) in s.iter().enumerate() { - ret += (digit as f64) * 10.0_f64.powi(-(i as i32)); +fn to_decimal_string(v: u64) -> [u8; NUM_DECIMALS] { + let mut ret = [0; NUM_DECIMALS]; + let mut value = v; + for i in (0..NUM_DECIMALS).rev() { + ret[i] = (value % 10) as u8; + value /= 10; } ret } -/// Find the simplest integer in the range [min, max] -fn simplest_digit_closed_range(min: i32, max: i32) -> i32 { - debug_assert!( - 1 <= min && min <= max && max <= 9, - "min should be in [1, 9], but was {min:?} and max should be in [min, 9], but was {max:?}" - ); - if min <= 5 && 5 <= max { - 5 - } else { - min.midpoint(max) +fn from_decimal_string(s: [u8; NUM_DECIMALS]) -> u64 { + let mut value = 0; + for &c in &s { + debug_assert!(c <= 9, "Bad number"); + value = value * 10 + c as u64; } + value } #[expect(clippy::approx_constant)] @@ -161,4 +184,53 @@ fn test_aim() { assert_eq!(best_in_range_f64(NEG_INFINITY, NEG_INFINITY), NEG_INFINITY); assert_eq!(best_in_range_f64(NEG_INFINITY, INFINITY), 0.0); assert_eq!(best_in_range_f64(INFINITY, NEG_INFINITY), 0.0); + + #[track_caller] + fn test_f64((min, max): (f64, f64), expected: f64) { + let aimed = best_in_range_f64(min, max); + assert!( + aimed == expected, + "smart_aim({min} – {max}) => {aimed}, but expected {expected}" + ); + } + #[track_caller] + fn test_i64((min, max): (i64, i64), expected: i64) { + let aimed = best_in_range_f64(min as _, max as _); + assert!( + aimed == expected as f64, + "smart_aim({min} – {max}) => {aimed}, but expected {expected}" + ); + } + + test_i64((99, 300), 100); + test_i64((300, 99), 100); + test_i64((-99, -300), -100); + test_i64((-99, 123), 0); // Prefer zero + test_i64((4, 9), 5); // Prefer ending on 5 + test_i64((14, 19), 15); // Prefer ending on 5 + test_i64((12, 65), 50); // Prefer leading 5 + test_i64((493, 879), 500); // Prefer leading 5 + test_i64((37, 48), 40); + test_i64((100, 123), 100); + test_i64((101, 1000), 1000); + test_i64((999, 1000), 1000); + test_i64((123, 500), 500); + test_i64((500, 777), 500); + test_i64((500, 999), 500); + test_i64((12345, 12780), 12500); + test_i64((12371, 12376), 12375); + test_i64((12371, 12376), 12375); + + test_f64((7.5, 16.3), 10.0); + test_f64((7.5, 76.3), 10.0); + test_f64((7.5, 763.3), 100.0); + test_f64((7.5, 1_345.0), 100.0); // Geometric mean + test_f64((7.5, 123_456.0), 1_000.0); // Geometric mean + test_f64((-0.2, 0.0), 0.0); // Prefer zero + test_f64((-10_004.23, 4.14), 0.0); // Prefer zero + test_f64((-0.2, 100.0), 0.0); // Prefer zero + test_f64((0.2, 0.0), 0.0); // Prefer zero + test_f64((7.8, 17.8), 10.0); + test_f64((14.1, 19.1), 15.0); // Prefer ending on 5 + test_f64((12.3, 65.9), 50.0); // Prefer leading 5 } From 31d313572b798d1924329cc074f0f22075fca712 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Nov 2025 10:35:57 +0100 Subject: [PATCH 295/388] Make sure `native_pixels_per_point` is set during app creation (#7683) Useful for things like analytics --- crates/eframe/src/native/glow_integration.rs | 18 ++++++++++++++--- crates/eframe/src/native/wgpu_integration.rs | 21 ++++++++++++++++---- crates/eframe/src/web/app_runner.rs | 8 ++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 4a3bee46a..b42674052 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -1041,11 +1041,23 @@ impl GlutinWindowContext { let mut viewport_from_window = HashMap::default(); let mut window_from_viewport = OrderedViewportIdMap::default(); - let mut info = ViewportInfo::default(); + let mut viewport_info = ViewportInfo::default(); if let Some(window) = &window { viewport_from_window.insert(window.id(), ViewportId::ROOT); window_from_viewport.insert(ViewportId::ROOT, window.id()); - egui_winit::update_viewport_info(&mut info, egui_ctx, window, true); + egui_winit::update_viewport_info(&mut viewport_info, egui_ctx, window, true); + + // Tell egui right away about native_pixels_per_point etc, + // so that the app knows about it during app creation: + let pixels_per_point = egui_winit::pixels_per_point(egui_ctx, window); + + egui_ctx.input_mut(|i| { + i.raw + .viewports + .insert(ViewportId::ROOT, viewport_info.clone()); + + i.pixels_per_point = pixels_per_point; + }); } let mut viewports = OrderedViewportIdMap::default(); @@ -1056,7 +1068,7 @@ impl GlutinWindowContext { class: ViewportClass::Root, builder: viewport_builder, deferred_commands: vec![], - info, + info: viewport_info, actions_requested: Default::default(), viewport_ui_cb: None, gl_surface: None, diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index b21b62aca..c6c715c8c 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -199,6 +199,22 @@ impl<'app> WgpuWinitApp<'app> { }, )); + let mut viewport_info = ViewportInfo::default(); + egui_winit::update_viewport_info(&mut viewport_info, &egui_ctx, &window, true); + + { + // Tell egui right away about native_pixels_per_point etc, + // so that the app knows about it during app creation: + let pixels_per_point = egui_winit::pixels_per_point(&egui_ctx, &window); + + egui_ctx.input_mut(|i| { + i.raw + .viewports + .insert(ViewportId::ROOT, viewport_info.clone()); + i.pixels_per_point = pixels_per_point; + }); + } + let window = Arc::new(window); { @@ -278,9 +294,6 @@ impl<'app> WgpuWinitApp<'app> { let mut viewport_from_window = HashMap::default(); viewport_from_window.insert(window.id(), ViewportId::ROOT); - let mut info = ViewportInfo::default(); - egui_winit::update_viewport_info(&mut info, &egui_ctx, &window, true); - let mut viewports = Viewports::default(); viewports.insert( ViewportId::ROOT, @@ -289,7 +302,7 @@ impl<'app> WgpuWinitApp<'app> { class: ViewportClass::Root, builder, deferred_commands: vec![], - info, + info: viewport_info, actions_requested: Default::default(), viewport_ui_cb: None, window: Some(window), diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index bd245a1fe..4a97235aa 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -65,6 +65,14 @@ impl AppRunner { o.zoom_factor = 1.0; }); + // Tell egui right away about native_pixels_per_point + // so that the app knows about it during app creation: + egui_ctx.input_mut(|i| { + let viewport_info = i.raw.viewports.entry(egui::ViewportId::ROOT).or_default(); + viewport_info.native_pixels_per_point = Some(super::native_pixels_per_point()); + i.pixels_per_point = super::native_pixels_per_point(); + }); + let cc = epi::CreationContext { egui_ctx: egui_ctx.clone(), integration_info: info.clone(), From 1e63bfd65798efda95d88b874586f0f514242a9c Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Fri, 7 Nov 2025 13:34:18 +0100 Subject: [PATCH 296/388] Improve accessibility and testability of `ComboBox` (#7658) Changed it to use labeled_by to avoid kittest finding the label when searching for the ComboBox and also set the value so a screen reader will know what's selected. --- crates/egui/src/containers/combo_box.rs | 18 ++++++++++-------- tests/egui_tests/tests/regression_tests.rs | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 8195024fb..c4097f803 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -239,7 +239,7 @@ impl ComboBox { let mut ir = combo_box_dyn( ui, button_id, - selected_text, + selected_text.clone(), menu_contents, icon, wrap_mode, @@ -247,14 +247,16 @@ impl ComboBox { popup_style, (width, height), ); + ir.response.widget_info(|| { + let mut info = WidgetInfo::new(WidgetType::ComboBox); + info.enabled = ui.is_enabled(); + info.current_text_value = Some(selected_text.text().to_owned()); + info + }); if let Some(label) = label { - ir.response.widget_info(|| { - WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), label.text()) - }); - ir.response |= ui.label(label); - } else { - ir.response - .widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), "")); + let label_response = ui.label(label); + ir.response = ir.response.labelled_by(label_response.id); + ir.response |= label_response; } ir }) diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs index a407864e7..1ee197cb5 100644 --- a/tests/egui_tests/tests/regression_tests.rs +++ b/tests/egui_tests/tests/regression_tests.rs @@ -61,3 +61,17 @@ fn text_edit_rtl() { harness.snapshot(format!("text_edit_rtl_{i}")); } } + +#[test] +fn combobox_should_have_value() { + let harness = Harness::new_ui(|ui| { + egui::ComboBox::from_label("Select an option") + .selected_text("Option 1") + .show_ui(ui, |_ui| {}); + }); + + assert_eq!( + harness.get_by_label("Select an option").value().as_deref(), + Some("Option 1") + ); +} From 04913ed651203906622c15588166bb648ab4f1fb Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Fri, 7 Nov 2025 13:34:25 +0100 Subject: [PATCH 297/388] Add some more text edit tests (#7608) Adds tests to text the clip option in text edits and how it behaves with a placeholder --------- Co-authored-by: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com> --- .../tests/snapshots/layout/text_edit_clip.png | 3 ++ .../snapshots/layout/text_edit_no_clip.png | 3 ++ .../layout/text_edit_placeholder_clip.png | 3 ++ .../snapshots/visuals/text_edit_clip.png | 3 ++ .../snapshots/visuals/text_edit_no_clip.png | 3 ++ .../visuals/text_edit_placeholder_clip.png | 3 ++ tests/egui_tests/tests/test_widgets.rs | 33 ++++++++++++++++++- 7 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/egui_tests/tests/snapshots/layout/text_edit_clip.png create mode 100644 tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png create mode 100644 tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png create mode 100644 tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_clip.png b/tests/egui_tests/tests/snapshots/layout/text_edit_clip.png new file mode 100644 index 000000000..0c4327b58 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_clip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f107d95fee9a5fb5fbfd2422452e1820738a84c81774587dbfa8153e91e4c73 +size 414552 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png b/tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png new file mode 100644 index 000000000..ecc6efa8b --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c1aebada9349f8cb4046469b0a6f9796a21f88b6724bd85cd832a40b8007409 +size 540527 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png b/tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png new file mode 100644 index 000000000..780fec82f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:685de2e33ff26aafa87426bcda18bb9963c2deb2a811cd0aae4450af0e245a06 +size 390735 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png new file mode 100644 index 000000000..f44900fa5 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf4236b1a8f63d184cd780c334d9f996e4d47817a96a29f0d81658d2d897597f +size 10529 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png new file mode 100644 index 000000000..7329c49cf --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7a63953853f526b83f80d63335b03e60258ea9a3416d19f8ed57d746b5c551d +size 21557 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png new file mode 100644 index 000000000..e1a15cf7d --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f7d802a4de7e30f8d254cab6d9ca127866c104c1738103bc4a579917e8f42d3 +size 9850 diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs index 6a75e36a3..440b1939b 100644 --- a/tests/egui_tests/tests/test_widgets.rs +++ b/tests/egui_tests/tests/test_widgets.rs @@ -2,7 +2,7 @@ use egui::accesskit::Role; use egui::load::SizedTexture; use egui::{ Align, AtomExt as _, AtomLayout, Button, Color32, ColorImage, Direction, DragValue, Event, - Grid, IntoAtoms as _, Layout, PointerButton, Response, Slider, Stroke, StrokeKind, + Grid, IntoAtoms as _, Layout, PointerButton, Response, Slider, Stroke, StrokeKind, TextEdit, TextWrapMode, TextureHandle, TextureOptions, Ui, UiBuilder, Vec2, Widget as _, include_image, }; use egui_kittest::kittest::{Queryable as _, by}; @@ -84,6 +84,37 @@ fn widget_tests() { }, &mut results, ); + test_widget( + "text_edit_clip", + |ui| { + ui.spacing_mut().text_edit_width = 45.0; + TextEdit::singleline(&mut "This is a very very long text".to_owned()) + .clip_text(true) + .ui(ui) + }, + &mut results, + ); + test_widget( + "text_edit_no_clip", + |ui| { + ui.spacing_mut().text_edit_width = 45.0; + TextEdit::singleline(&mut "This is a very very long text".to_owned()) + .clip_text(false) + .ui(ui) + }, + &mut results, + ); + test_widget( + "text_edit_placeholder_clip", + |ui| { + ui.spacing_mut().text_edit_width = 45.0; + TextEdit::singleline(&mut String::new()) + .hint_text("This is a very very long placeholder") + .clip_text(true) + .ui(ui) + }, + &mut results, + ); test_widget( "slider", From 1d4d14f18ee402e6eb333cf9c2ddaf2453aaed69 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 7 Nov 2025 14:43:49 +0100 Subject: [PATCH 298/388] Hide scroll bars when dragging other things (#7689) This closes a small visual glitch where scroll bars would show up when dragging something unrelated, like a slider or a panel side. --- crates/egui/src/containers/scroll_area.rs | 131 ++++++++++++---------- 1 file changed, 74 insertions(+), 57 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index db64ba03b..d63a2ab59 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::{ - Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind, - UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, + Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, + UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, }; #[derive(Clone, Copy, Debug)] @@ -659,6 +659,9 @@ struct Prepared { /// not for us to handle so we save it and restore it after this [`ScrollArea`] is done. saved_scroll_target: [Option; 2], + /// The response from dragging the background (if enabled) + background_drag_response: Option, + animated: bool, } @@ -772,70 +775,72 @@ impl ScrollArea { let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size); let dt = ui.input(|i| i.stable_dt).at_most(0.1); - if scroll_source.drag - && ui.is_enabled() - && (state.content_is_too_large[0] || state.content_is_too_large[1]) - { - // Drag contents to scroll (for touch screens mostly). - // We must do this BEFORE adding content to the `ScrollArea`, - // or we will steal input from the widgets we contain. - let content_response_option = state - .interact_rect - .map(|rect| ui.interact(rect, id.with("area"), Sense::drag())); + let background_drag_response = + if scroll_source.drag && ui.is_enabled() && state.content_is_too_large.any() { + // Drag contents to scroll (for touch screens mostly). + // We must do this BEFORE adding content to the `ScrollArea`, + // or we will steal input from the widgets we contain. + let content_response_option = state + .interact_rect + .map(|rect| ui.interact(rect, id.with("area"), Sense::drag())); - if content_response_option - .as_ref() - .is_some_and(|response| response.dragged()) - { - for d in 0..2 { - if direction_enabled[d] { - ui.input(|input| { - state.offset[d] -= input.pointer.delta()[d]; - }); - state.scroll_stuck_to_end[d] = false; - state.offset_target[d] = None; - } - } - } else { - // Apply the cursor velocity to the scroll area when the user releases the drag. if content_response_option .as_ref() - .is_some_and(|response| response.drag_stopped()) + .is_some_and(|response| response.dragged()) { - state.vel = - direction_enabled.to_vec2() * ui.input(|input| input.pointer.velocity()); - } - for d in 0..2 { - // Kinetic scrolling - let stop_speed = 20.0; // Pixels per second. - let friction_coeff = 1000.0; // Pixels per second squared. + for d in 0..2 { + if direction_enabled[d] { + ui.input(|input| { + state.offset[d] -= input.pointer.delta()[d]; + }); + state.scroll_stuck_to_end[d] = false; + state.offset_target[d] = None; + } + } + } else { + // Apply the cursor velocity to the scroll area when the user releases the drag. + if content_response_option + .as_ref() + .is_some_and(|response| response.drag_stopped()) + { + state.vel = direction_enabled.to_vec2() + * ui.input(|input| input.pointer.velocity()); + } + for d in 0..2 { + // Kinetic scrolling + let stop_speed = 20.0; // Pixels per second. + let friction_coeff = 1000.0; // Pixels per second squared. - let friction = friction_coeff * dt; - if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed { - state.vel[d] = 0.0; - } else { - state.vel[d] -= friction * state.vel[d].signum(); - // Offset has an inverted coordinate system compared to - // the velocity, so we subtract it instead of adding it - state.offset[d] -= state.vel[d] * dt; - ctx.request_repaint(); + let friction = friction_coeff * dt; + if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed { + state.vel[d] = 0.0; + } else { + state.vel[d] -= friction * state.vel[d].signum(); + // Offset has an inverted coordinate system compared to + // the velocity, so we subtract it instead of adding it + state.offset[d] -= state.vel[d] * dt; + ctx.request_repaint(); + } } } - } - // Set the desired mouse cursors. - if let Some(response) = content_response_option { - if response.dragged() { - if let Some(cursor) = on_drag_cursor { - response.on_hover_cursor(cursor); + // Set the desired mouse cursors. + if let Some(response) = &content_response_option { + if response.dragged() + && let Some(cursor) = on_drag_cursor + { + ui.ctx().set_cursor_icon(cursor); + } else if response.hovered() + && let Some(cursor) = on_hover_cursor + { + ui.ctx().set_cursor_icon(cursor); } - } else if response.hovered() - && let Some(cursor) = on_hover_cursor - { - response.on_hover_cursor(cursor); } - } - } + + content_response_option + } else { + None + }; // Scroll with an animation if we have a target offset (that hasn't been cleared by the code // above). @@ -888,6 +893,7 @@ impl ScrollArea { wheel_scroll_multiplier, stick_to_end, saved_scroll_target, + background_drag_response, animated, } } @@ -1003,6 +1009,7 @@ impl Prepared { wheel_scroll_multiplier, stick_to_end, saved_scroll_target, + background_drag_response, animated, } = self; @@ -1118,7 +1125,16 @@ impl Prepared { ); let max_offset = content_size - inner_rect.size(); - let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); + + // Drag-to-scroll? + let is_dragging_background = background_drag_response + .as_ref() + .is_some_and(|r| r.dragged()); + + let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect) + && ui.ctx().dragged_id().is_none() + || is_dragging_background; + if scroll_source.mouse_wheel && ui.is_enabled() && is_hovering_outer_rect { let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction && direction_enabled[0] != direction_enabled[1]; @@ -1204,6 +1220,7 @@ impl Prepared { let is_hovering_bar_area = is_hovering_outer_rect && ui.rect_contains_pointer(max_bar_rect) + && !is_dragging_background || state.scroll_bar_interaction[d]; let is_hovering_bar_area_t = ui From d8dcb316739dbaa520d0b8ed11788d37ee888fa3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 7 Nov 2025 14:46:09 +0100 Subject: [PATCH 299/388] `kittest`: add drag-and-drop helpers (#7690) --- crates/egui_kittest/src/lib.rs | 57 ++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index c8112f47b..33a188ea4 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -8,9 +8,7 @@ mod builder; mod snapshot; #[cfg(feature = "snapshot")] -pub use snapshot::*; -use std::fmt::{Debug, Display, Formatter}; -use std::time::Duration; +pub use crate::snapshot::*; mod app_kind; mod node; @@ -20,19 +18,26 @@ mod texture_to_image; #[cfg(feature = "wgpu")] pub mod wgpu; -pub use kittest; +// re-exports: +pub use { + self::{builder::*, node::*, renderer::*}, + kittest, +}; + +use std::{ + fmt::{Debug, Display, Formatter}, + time::Duration, +}; + +use egui::{ + Color32, Key, Modifiers, PointerButton, Pos2, Rect, RepaintCause, Shape, Vec2, ViewportId, + epaint::{ClippedShape, RectShape}, + style::ScrollAnimation, +}; +use kittest::Queryable; use crate::app_kind::AppKind; -pub use builder::*; -pub use node::*; -pub use renderer::*; - -use egui::epaint::{ClippedShape, RectShape}; -use egui::style::ScrollAnimation; -use egui::{Color32, Key, Modifiers, Pos2, Rect, RepaintCause, Shape, Vec2, ViewportId}; -use kittest::Queryable; - #[derive(Debug, Clone)] pub struct ExceededMaxStepsError { pub max_steps: u64, @@ -598,6 +603,32 @@ impl<'a, State> Harness<'a, State> { self.key_combination_modifiers(modifiers, &[key]); } + /// Move mouse cursor to this position. + pub fn hover_at(&self, pos: egui::Pos2) { + self.event(egui::Event::PointerMoved(pos)); + } + + /// Start dragging from a position. + pub fn drag_at(&self, pos: egui::Pos2) { + self.event(egui::Event::PointerButton { + pos, + button: PointerButton::Primary, + pressed: true, + modifiers: Modifiers::NONE, + }); + } + + /// Stop dragging and remove cursor. + pub fn drop_at(&self, pos: egui::Pos2) { + self.event(egui::Event::PointerButton { + pos, + button: PointerButton::Primary, + pressed: false, + modifiers: Modifiers::NONE, + }); + self.remove_cursor(); + } + /// Remove the cursor from the screen. /// /// Will fire a [`egui::Event::PointerGone`] event. From fa4cfec777c9ea21b8643f77b6a26561da44aad0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 7 Nov 2025 15:34:36 +0100 Subject: [PATCH 300/388] Change text color of selected text (#7691) Selected text now gets the color of `visuals.selection.stroke.color`. This means you can have inverted colors for selected text, like in the new test: image It also means the color of selected text in labels matches that of the text color of selected buttons. --- crates/egui/src/style.rs | 3 ++ crates/egui/src/text_selection/visuals.rs | 31 +++++++++++++++++-- crates/egui_demo_lib/tests/misc.rs | 26 ++++++++++++++-- .../image_dark_x1.00.png | 0 .../image_dark_x1.41.png | 0 .../image_dark_x2.00.png | 0 .../image_light_x1.00.png | 0 .../image_light_x1.41.png | 0 .../image_light_x2.00.png | 0 .../tests/snapshots/text_selection.png | 3 ++ crates/emath/src/rect.rs | 3 +- crates/epaint/src/text/text_layout.rs | 9 ++++-- crates/epaint/src/text/text_layout_types.rs | 3 ++ 13 files changed, 70 insertions(+), 8 deletions(-) rename crates/egui_demo_lib/tests/snapshots/{image_blending => italics}/image_dark_x1.00.png (100%) rename crates/egui_demo_lib/tests/snapshots/{image_blending => italics}/image_dark_x1.41.png (100%) rename crates/egui_demo_lib/tests/snapshots/{image_blending => italics}/image_dark_x2.00.png (100%) rename crates/egui_demo_lib/tests/snapshots/{image_blending => italics}/image_light_x1.00.png (100%) rename crates/egui_demo_lib/tests/snapshots/{image_blending => italics}/image_light_x1.41.png (100%) rename crates/egui_demo_lib/tests/snapshots/{image_blending => italics}/image_light_x2.00.png (100%) create mode 100644 crates/egui_demo_lib/tests/snapshots/text_selection.png diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 454fc6d89..9982c05bb 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1129,7 +1129,10 @@ impl Visuals { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct Selection { + /// Background color behind selected text and other selectable buttons. pub bg_fill: Color32, + + /// Color of selected text. pub stroke: Stroke, } diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index 0f6d54abd..50bb1a34d 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -25,7 +25,9 @@ pub fn paint_text_selection( // and so we need to clone it if it is shared: let galley: &mut Galley = Arc::make_mut(galley); - let color = visuals.selection.bg_fill; + let background_color = visuals.selection.bg_fill; + let text_color = visuals.selection.stroke.color; + let [min, max] = cursor_range.sorted_cursors(); let min = galley.layout_from_cursor(min); let max = galley.layout_from_cursor(max); @@ -53,6 +55,31 @@ pub fn paint_text_selection( let rect = Rect::from_min_max(pos2(left, 0.0), pos2(right, row.size.y)); let mesh = &mut row.visuals.mesh; + if !row.glyphs.is_empty() { + // Change color of the selected text: + let first_glyph_index = if ri == min.row { min.column } else { 0 }; + let last_glyph_index = if ri == max.row { + max.column + } else { + row.glyphs.len() - 1 + }; + + let first_vertex_index = row + .glyphs + .get(first_glyph_index) + .map_or(row.visuals.glyph_vertex_range.start, |g| { + g.first_vertex as _ + }); + let last_vertex_index = row + .glyphs + .get(last_glyph_index) + .map_or(row.visuals.glyph_vertex_range.end, |g| g.first_vertex as _); + + for vi in first_vertex_index..last_vertex_index { + mesh.vertices[vi].color = text_color; + } + } + // Time to insert the selection rectangle into the row mesh. // It should be on top (after) of any background in the galley, // but behind (before) any glyphs. The row visuals has this information: @@ -60,7 +87,7 @@ pub fn paint_text_selection( // Start by appending the selection rectangle to end of the mesh, as two triangles (= 6 indices): let num_indices_before = mesh.indices.len(); - mesh.add_colored_rect(rect, color); + mesh.add_colored_rect(rect, background_color); assert_eq!( num_indices_before + 6, mesh.indices.len(), diff --git a/crates/egui_demo_lib/tests/misc.rs b/crates/egui_demo_lib/tests/misc.rs index 395baceb6..af8858bca 100644 --- a/crates/egui_demo_lib/tests/misc.rs +++ b/crates/egui_demo_lib/tests/misc.rs @@ -1,4 +1,5 @@ -use egui_kittest::Harness; +use egui::{Color32, accesskit::Role}; +use egui_kittest::{Harness, kittest::Queryable as _}; #[test] fn test_kerning() { @@ -42,7 +43,7 @@ fn test_italics() { harness.run(); harness.fit_contents(); harness.snapshot(format!( - "image_blending/image_{theme}_x{pixels_per_point:.2}", + "italics/image_{theme}_x{pixels_per_point:.2}", theme = match theme { egui::Theme::Dark => "dark", egui::Theme::Light => "light", @@ -51,3 +52,24 @@ fn test_italics() { } } } + +#[test] +fn test_text_selection() { + let mut harness = Harness::builder().build_ui(|ui| { + let visuals = ui.visuals_mut(); + visuals.selection.bg_fill = Color32::LIGHT_GREEN; + visuals.selection.stroke.color = Color32::DARK_BLUE; + + ui.label("Some varied ☺ text :)\nAnd it has a second line!"); + }); + harness.run(); + harness.fit_contents(); + + // Drag to select text: + let label = harness.get_by_role(Role::Label); + harness.drag_at(label.rect().lerp_inside([0.2, 0.25])); + harness.drop_at(label.rect().lerp_inside([0.6, 0.75])); + harness.run(); + + harness.snapshot("text_selection"); +} diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.00.png similarity index 100% rename from crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.00.png rename to crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.00.png diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.41.png b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.41.png similarity index 100% rename from crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x1.41.png rename to crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.41.png diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x2.00.png similarity index 100% rename from crates/egui_demo_lib/tests/snapshots/image_blending/image_dark_x2.00.png rename to crates/egui_demo_lib/tests/snapshots/italics/image_dark_x2.00.png diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.00.png similarity index 100% rename from crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.00.png rename to crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.00.png diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.41.png b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.41.png similarity index 100% rename from crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x1.41.png rename to crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.41.png diff --git a/crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x2.00.png similarity index 100% rename from crates/egui_demo_lib/tests/snapshots/image_blending/image_light_x2.00.png rename to crates/egui_demo_lib/tests/snapshots/italics/image_light_x2.00.png diff --git a/crates/egui_demo_lib/tests/snapshots/text_selection.png b/crates/egui_demo_lib/tests/snapshots/text_selection.png new file mode 100644 index 000000000..78ebc0dbf --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/text_selection.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14f253fedc94985ff1431f1016d901d747e1f9948531cc6350f6615649f29056 +size 4862 diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index b46fc43ca..81729713b 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -449,7 +449,8 @@ impl Rect { /// Linearly interpolate so that `[0, 0]` is [`Self::min`] and /// `[1, 1]` is [`Self::max`]. #[inline] - pub fn lerp_inside(&self, t: Vec2) -> Pos2 { + pub fn lerp_inside(&self, t: impl Into) -> Pos2 { + let t = t.into(); Pos2 { x: lerp(self.min.x..=self.max.x, t.x), y: lerp(self.min.y..=self.max.y, t.y), diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index b1fe895da..1db56731d 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -230,6 +230,7 @@ fn layout_section( font_ascent: font_metrics.ascent, uv_rect: glyph_alloc.uv_rect, section_index, + first_vertex: 0, // filled in later }); paragraph.cursor_x_px += glyph_alloc.advance_width_px; @@ -531,6 +532,7 @@ fn replace_last_glyph_with_overflow_character( font_ascent: font_metrics.ascent, uv_rect: replacement_glyph_alloc.uv_rect, section_index, + first_vertex: 0, // filled in later }); return; } @@ -748,7 +750,7 @@ fn tessellate_row( point_scale: PointScale, job: &LayoutJob, format_summary: &FormatSummary, - row: &Row, + row: &mut Row, ) -> RowVisuals { if row.glyphs.is_empty() { return Default::default(); @@ -843,8 +845,9 @@ fn add_row_backgrounds(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh end_run(run_start.take(), last_rect.right()); } -fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh: &mut Mesh) { - for glyph in &row.glyphs { +fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &mut Row, mesh: &mut Mesh) { + for glyph in &mut row.glyphs { + glyph.first_vertex = mesh.vertices.len() as u32; let uv_rect = glyph.uv_rect; if !uv_rect.is_nothing() { let mut left_top = glyph.pos + uv_rect.offset; diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index d87f9a579..f3963394a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -701,6 +701,9 @@ pub struct Glyph { /// enable the paragraph-concat optimization path without having to /// adjust `section_index` when concatting. pub(crate) section_index: u32, + + /// Which is our first vertex in [`RowVisuals::mesh`]. + pub first_vertex: u32, } impl Glyph { From 93425ae06b8ea3c4f8f8cc9dc01851c37876495b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 10 Nov 2025 16:34:58 +0100 Subject: [PATCH 301/388] Allow multiple atoms in `Button::shortcut_text` and `right_text` (#7696) Useful when intermixing text and icons (e.g. for modifiers) --- crates/egui/src/widgets/button.rs | 25 ++++++++++++------- .../tests/snapshots/button_shortcut.png | 3 +++ tests/egui_tests/tests/test_atoms.rs | 11 ++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 tests/egui_tests/tests/snapshots/button_shortcut.png diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index af31b40af..ccb1db69f 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -223,22 +223,29 @@ impl<'a> Button<'a> { /// /// See also [`Self::right_text`]. #[inline] - pub fn shortcut_text(mut self, shortcut_text: impl Into>) -> Self { - let mut atom = shortcut_text.into(); - atom.kind = match atom.kind { - AtomKind::Text(text) => AtomKind::Text(text.weak()), - other => other, - }; + pub fn shortcut_text(mut self, shortcut_text: impl IntoAtoms<'a>) -> Self { self.layout.push_right(Atom::grow()); - self.layout.push_right(atom); + + for mut atom in shortcut_text.into_atoms() { + atom.kind = match atom.kind { + AtomKind::Text(text) => AtomKind::Text(text.weak()), + other => other, + }; + self.layout.push_right(atom); + } + self } /// Show some text on the right side of the button. #[inline] - pub fn right_text(mut self, right_text: impl Into>) -> Self { + pub fn right_text(mut self, right_text: impl IntoAtoms<'a>) -> Self { self.layout.push_right(Atom::grow()); - self.layout.push_right(right_text.into()); + + for atom in right_text.into_atoms() { + self.layout.push_right(atom); + } + self } diff --git a/tests/egui_tests/tests/snapshots/button_shortcut.png b/tests/egui_tests/tests/snapshots/button_shortcut.png new file mode 100644 index 000000000..7f39196b8 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/button_shortcut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5befd84158b582c79a968f36e43c7017187b364824eb4470b048d133e62f9360 +size 1600 diff --git a/tests/egui_tests/tests/test_atoms.rs b/tests/egui_tests/tests/test_atoms.rs index cf2abbe1a..6f4b694e6 100644 --- a/tests/egui_tests/tests/test_atoms.rs +++ b/tests/egui_tests/tests/test_atoms.rs @@ -108,3 +108,14 @@ fn test_intrinsic_size() { } } } + +#[test] +fn test_button_shortcut_text() { + let mut harness = HarnessBuilder::default().build_ui(|ui| { + ui.add(egui::Button::new("Click me").shortcut_text(("1", "2", "3"))); + }); + harness.run(); + harness.fit_contents(); + + harness.snapshot("button_shortcut"); +} From 6b79845431ba1f4e2643cd9bba6b9b52c7b3a65e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 10 Nov 2025 21:49:31 +0100 Subject: [PATCH 302/388] Turn `HarnessBuilder::with_options` into a proper builder method (#7697) My bad when first creating it --- crates/egui_kittest/src/builder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index cc686914f..09b91d26d 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -4,6 +4,7 @@ use egui::{Pos2, Rect, Vec2}; use std::marker::PhantomData; /// Builder for [`Harness`]. +#[must_use] pub struct HarnessBuilder { pub(crate) screen_rect: Rect, pub(crate) pixels_per_point: f32, @@ -64,8 +65,10 @@ impl HarnessBuilder { /// Set the default options used for snapshot tests on this harness. #[cfg(feature = "snapshot")] - pub fn with_options(&mut self, options: crate::SnapshotOptions) { + #[inline] + pub fn with_options(mut self, options: crate::SnapshotOptions) -> Self { self.default_snapshot_options = options; + self } /// Override the [`egui::os::OperatingSystem`] reported to egui. From f33b0ffe6ebb3a0dc096bd05a7666ef38c37c39a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 12 Nov 2025 08:52:43 +0100 Subject: [PATCH 303/388] Fix link checker --- lychee.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/lychee.toml b/lychee.toml index 71f49b5e4..21e91f2ef 100644 --- a/lychee.toml +++ b/lychee.toml @@ -42,4 +42,5 @@ accept = [ # Exclude URLs and mail addresses from checking (supports regex). exclude = [ "https://creativecommons.org/.*", # They don't like bots + "https://www.unicode.org/.*", ] From 1af5d1d37ecba2c16a354d2d318d74a25c85da95 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 12 Nov 2025 10:51:28 +0100 Subject: [PATCH 304/388] Remove `accesskit` feature and always depend on `accesskit` (#7701) * Closes #3137 With this, `egui` will always depend on `accesskit`, removing a lot of `#[cfg(feature = "accesskit")]` throughout the code. --- crates/eframe/Cargo.toml | 2 +- crates/eframe/src/web/app_runner.rs | 3 +- crates/egui-winit/Cargo.toml | 2 +- crates/egui-winit/src/lib.rs | 4 ++- crates/egui/Cargo.toml | 8 ++--- crates/egui/src/containers/window.rs | 1 - crates/egui/src/context.rs | 28 ++++++--------- crates/egui/src/data/input.rs | 1 - crates/egui/src/data/output.rs | 10 ++---- crates/egui/src/id.rs | 1 - crates/egui/src/input_state/mod.rs | 4 --- crates/egui/src/lib.rs | 2 -- crates/egui/src/memory/mod.rs | 34 +++++++------------ crates/egui/src/pass_state.rs | 9 +---- crates/egui/src/response.rs | 9 ----- .../egui/src/text_selection/accesskit_text.rs | 3 +- .../egui/src/text_selection/cursor_range.rs | 2 -- .../text_selection/label_text_selection.rs | 1 - crates/egui/src/text_selection/mod.rs | 1 - crates/egui/src/ui.rs | 7 ---- crates/egui/src/ui_builder.rs | 9 +---- crates/egui/src/widgets/drag_value.rs | 25 +++++--------- crates/egui/src/widgets/slider.rs | 27 ++++++--------- crates/egui/src/widgets/text_edit/builder.rs | 1 - crates/egui_kittest/Cargo.toml | 2 +- 25 files changed, 56 insertions(+), 140 deletions(-) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index ce2103cc8..74669e43b 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -40,7 +40,7 @@ default = [ ] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). -accesskit = ["egui/accesskit", "egui-winit/accesskit"] +accesskit = ["egui-winit/accesskit"] # Allow crates to choose an android-activity backend via Winit # - It's important that most applications should not have to depend on android-activity directly, and can diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 4a97235aa..4f4bd518a 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -324,8 +324,7 @@ impl AppRunner { events: _, // already handled mutable_text_under_cursor: _, // TODO(#4569): https://github.com/emilk/egui/issues/4569 ime, - #[cfg(feature = "accesskit")] - accesskit_update: _, // not currently implemented + accesskit_update: _, // not currently implemented num_completed_passes: _, // handled by `Context::run` request_discard_reasons: _, // handled by `Context::run` } = platform_output; diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index a4c84b05f..d1b2ab220 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -24,7 +24,7 @@ rustdoc-args = ["--generate-link-to-definition"] default = ["clipboard", "links", "wayland", "winit/default", "x11"] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). -accesskit = ["dep:accesskit_winit", "egui/accesskit"] +accesskit = ["dep:accesskit_winit"] # Allow crates to choose an android-activity backend via Winit # - It's important that most applications should not have to depend on android-activity directly, and can diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index d72c44245..7660e3cef 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -888,7 +888,6 @@ impl State { events: _, // handled elsewhere mutable_text_under_cursor: _, // only used in eframe web ime, - #[cfg(feature = "accesskit")] accesskit_update, num_completed_passes: _, // `egui::Context::run` handles this request_discard_reasons: _, // `egui::Context::run` handles this @@ -947,6 +946,9 @@ impl State { profiling::scope!("accesskit"); accesskit.update_if_active(|| update); } + + #[cfg(not(feature = "accesskit"))] + let _ = accesskit_update; } fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) { diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index c82ae5618..764d2401e 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -26,10 +26,6 @@ rustdoc-args = ["--generate-link-to-definition"] [features] default = ["default_fonts"] -## Exposes detailed accessibility implementation required by platform -## accessibility APIs. Also requires support in the egui integration. -accesskit = ["dep:accesskit"] - ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`. bytemuck = ["epaint/bytemuck"] @@ -61,7 +57,7 @@ persistence = ["serde", "epaint/serde", "ron"] rayon = ["epaint/rayon"] ## Allow serialization using [`serde`](https://docs.rs/serde). -serde = ["dep:serde", "epaint/serde", "accesskit?/serde"] +serde = ["dep:serde", "epaint/serde", "accesskit/serde"] ## Change Vertex layout to be compatible with unity unity = ["epaint/unity"] @@ -75,6 +71,7 @@ _override_unity = ["epaint/_override_unity"] emath = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false } +accesskit.workspace = true ahash.workspace = true bitflags.workspace = true log.workspace = true @@ -84,7 +81,6 @@ smallvec.workspace = true unicode-segmentation.workspace = true #! ### Optional dependencies -accesskit = { workspace = true, optional = true } backtrace = { workspace = true, optional = true } diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index da7e65c1b..c3bbb760c 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -899,7 +899,6 @@ fn resize_interaction( let rect = outer_rect.shrink(window_frame.stroke.width / 2.0); let side_response = |rect, id| { - #[cfg(feature = "accesskit")] ctx.register_accesskit_parent(id, _accessibility_parent); let response = ctx.create_widget( WidgetRect { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 5a868dd75..587c3b377 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -41,7 +41,6 @@ use crate::{ viewport::ViewportClass, }; -#[cfg(feature = "accesskit")] use crate::IdMap; /// Information given to the backend about when it is time to repaint the ui. @@ -404,7 +403,6 @@ struct ContextImpl { embed_viewports: bool, - #[cfg(feature = "accesskit")] is_accesskit_enabled: bool, loaders: Arc, @@ -507,7 +505,6 @@ impl ContextImpl { }, ); - #[cfg(feature = "accesskit")] if self.is_accesskit_enabled { profiling::scope!("accesskit"); use crate::pass_state::AccessKitPassState; @@ -589,10 +586,10 @@ impl ContextImpl { } } - #[cfg(feature = "accesskit")] fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::Node { let state = self.viewport().this_pass.accesskit_state.as_mut().unwrap(); let builders = &mut state.nodes; + if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { entry.insert(Default::default()); @@ -619,6 +616,7 @@ impl ContextImpl { let parent_builder = builders.get_mut(&parent_id).unwrap(); parent_builder.push_child(id.accesskit_id()); } + builders.get_mut(&id).unwrap() } @@ -1204,7 +1202,6 @@ impl Context { plugins.on_widget_under_pointer(self, &w); } - #[cfg(feature = "accesskit")] if allow_focus && w.sense.is_focusable() { // Make sure anything that can receive focus has an AccessKit node. // TODO(mwcampbell): For nodes that are filled from widget info, @@ -1212,7 +1209,6 @@ 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()); @@ -1220,12 +1216,14 @@ impl Context { viewport .input .consume_accesskit_action_requests(res.id, |request| { + use accesskit::Action; + // 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 => { + Action::ScrollIntoView => { viewport.this_pass.scroll_target = [ Some(ScrollTarget::new( res.rect.x_range(), @@ -1239,16 +1237,16 @@ impl Context { )), ]; } - accesskit::Action::ScrollDown => { + Action::ScrollDown => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::UP; } - accesskit::Action::ScrollUp => { + Action::ScrollUp => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::DOWN; } - accesskit::Action::ScrollLeft => { + Action::ScrollLeft => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::LEFT; } - accesskit::Action::ScrollRight => { + Action::ScrollRight => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::RIGHT; } _ => return false, @@ -1341,7 +1339,6 @@ impl Context { res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true); } - #[cfg(feature = "accesskit")] if enabled && sense.senses_click() && input.has_accesskit_action_request(id, accesskit::Action::Click) @@ -2498,7 +2495,6 @@ impl ContextImpl { let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); - #[cfg(feature = "accesskit")] { profiling::scope!("accesskit"); let state = viewport.this_pass.accesskit_state.take(); @@ -3497,9 +3493,8 @@ impl Context { /// /// The `Context` lock is held while the given closure is called! /// - /// Returns `None` if acesskit is off. + /// Returns `None` if accesskit is off. // TODO(emilk): consider making both read-only and read-write versions - #[cfg(feature = "accesskit")] pub fn accesskit_node_builder( &self, id: Id, @@ -3515,7 +3510,6 @@ impl Context { }) } - #[cfg(feature = "accesskit")] pub(crate) fn register_accesskit_parent(&self, id: Id, parent_id: Id) { self.write(|ctx| { if let Some(state) = ctx.viewport().this_pass.accesskit_state.as_mut() { @@ -3525,13 +3519,11 @@ impl Context { } /// Enable generation of AccessKit tree updates in all future frames. - #[cfg(feature = "accesskit")] pub fn enable_accesskit(&self) { self.write(|ctx| ctx.is_accesskit_enabled = true); } /// Disable generation of AccessKit tree updates in all future frames. - #[cfg(feature = "accesskit")] pub fn disable_accesskit(&self) { self.write(|ctx| ctx.is_accesskit_enabled = false); } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 869945ece..61f6ae00c 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -548,7 +548,6 @@ pub enum Event { WindowFocused(bool), /// An assistive technology (e.g. screen reader) requested an action. - #[cfg(feature = "accesskit")] AccessKitActionRequest(accesskit::ActionRequest), /// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`]. diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index deec5162d..2c6edba84 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -128,7 +128,6 @@ pub struct PlatformOutput { /// The difference in the widget tree since last frame. /// /// NOTE: this needs to be per-viewport. - #[cfg(feature = "accesskit")] pub accesskit_update: Option, /// How many ui passes is this the sum of? @@ -175,7 +174,6 @@ impl PlatformOutput { mut events, mutable_text_under_cursor, ime, - #[cfg(feature = "accesskit")] accesskit_update, num_completed_passes, mut request_discard_reasons, @@ -190,12 +188,8 @@ impl PlatformOutput { self.request_discard_reasons .append(&mut request_discard_reasons); - #[cfg(feature = "accesskit")] - { - // egui produces a complete AccessKit tree for each frame, - // so overwrite rather than appending. - self.accesskit_update = accesskit_update; - } + // egui produces a complete AccessKit tree for each frame, so overwrite rather than append: + self.accesskit_update = accesskit_update; } /// Take everything ephemeral (everything except `cursor_icon` currently) diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 0565dc567..7484930c8 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -79,7 +79,6 @@ impl Id { self.0.get() } - #[cfg(feature = "accesskit")] pub(crate) fn accesskit_id(&self) -> accesskit::NodeId { self.value().into() } diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 7b163a90f..d87788162 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -855,7 +855,6 @@ impl InputState { } } - #[cfg(feature = "accesskit")] pub fn accesskit_action_requests( &self, id: crate::Id, @@ -873,7 +872,6 @@ impl InputState { }) } - #[cfg(feature = "accesskit")] pub fn consume_accesskit_action_requests( &mut self, id: crate::Id, @@ -890,12 +888,10 @@ impl InputState { }); } - #[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() } - #[cfg(feature = "accesskit")] pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize { self.accesskit_action_requests(id, action).count() } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 960480b23..3071f7196 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -448,7 +448,6 @@ pub mod widgets; #[cfg(debug_assertions)] mod callstack; -#[cfg(feature = "accesskit")] pub use accesskit; #[deprecated = "Use the ahash crate directly."] @@ -708,7 +707,6 @@ pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) { }); } -#[cfg(feature = "accesskit")] pub fn accesskit_root_id() -> Id { Id::new("accesskit_root") } diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index ddc5a9ffe..6192f3e72 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -470,7 +470,6 @@ pub(crate) struct Focus { /// The ID of a widget to give the focus to in the next frame. id_next_frame: Option, - #[cfg(feature = "accesskit")] id_requested_by_accesskit: Option, /// If set, the next widget that is interested in focus will automatically get it. @@ -529,10 +528,7 @@ impl Focus { } let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default(); - #[cfg(feature = "accesskit")] - { - self.id_requested_by_accesskit = None; - } + self.id_requested_by_accesskit = None; self.focus_direction = FocusDirection::None; @@ -567,16 +563,13 @@ impl Focus { self.focus_direction = cardinality; } - #[cfg(feature = "accesskit")] + if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest { + action: accesskit::Action::Focus, + target, + data: None, + }) = event { - if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest { - action: accesskit::Action::Focus, - target, - data: None, - }) = event - { - self.id_requested_by_accesskit = Some(*target); - } + self.id_requested_by_accesskit = Some(*target); } } } @@ -606,14 +599,11 @@ impl Focus { } fn interested_in_focus(&mut self, id: Id) { - #[cfg(feature = "accesskit")] - { - if self.id_requested_by_accesskit == Some(id.accesskit_id()) { - self.focused_widget = Some(FocusWidget::new(id)); - self.id_requested_by_accesskit = None; - self.give_to_next = false; - self.reset_focus(); - } + if self.id_requested_by_accesskit == Some(id.accesskit_id()) { + self.focused_widget = Some(FocusWidget::new(id)); + self.id_requested_by_accesskit = None; + self.give_to_next = false; + self.reset_focus(); } // The rect is updated at the end of the frame. diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index 2be7e5098..9b323bfa0 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -67,7 +67,6 @@ impl ScrollTarget { } } -#[cfg(feature = "accesskit")] #[derive(Clone)] pub struct AccessKitPassState { pub nodes: IdMap, @@ -225,7 +224,6 @@ pub struct PassState { /// as when swiping down on a touch-screen or track-pad with natural scrolling. pub scroll_delta: (Vec2, style::ScrollAnimation), - #[cfg(feature = "accesskit")] pub accesskit_state: Option, /// Highlight these widgets the next pass. @@ -247,7 +245,6 @@ impl Default for PassState { used_by_panels: Rect::NAN, scroll_target: [None, None], scroll_delta: (Vec2::default(), style::ScrollAnimation::none()), - #[cfg(feature = "accesskit")] accesskit_state: None, highlight_next_pass: Default::default(), @@ -270,7 +267,6 @@ impl PassState { used_by_panels, scroll_target, scroll_delta, - #[cfg(feature = "accesskit")] accesskit_state, highlight_next_pass, @@ -293,10 +289,7 @@ impl PassState { *debug_rect = None; } - #[cfg(feature = "accesskit")] - { - *accesskit_state = None; - } + *accesskit_state = None; highlight_next_pass.clear(); } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index e17c1aff5..0159a1f5e 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -793,7 +793,6 @@ impl Response { if let Some(event) = event { self.output_event(event); } else { - #[cfg(feature = "accesskit")] self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, make_info()); }); @@ -803,7 +802,6 @@ impl Response { } pub fn output_event(&self, event: crate::output::OutputEvent) { - #[cfg(feature = "accesskit")] self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone()); }); @@ -814,7 +812,6 @@ impl Response { self.ctx.output_mut(|o| o.events.push(event)); } - #[cfg(feature = "accesskit")] pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::Node) { if !self.enabled() { builder.set_disabled(); @@ -833,7 +830,6 @@ impl Response { } } - #[cfg(feature = "accesskit")] fn fill_accesskit_node_from_widget_info( &self, builder: &mut accesskit::Node, @@ -908,14 +904,9 @@ impl Response { /// # }); /// ``` pub fn labelled_by(self, id: Id) -> Self { - #[cfg(feature = "accesskit")] self.ctx.accesskit_node_builder(self.id, |builder| { builder.push_labelled_by(id.accesskit_id()); }); - #[cfg(not(feature = "accesskit"))] - { - let _ = id; - } self } diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index 4d64229c5..974a334d0 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -42,8 +42,9 @@ pub fn update_accesskit_for_text_widget( for (row_index, row) in galley.rows.iter().enumerate() { let row_id = parent_id.with(row_index); - #[cfg(feature = "accesskit")] + ctx.register_accesskit_parent(row_id, parent_id); + ctx.accesskit_node_builder(row_id, |builder| { builder.set_role(accesskit::Role::TextRun); let rect = global_from_galley * row.rect_without_leading_space(); diff --git a/crates/egui/src/text_selection/cursor_range.rs b/crates/egui/src/text_selection/cursor_range.rs index 10980c581..a816f5f26 100644 --- a/crates/egui/src/text_selection/cursor_range.rs +++ b/crates/egui/src/text_selection/cursor_range.rs @@ -190,7 +190,6 @@ impl CCursorRange { .. } => self.on_key_press(os, galley, modifiers, *key), - #[cfg(feature = "accesskit")] Event::AccessKitActionRequest(accesskit::ActionRequest { action: accesskit::Action::SetTextSelection, target, @@ -220,7 +219,6 @@ impl CCursorRange { // ---------------------------------------------------------------------------- -#[cfg(feature = "accesskit")] fn ccursor_from_accesskit_text_position( id: Id, galley: &Galley, diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index 0405ca5da..bc2884441 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -624,7 +624,6 @@ impl LabelSelectionState { ); } - #[cfg(feature = "accesskit")] super::accesskit_text::update_accesskit_for_text_widget( ui.ctx(), response.id, diff --git a/crates/egui/src/text_selection/mod.rs b/crates/egui/src/text_selection/mod.rs index 8d0943d60..cbd51c31a 100644 --- a/crates/egui/src/text_selection/mod.rs +++ b/crates/egui/src/text_selection/mod.rs @@ -1,6 +1,5 @@ //! Helpers regarding text selection for labels and text edit. -#[cfg(feature = "accesskit")] pub mod accesskit_text; mod cursor_range; diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d746b8fec..08bb9cee5 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -133,7 +133,6 @@ impl Ui { sizing_pass, style, sense, - #[cfg(feature = "accesskit")] accessibility_parent, } = ui_builder; @@ -175,7 +174,6 @@ impl Ui { min_rect_already_remembered: false, }; - #[cfg(feature = "accesskit")] if let Some(accessibility_parent) = accessibility_parent { ui.ctx() .register_accesskit_parent(ui.unique_id, accessibility_parent); @@ -202,7 +200,6 @@ impl Ui { ui.set_invisible(); } - #[cfg(feature = "accesskit")] ui.ctx().accesskit_node_builder(ui.unique_id, |node| { node.set_role(accesskit::Role::GenericContainer); }); @@ -273,7 +270,6 @@ impl Ui { sizing_pass, style, sense, - #[cfg(feature = "accesskit")] accessibility_parent, } = ui_builder; @@ -343,7 +339,6 @@ impl Ui { child_ui.disable(); } - #[cfg(feature = "accesskit")] child_ui.ctx().register_accesskit_parent( child_ui.unique_id, accessibility_parent.unwrap_or(self.unique_id), @@ -363,7 +358,6 @@ impl Ui { true, ); - #[cfg(feature = "accesskit")] child_ui .ctx() .accesskit_node_builder(child_ui.unique_id, |node| { @@ -1129,7 +1123,6 @@ impl Ui { impl Ui { /// Check for clicks, drags and/or hover on a specific region of this [`Ui`]. pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response { - #[cfg(feature = "accesskit")] self.ctx().register_accesskit_parent(id, self.unique_id); self.ctx().create_widget( diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index 51b8ec8a5..686fdcb47 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -24,7 +24,6 @@ pub struct UiBuilder { pub sizing_pass: bool, pub style: Option>, pub sense: Option, - #[cfg(feature = "accesskit")] pub accessibility_parent: Option, } @@ -187,15 +186,9 @@ impl UiBuilder { /// /// This will override the automatic parent assignment for accessibility purposes. /// If not set, the parent [`Ui`]'s ID will be used as the accessibility parent. - /// - /// This does nothing if the `accesskit` feature is not enabled. - #[cfg_attr(not(feature = "accesskit"), expect(unused_mut, unused_variables))] #[inline] pub fn accessibility_parent(mut self, parent_id: Id) -> Self { - #[cfg(feature = "accesskit")] - { - self.accessibility_parent = Some(parent_id); - } + self.accessibility_parent = Some(parent_id); self } } diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 9515726c2..29d596201 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -489,27 +489,21 @@ impl Widget for DragValue<'_> { - input.count_and_consume_key(Modifiers::NONE, Key::ArrowDown) as f64; } - #[cfg(feature = "accesskit")] - { - use accesskit::Action; - change += input.num_accesskit_action_requests(id, Action::Increment) as f64 - - input.num_accesskit_action_requests(id, Action::Decrement) as f64; - } + use accesskit::Action; + change += input.num_accesskit_action_requests(id, Action::Increment) as f64 + - input.num_accesskit_action_requests(id, Action::Decrement) as f64; change }); - #[cfg(feature = "accesskit")] - { + ui.input(|input| { use accesskit::{Action, ActionData}; - ui.input(|input| { - for request in input.accesskit_action_requests(id, Action::SetValue) { - if let Some(ActionData::NumericValue(new_value)) = request.data { - value = new_value; - } + for request in input.accesskit_action_requests(id, Action::SetValue) { + if let Some(ActionData::NumericValue(new_value)) = request.data { + value = new_value; } - }); - } + } + }); if clamp_existing_to_range { value = clamp_value_to_range(value, range.clone()); @@ -669,7 +663,6 @@ impl Widget for DragValue<'_> { response.widget_info(|| WidgetInfo::drag_value(ui.is_enabled(), value)); - #[cfg(feature = "accesskit")] ui.ctx().accesskit_node_builder(response.id, |builder| { use accesskit::Action; // If either end of the range is unbounded, it's better diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 7937e5897..129c41c3b 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -716,14 +716,11 @@ impl Slider<'_> { }); } - #[cfg(feature = "accesskit")] - { + ui.input(|input| { use accesskit::Action; - ui.input(|input| { - decrement += input.num_accesskit_action_requests(response.id, Action::Decrement); - increment += input.num_accesskit_action_requests(response.id, Action::Increment); - }); - } + decrement += input.num_accesskit_action_requests(response.id, Action::Decrement); + increment += input.num_accesskit_action_requests(response.id, Action::Increment); + }); let kb_step = increment as f32 - decrement as f32; @@ -759,17 +756,14 @@ impl Slider<'_> { self.set_value(new_value); } - #[cfg(feature = "accesskit")] - { + ui.input(|input| { use accesskit::{Action, ActionData}; - ui.input(|input| { - for request in input.accesskit_action_requests(response.id, Action::SetValue) { - if let Some(ActionData::NumericValue(new_value)) = request.data { - self.set_value(new_value); - } + for request in input.accesskit_action_requests(response.id, Action::SetValue) { + if let Some(ActionData::NumericValue(new_value)) = request.data { + self.set_value(new_value); } - }); - } + } + }); // Paint it: if ui.is_rect_visible(response.rect) { @@ -978,7 +972,6 @@ impl Slider<'_> { } response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text())); - #[cfg(feature = "accesskit")] ui.ctx().accesskit_node_builder(response.id, |builder| { use accesskit::Action; builder.set_min_numeric_value(*self.range.start()); diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index c0364e7ee..6f2da1baa 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -844,7 +844,6 @@ impl TextEdit<'_> { }); } - #[cfg(feature = "accesskit")] { let role = if password { accesskit::Role::PasswordInput diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index a81413840..38ff7349e 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -35,7 +35,7 @@ x11 = ["eframe?/x11"] [dependencies] kittest.workspace = true -egui = { workspace = true, features = ["accesskit"] } +egui.workspace = true eframe = { workspace = true, optional = true } # wgpu dependencies From df6f35d5682ee0e5e1f8b26c3de210db0267d112 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 12 Nov 2025 10:51:38 +0100 Subject: [PATCH 305/388] eframe: add feature `wgpu_no_default_features` (#7700) * Part of https://github.com/emilk/egui/issues/5889 * Closes https://github.com/emilk/egui/issues/7106 This changes the `eframe/wgpu` feature to also enable all the `default` features of `wgpu` and `egui-wgpu`. This makes switching `eframe` backend from `glow` to `wgpu` a lot easier. To get the old behavior (depend on `wgpu` but you must opt-in to all its features), use the new `wgpu_no_default_features` feature. --- .github/workflows/rust.yml | 4 +- crates/eframe/Cargo.toml | 22 +++---- crates/eframe/src/epi.rs | 66 ++++++++++---------- crates/eframe/src/lib.rs | 30 ++++----- crates/eframe/src/native/epi_integration.rs | 6 +- crates/eframe/src/native/glow_integration.rs | 4 +- crates/eframe/src/native/mod.rs | 2 +- crates/eframe/src/native/run.rs | 4 +- crates/eframe/src/web/app_runner.rs | 8 +-- crates/eframe/src/web/mod.rs | 6 +- 10 files changed, 77 insertions(+), 75 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 85a059160..5d30d7e31 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -46,7 +46,9 @@ jobs: - run: cargo clippy --locked --no-default-features --lib --all-targets - - run: cargo clippy --locked --no-default-features --features x11 --lib -p eframe + - run: cargo clippy --locked --no-default-features --lib -p eframe --features x11 + + - run: cargo clippy --locked --no-default-features --lib -p eframe --features x11,wgpu_no_default_features - run: cargo clippy --locked --no-default-features --lib -p egui_extras diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 74669e43b..fbd8ffa87 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -33,10 +33,6 @@ default = [ "web_screen_reader", "winit/default", "x11", - "egui-wgpu?/fragile-send-sync-non-atomic-wasm", - # Let's enable some backends so that users can use `eframe` out-of-the-box - # without having to explicitly opt-in to backends - "egui-wgpu?/default", ] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). @@ -82,16 +78,18 @@ web_screen_reader = ["web-sys/SpeechSynthesis", "web-sys/SpeechSynthesisUtteranc ## ## This overrides the `glow` feature. ## -## By default, only WebGPU is enabled on web. -## If you want to enable WebGL, you need to turn on the `webgl` feature of crate `wgpu`: -## -## ```toml -## wgpu = { version = "*", features = ["webgpu", "webgl"] } -## ``` -## ## By default, eframe will prefer WebGPU over WebGL, but ## you can configure this at run-time with [`NativeOptions::wgpu_options`]. -wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"] +wgpu = ["wgpu_no_default_features", "egui-wgpu/default"] + +## This is exactly like the `wgpu` feature, but does NOT enable the default features of `wgpu` and `egui-wgpu`. +## +## This means that no `wgpu` backends are enabled. You will need to enable them yourself, e.g. like this: +## +## ```toml +## wgpu = { version = "*", features = ["dx12", "metal", "webgl"] } +## ``` +wgpu_no_default_features = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"] ## Enables compiling for x11. x11 = [ diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 384b8e918..5e6adb1b4 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -10,7 +10,7 @@ use std::any::Any; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use crate::native::winit_integration::UserEvent; #[cfg(not(target_arch = "wasm32"))] @@ -22,7 +22,7 @@ use raw_window_handle::{ use static_assertions::assert_not_impl_any; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes}; /// Hook into the building of an event loop before it is run @@ -30,7 +30,7 @@ pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes}; /// You can configure any platform specific details required on top of the default configuration /// done by `EFrame`. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub type EventLoopBuilderHook = Box)>; /// Hook into the building of a the native window. @@ -38,7 +38,7 @@ pub type EventLoopBuilderHook = Box) /// You can configure any platform specific details required on top of the default configuration /// done by `eframe`. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub type WindowBuilderHook = Box egui::ViewportBuilder>; type DynError = Box; @@ -79,7 +79,7 @@ pub struct CreationContext<'s> { /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. /// /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub wgpu_render_state: Option, /// Raw platform window handle @@ -121,7 +121,7 @@ impl CreationContext<'_> { gl: None, #[cfg(feature = "glow")] get_proc_address: None, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: None, #[cfg(not(target_arch = "wasm32"))] raw_window_handle: Err(HandleError::NotSupported), @@ -317,7 +317,7 @@ pub struct NativeOptions { pub hardware_acceleration: HardwareAcceleration, /// What rendering backend to use. - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub renderer: Renderer, /// This controls what happens when you close the main eframe window. @@ -340,7 +340,7 @@ pub struct NativeOptions { /// event loop before it is run. /// /// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook. - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub event_loop_builder: Option, /// Hook into the building of a window. @@ -349,7 +349,7 @@ pub struct NativeOptions { /// window appearance. /// /// Note: A [`NativeOptions`] clone will not include any `window_builder` hook. - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub window_builder: Option, #[cfg(feature = "glow")] @@ -367,7 +367,7 @@ pub struct NativeOptions { pub centered: bool, /// Configures wgpu instance/device/adapter/surface creation and renderloop. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub wgpu_options: egui_wgpu::WgpuConfiguration, /// Controls whether or not the native window position and size will be @@ -404,13 +404,13 @@ impl Clone for NativeOptions { Self { viewport: self.viewport.clone(), - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] event_loop_builder: None, // Skip any builder callbacks if cloning - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] window_builder: None, // Skip any builder callbacks if cloning - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: self.wgpu_options.clone(), persistence_path: self.persistence_path.clone(), @@ -435,15 +435,15 @@ impl Default for NativeOptions { stencil_buffer: 0, hardware_acceleration: HardwareAcceleration::Preferred, - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] renderer: Renderer::default(), run_and_return: true, - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] event_loop_builder: None, - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] window_builder: None, #[cfg(feature = "glow")] @@ -451,7 +451,7 @@ impl Default for NativeOptions { centered: false, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), persist_window: true, @@ -484,7 +484,7 @@ pub struct WebOptions { pub webgl_context_option: WebGlContextOption, /// Configures wgpu instance/device/adapter/surface creation and renderloop. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub wgpu_options: egui_wgpu::WgpuConfiguration, /// Controls whether to apply dithering to minimize banding artifacts. @@ -524,7 +524,7 @@ impl Default for WebOptions { #[cfg(feature = "glow")] webgl_context_option: WebGlContextOption::BestFirst, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), dithering: true, @@ -561,7 +561,7 @@ pub enum WebGlContextOption { /// What rendering backend to use. /// /// You need to enable the "glow" and "wgpu" features to have a choice. -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] @@ -571,49 +571,49 @@ pub enum Renderer { Glow, /// Use [`egui_wgpu`] renderer for [`wgpu`](https://github.com/gfx-rs/wgpu). - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Wgpu, } -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] impl Default for Renderer { fn default() -> Self { #[cfg(not(feature = "glow"))] - #[cfg(not(feature = "wgpu"))] + #[cfg(not(feature = "wgpu_no_default_features"))] compile_error!( "eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'" ); #[cfg(feature = "glow")] - #[cfg(not(feature = "wgpu"))] + #[cfg(not(feature = "wgpu_no_default_features"))] return Self::Glow; #[cfg(not(feature = "glow"))] - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] return Self::Wgpu; // By default, only the `glow` feature is enabled, so if the user added `wgpu` to the feature list // they probably wanted to use wgpu: #[cfg(feature = "glow")] - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] return Self::Wgpu; } } -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] impl std::fmt::Display for Renderer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #[cfg(feature = "glow")] Self::Glow => "glow".fmt(f), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Self::Wgpu => "wgpu".fmt(f), } } } -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] impl std::str::FromStr for Renderer { type Err = String; @@ -622,7 +622,7 @@ impl std::str::FromStr for Renderer { #[cfg(feature = "glow")] "glow" => Ok(Self::Glow), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] "wgpu" => Ok(Self::Wgpu), _ => Err(format!( @@ -655,7 +655,7 @@ pub struct Frame { Option egui::TextureId>>, /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] #[doc(hidden)] pub wgpu_render_state: Option, @@ -705,7 +705,7 @@ impl Frame { #[cfg(not(target_arch = "wasm32"))] raw_window_handle: Err(HandleError::NotSupported), storage: None, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: None, } } @@ -764,7 +764,7 @@ impl Frame { /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. /// /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> { self.wgpu_render_state.as_ref() } diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index f0f392256..d803a5249 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -159,7 +159,7 @@ pub use {egui, egui::emath, egui::epaint}; #[cfg(feature = "glow")] pub use {egui_glow, glow}; -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] pub use {egui_wgpu, wgpu}; mod epi; @@ -188,19 +188,19 @@ pub use web::{WebLogger, WebRunner}; // When compiling natively #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] mod native; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use native::run::EframeWinitApplication; #[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use native::run::EframePumpStatus; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[cfg(feature = "persistence")] pub use native::file_storage::storage_dir; @@ -252,7 +252,7 @@ pub mod icon_data; /// # Errors /// This function can fail if we fail to set up a graphics context. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[allow(clippy::needless_pass_by_value, clippy::allow_attributes)] pub fn run_native( app_name: &str, @@ -268,7 +268,7 @@ pub fn run_native( native::run::run_glow(app_name, native_options, app_creator) } - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Renderer::Wgpu => { log::debug!("Using the wgpu renderer"); native::run::run_wgpu(app_name, native_options, app_creator) @@ -322,7 +322,7 @@ pub fn run_native( /// /// See the `external_eventloop` example for a more complete example. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub fn create_native<'a>( app_name: &str, mut native_options: NativeOptions, @@ -343,7 +343,7 @@ pub fn create_native<'a>( )) } - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Renderer::Wgpu => { log::debug!("Using the wgpu renderer"); EframeWinitApplication::new(native::run::create_wgpu( @@ -357,7 +357,7 @@ pub fn create_native<'a>( } #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { #[cfg(not(feature = "__screenshot"))] assert!( @@ -371,7 +371,7 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { let renderer = native_options.renderer; - #[cfg(all(feature = "glow", feature = "wgpu"))] + #[cfg(all(feature = "glow", feature = "wgpu_no_default_features"))] { match native_options.renderer { Renderer::Glow => "glow", @@ -420,7 +420,7 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { /// # Errors /// This function can fail if we fail to set up a graphics context. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub fn run_simple_native( app_name: &str, native_options: NativeOptions, @@ -472,7 +472,7 @@ pub enum Error { OpenGL(egui_glow::PainterError), /// An error from [`wgpu`]. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Wgpu(egui_wgpu::WgpuError), } @@ -510,7 +510,7 @@ impl From for Error { } } -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] impl From for Error { #[inline] fn from(err: egui_wgpu::WgpuError) -> Self { @@ -551,7 +551,7 @@ impl std::fmt::Display for Error { write!(f, "egui_glow: {err}") } - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Self::Wgpu(err) => { write!(f, "WGPU error: {err}") } diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 1b4c4b664..447b3ea9d 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -179,7 +179,9 @@ impl EpiIntegration { #[cfg(feature = "glow")] glow_register_native_texture: Option< Box egui::TextureId>, >, - #[cfg(feature = "wgpu")] wgpu_render_state: Option, + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: Option< + egui_wgpu::RenderState, + >, ) -> Self { let frame = epi::Frame { info: epi::IntegrationInfo { cpu_usage: None }, @@ -188,7 +190,7 @@ impl EpiIntegration { gl, #[cfg(feature = "glow")] glow_register_native_texture, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state, raw_display_handle: window.display_handle().map(|h| h.as_raw()), raw_window_handle: window.window_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index b42674052..c5358527a 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -239,7 +239,7 @@ impl<'app> GlowWinitApp<'app> { let painter = painter.clone(); move |native| painter.borrow_mut().register_native_texture(native) })), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] None, ); @@ -301,7 +301,7 @@ impl<'app> GlowWinitApp<'app> { storage: integration.frame.storage(), gl: Some(gl), get_proc_address: Some(&get_proc_address), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: None, raw_display_handle: window.display_handle().map(|h| h.as_raw()), raw_window_handle: window.window_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/native/mod.rs b/crates/eframe/src/native/mod.rs index cc0bfd7fc..eb9413717 100644 --- a/crates/eframe/src/native/mod.rs +++ b/crates/eframe/src/native/mod.rs @@ -12,5 +12,5 @@ pub(crate) mod winit_integration; #[cfg(feature = "glow")] mod glow_integration; -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] mod wgpu_integration; diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 6fdae7d3c..7a3a8b4c4 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -381,7 +381,7 @@ pub fn create_glow<'a>( // ---------------------------------------------------------------------------- -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] pub fn run_wgpu( app_name: &str, mut native_options: epi::NativeOptions, @@ -404,7 +404,7 @@ pub fn run_wgpu( run_and_exit(event_loop, wgpu_eframe) } -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] pub fn create_wgpu<'a>( app_name: &str, native_options: epi::NativeOptions, diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 4f4bd518a..d8c209205 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -84,9 +84,9 @@ impl AppRunner { #[cfg(feature = "glow")] get_proc_address: None, - #[cfg(all(feature = "wgpu", not(feature = "glow")))] + #[cfg(all(feature = "wgpu_no_default_features", not(feature = "glow")))] wgpu_render_state: painter.render_state(), - #[cfg(all(feature = "wgpu", feature = "glow"))] + #[cfg(all(feature = "wgpu_no_default_features", feature = "glow"))] wgpu_render_state: None, }; let app = app_creator(&cc).map_err(|err| err.to_string())?; @@ -98,9 +98,9 @@ impl AppRunner { #[cfg(feature = "glow")] gl: Some(painter.gl().clone()), - #[cfg(all(feature = "wgpu", not(feature = "glow")))] + #[cfg(all(feature = "wgpu_no_default_features", not(feature = "glow")))] wgpu_render_state: painter.render_state(), - #[cfg(all(feature = "wgpu", feature = "glow"))] + #[cfg(all(feature = "wgpu_no_default_features", feature = "glow"))] wgpu_render_state: None, }; diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index fdc9d2123..1cfdbb3f3 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -23,7 +23,7 @@ pub use panic_handler::{PanicHandler, PanicSummary}; pub use web_logger::WebLogger; pub use web_runner::WebRunner; -#[cfg(not(any(feature = "glow", feature = "wgpu")))] +#[cfg(not(any(feature = "glow", feature = "wgpu_no_default_features")))] compile_error!("You must enable either the 'glow' or 'wgpu' feature"); mod web_painter; @@ -33,9 +33,9 @@ mod web_painter_glow; #[cfg(feature = "glow")] pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow; -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] mod web_painter_wgpu; -#[cfg(all(feature = "wgpu", not(feature = "glow")))] +#[cfg(all(feature = "wgpu_no_default_features", not(feature = "glow")))] pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; pub use backend::*; From 115adac41d408bd46be4295c3d837256553dabca Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 12 Nov 2025 22:26:37 +0100 Subject: [PATCH 306/388] Add `Response::total_drag_delta` and `PointerState::total_drag_delta` (#7708) Useful in many cases. In a follow-up PR I will use it to prevent drift when dragging/resizing windows --- crates/egui/src/input_state/mod.rs | 5 +++++ crates/egui/src/response.rs | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index d87788162..0c99b6ac3 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -1263,6 +1263,11 @@ impl PointerState { self.press_origin } + /// How far has the pointer moved since the start of the drag (if any)? + pub fn total_drag_delta(&self) -> Option { + Some(self.latest_pos? - self.press_origin?) + } + /// When did the current click/drag originate? /// `None` if no mouse button is down. #[inline(always)] diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 0159a1f5e..7df843dfc 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -396,7 +396,7 @@ impl Response { self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button)) } - /// If dragged, how many points were we dragged and in what direction? + /// If dragged, how many points were we dragged in since last frame? #[inline] pub fn drag_delta(&self) -> Vec2 { if self.dragged() { @@ -410,7 +410,22 @@ impl Response { } } - /// If dragged, how far did the mouse move? + /// If dragged, how many points have we been dragged since the start of the drag? + #[inline] + pub fn total_drag_delta(&self) -> Option { + if self.dragged() { + let mut delta = self.ctx.input(|i| i.pointer.total_drag_delta())?; + if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) { + delta *= from_global.scaling; + } + Some(delta) + } else { + None + } + } + + /// If dragged, how far did the mouse move since last frame? + /// /// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`] /// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen. /// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter. From 5e6615a129ea347b0ec1aece7e5b3a5af30d2e72 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 12 Nov 2025 22:40:04 +0100 Subject: [PATCH 307/388] Prevent drift when resizing and moving windows (#7709) * Follows https://github.com/emilk/egui/pull/7708 * Related to #202 This fixes a particular issue where resizing a window would move it, and resizing it back would not restore it. You can still move a window by resizing it (I didn't focus on that bug here), but at least now the window will return to its original position when you move back the mouse. --- crates/egui/src/containers/area.rs | 12 +++++++++++- crates/egui/src/containers/window.rs | 27 +++++++++++++++++++-------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 1c3e058b3..f1e12bf73 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -525,11 +525,21 @@ impl Area { true, ); + // Used to prevent drift + let pivot_at_start_of_drag_id = id.with("pivot_at_drag_start"); + if movable && move_response.dragged() && let Some(pivot_pos) = &mut state.pivot_pos { - *pivot_pos += move_response.drag_delta(); + let pivot_at_start_of_drag = ctx.data_mut(|data| { + *data.get_temp_mut_or::(pivot_at_start_of_drag_id, *pivot_pos) + }); + + *pivot_pos = + pivot_at_start_of_drag + move_response.total_drag_delta().unwrap_or_default(); + } else { + ctx.data_mut(|data| data.remove::(pivot_at_start_of_drag_id)); } if (move_response.dragged() || move_response.clicked()) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index c3bbb760c..8d7a95be7 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -825,7 +825,7 @@ fn resize_response( area: &mut area::Prepared, resize_id: Id, ) { - let Some(mut new_rect) = move_and_resize_window(ctx, &resize_interaction) else { + let Some(mut new_rect) = move_and_resize_window(ctx, resize_id, &resize_interaction) else { return; }; @@ -847,27 +847,38 @@ fn resize_response( } /// Acts on outer rect (outside the stroke) -fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Option { +fn move_and_resize_window(ctx: &Context, id: Id, interaction: &ResizeInteraction) -> Option { + // Used to prevent drift + let rect_at_start_of_drag_id = id.with("window_rect_at_drag_start"); + if !interaction.any_dragged() { + ctx.data_mut(|data| { + data.remove::(rect_at_start_of_drag_id); + }); return None; } - let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?; - let mut rect = interaction.outer_rect; // prevent drift + let total_drag_delta = ctx.input(|i| i.pointer.total_drag_delta())?; + + let rect_at_start_of_drag = ctx.data_mut(|data| { + *data.get_temp_mut_or::(rect_at_start_of_drag_id, interaction.outer_rect) + }); + + let mut rect = rect_at_start_of_drag; // prevent drift // Put the rect in the center of the stroke: rect = rect.shrink(interaction.window_frame.stroke.width / 2.0); if interaction.left.drag { - rect.min.x = pointer_pos.x; + rect.min.x += total_drag_delta.x; } else if interaction.right.drag { - rect.max.x = pointer_pos.x; + rect.max.x += total_drag_delta.x; } if interaction.top.drag { - rect.min.y = pointer_pos.y; + rect.min.y += total_drag_delta.y; } else if interaction.bottom.drag { - rect.max.y = pointer_pos.y; + rect.max.y += total_drag_delta.y; } // Return to having the rect outside the stroke: From 9a073d939905b9f9a0d2cf63127e23101c9831b5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Nov 2025 10:52:46 +0100 Subject: [PATCH 308/388] Prevent widgets sometimes appearing to move relative to each other (#7710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes when moving a window, having a tooltip attached to the mouse pointer, or scrolling a `ScrollArea`, you would see this disturbing effect: ![drift-bug](https://github.com/user-attachments/assets/013a5f49-ee02-417c-8441-1e1a0369e8bd) This is caused by us rounding many visual elements (lines, rectangles, text, …) to physical pixels in order to keep them sharp. If the window/tooltip itself is not rounded to a physical pixel, then you can get this behavior. So from now on the position of all areas/windows/tooltips/popups/ScrollArea gets rounded to the closes pixel. * Unlocked by https://github.com/emilk/egui/pull/7709 --- crates/egui/src/containers/area.rs | 25 +++++++++++++------ crates/egui/src/containers/scroll_area.rs | 8 ++++++ .../tests/snapshots/imageviewer.png | 4 +-- .../tests/snapshots/demos/Input Test.png | 2 +- .../tests/snapshots/demos/Misc Demos.png | 4 +-- .../tests/snapshots/demos/Panels.png | 4 +-- .../tests/snapshots/demos/Table.png | 4 +-- .../tests/snapshots/demos/Window Options.png | 4 +-- 8 files changed, 37 insertions(+), 18 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index f1e12bf73..3ebac1d65 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -553,13 +553,14 @@ impl Area { move_response }; - if constrain { - state.set_left_top_pos( - Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min, - ); - } - - state.set_left_top_pos(state.left_top_pos()); + state.set_left_top_pos(round_area_position( + ctx, + if constrain { + Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min + } else { + state.left_top_pos() + }, + )); // Update response with possibly moved/constrained rect: move_response.rect = state.rect(); @@ -580,6 +581,16 @@ impl Area { } } +fn round_area_position(ctx: &Context, pos: Pos2) -> Pos2 { + // We round a lot of rendering to pixels, so we round the whole + // area positions to pixels too, so avoid widgets appearing to float + // around independently of each other when the area is dragged. + // But just in case pixels_per_point is irrational, + // we then also round to ui coordinates: + + pos.round_to_pixels(ctx.pixels_per_point()).round_ui() +} + impl Prepared { pub(crate) fn state(&self) -> &AreaState { &self.state diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index d63a2ab59..4d952d315 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -2,6 +2,8 @@ use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; +use emath::GuiRounding as _; + use crate::{ Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, @@ -748,6 +750,12 @@ impl ScrollArea { } let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size); + + // Round to pixels to avoid widgets appearing to "float" when scrolling fractional amounts: + let content_max_rect = content_max_rect + .round_to_pixels(ui.pixels_per_point()) + .round_ui(); + let mut content_ui = ui.new_child( UiBuilder::new() .ui_stack_info(UiStackInfo::new(UiKind::ScrollArea)) diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index e1d518a96..b0d60672b 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:dc9c22567b76193a7f6753c4217adb3c92afa921c488ba1cf2e14b403814e7ac -size 99841 +oid sha256:128ca4e741995ffcdc07b027407d63911ded6c94fe3fe1dd0efecbf9408fb3af +size 99871 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 58cc9f94b..dd2d414a4 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:aff927596be5db77349ec0bbdcc852a0b1467e94c2a553a740a383ae318bad18 +oid sha256:bb3f7b5f790830b46d1410c2bbb5e19c6beb403f8fe979eb8d250fba4f89be3e size 51670 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 396d83508..919bdc66d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d6f055247034fa13ab55c9ec1fca275e6c23999c9a7e01c87af1fcc930faac6 -size 66777 +oid sha256:170cee9d72a4ab59aa2faf1b77aff4a9eee64f3380aa3f1b256340d88b1dabc2 +size 66525 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 22daed0ed..ca9bacfca 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:c91f592571ba654d0a96791662ae7530a1db4c1630b57c795d1c006ea6e46f19 -size 256975 +oid sha256:f7a7d0e2618b852b5966073438c95cb62901d5410c1473639920b0b0bf2ec59b +size 256913 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index cf728bf3f..188c548d8 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fbcca2b13c94769a62b44853b19f7e841bbb60c9197b3d0bf6e83ef9f8f76d1 -size 77815 +oid sha256:72f4c6fe4f5ec243506152027e1150f3069caf98511ceef92b8fea4f6a1563d5 +size 77614 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 97fa6cebc..e782c983a 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:a31f0c12bb70449136443f9086103bd5b46356eedc2bb93ae1b6b10684ab69ca -size 36285 +oid sha256:611a2d6c793a85eebe807b2ddd4446cc0bc21e4284343dd756e64f0232fb6815 +size 35991 From 9875f226585c3568c71bd22e976ff5902376270e Mon Sep 17 00:00:00 2001 From: WickedShell Date: Thu, 13 Nov 2025 02:53:39 -0700 Subject: [PATCH 309/388] Fix double negative in documentation (#7711) The double negative of not undefined conflicted with the example given in parens, This just removes the double negative to agree with the rest of the doc line. I have *not* audited to see if this ordering actually is strictly forced elsewhere. (Apologies for the smallest documentation pull request ever) * [x] I have followed the instructions in the PR template --- crates/egui/src/containers/modal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/containers/modal.rs b/crates/egui/src/containers/modal.rs index 6b846ab5e..23190ddf6 100644 --- a/crates/egui/src/containers/modal.rs +++ b/crates/egui/src/containers/modal.rs @@ -11,7 +11,7 @@ use crate::{ /// /// You can show multiple modals on top of each other. The topmost modal will always be /// the most recently shown one. -/// If multiple modals are newly shown in the same frame, the order of the modals not undefined +/// If multiple modals are newly shown in the same frame, the order of the modals is undefined /// (either first or second could be top). pub struct Modal { pub area: Area, From 51b0d0e4b951eefec5c6858d2f52e718f06095a1 Mon Sep 17 00:00:00 2001 From: Stefan Tammer <34222573+StT191@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:16:10 +0100 Subject: [PATCH 310/388] [egui-wgpu] Put the `capture` module behind a feature flag, make the `egui` dependency optional (#7698) This PR enables users of `egui-wgpu` to render `epaint` primitives without having to bring in the complete `egui` crate and all it's dependencies. --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/Cargo.toml | 3 ++- crates/egui-wgpu/Cargo.toml | 7 +++++-- crates/egui-wgpu/src/lib.rs | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index fbd8ffa87..d5ef8e744 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -135,6 +135,7 @@ winit = { workspace = true, default-features = false, features = ["rwh_06"] } # optional native: egui-wgpu = { workspace = true, optional = true, features = [ "winit", + "capture", ] } # if wgpu is used, use it with winit pollster = { workspace = true, optional = true } # needed for wgpu @@ -240,7 +241,7 @@ web-sys = { workspace = true, features = [ ] } # optional web: -egui-wgpu = { workspace = true, optional = true } # if wgpu is used, use it without (!) winit +egui-wgpu = { workspace = true, optional = true, features = ["capture"] } # if wgpu is used, use it without (!) winit wgpu = { workspace = true, optional = true } # Native dev dependencies for testing diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index cd897b63e..c514e0a49 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -27,8 +27,11 @@ rustdoc-args = ["--generate-link-to-definition"] [features] default = ["fragile-send-sync-non-atomic-wasm", "macos-window-resize-jitter-fix", "wgpu/default"] +## Enables the `capture` module for capturing screenshots. +capture = ["dep:egui"] + ## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11` -winit = ["dep:winit", "winit/rwh_06"] +winit = ["dep:winit", "winit/rwh_06", "dep:egui", "capture"] ## Enables Wayland support for winit. wayland = ["winit?/wayland"] @@ -47,7 +50,6 @@ fragile-send-sync-non-atomic-wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"] macos-window-resize-jitter-fix = ["wgpu/metal"] [dependencies] -egui = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false, features = ["bytemuck"] } ahash.workspace = true @@ -62,4 +64,5 @@ wgpu = { workspace = true, features = ["wgsl"] } # Optional dependencies: +egui = { workspace = true, optional = true, default-features = false } winit = { workspace = true, optional = true, default-features = false } diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 59e27e7ac..d340526af 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -29,6 +29,7 @@ pub use renderer::*; pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting}; /// Helpers for capturing screenshots of the UI. +#[cfg(feature = "capture")] pub mod capture; /// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`]. From 9ef610e16b243d8f71a21d3e0eb76c195fc224f7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Nov 2025 11:16:23 +0100 Subject: [PATCH 311/388] Make `wgpu` the default renderer for `eframe` and egui.rs (#7615) * Closes https://github.com/emilk/egui/issues/5889 See the above issue for motivation. To use glow instead, disable the default features of `eframe` and opt-in to `glow`. This also changes egui.rs to use wgpu, which means WebGPU when available, and WebGL otherwise --- Cargo.toml | 2 + crates/eframe/Cargo.toml | 18 +++++-- crates/eframe/src/epi.rs | 11 +++- crates/eframe/src/web/app_runner.rs | 62 ++++++++++++++++++----- crates/eframe/src/web/mod.rs | 4 -- crates/eframe/src/web/web_painter_glow.rs | 2 +- crates/eframe/src/web/web_painter_wgpu.rs | 14 ++--- crates/egui_demo_app/Cargo.toml | 2 +- scripts/build_demo_web.sh | 18 +++---- 9 files changed, 90 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dfdb595c3..1569299ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,6 +232,7 @@ iter_on_single_items = "warn" iter_over_hash_type = "warn" iter_without_into_iter = "warn" large_digit_groups = "warn" +large_futures = "warn" large_include_file = "warn" large_stack_arrays = "warn" large_stack_frames = "warn" @@ -329,6 +330,7 @@ unnecessary_semicolon = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" +unused_async = "warn" unused_peekable = "warn" unused_rounding = "warn" unused_self = "warn" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index d5ef8e744..6924633f1 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -28,9 +28,9 @@ workspace = true default = [ "accesskit", "default_fonts", - "glow", "wayland", # Required for Linux support (including CI!) "web_screen_reader", + "wgpu", "winit/default", "x11", ] @@ -52,7 +52,11 @@ android-native-activity = ["egui-winit/android-native-activity"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["egui/default_fonts"] -## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow). +## Enable [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow). +## +## There is generally no need to enable both the `wgpu` and `glow` features, +## but if you do you can pick the renderer to use with [`NativeOptions::renderer`] +## and `WebOptions::renderer`. glow = ["dep:egui_glow", "dep:glow", "dep:glutin-winit", "dep:glutin"] ## Enable saving app state to disk. @@ -74,9 +78,15 @@ wayland = [ ## For other platforms, use the `accesskit` feature instead. web_screen_reader = ["web-sys/SpeechSynthesis", "web-sys/SpeechSynthesisUtterance"] -## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu)). +## Enable [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu)). ## -## This overrides the `glow` feature. +## There is generally no need to enable both the `wgpu` and `glow` features, +## but if you do you can pick the renderer to use with [`NativeOptions::renderer`] +## and `WebOptions::renderer`. +## +## Switching from `wgpu (the default)` to `glow` can significantly reduce your binary size +## (including the .wasm of a web app). +## See for more details. ## ## By default, eframe will prefer WebGPU over WebGL, but ## you can configure this at run-time with [`NativeOptions::wgpu_options`]. diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 5e6adb1b4..d6c9a10b8 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -471,6 +471,10 @@ impl Default for NativeOptions { /// Options when using `eframe` in a web page. #[cfg(target_arch = "wasm32")] pub struct WebOptions { + /// What rendering backend to use. + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] + pub renderer: Renderer, + /// Sets the number of bits in the depth buffer. /// /// `egui` doesn't need the depth buffer, so the default value is 0. @@ -519,6 +523,9 @@ pub struct WebOptions { impl Default for WebOptions { fn default() -> Self { Self { + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] + renderer: Renderer::default(), + depth_buffer: 0, #[cfg(feature = "glow")] @@ -592,8 +599,8 @@ impl Default for Renderer { #[cfg(feature = "wgpu_no_default_features")] return Self::Wgpu; - // By default, only the `glow` feature is enabled, so if the user added `wgpu` to the feature list - // they probably wanted to use wgpu: + // It's weird that the user has enabled both glow and wgpu, + // but let's pick the better of the two (wgpu): #[cfg(feature = "glow")] #[cfg(feature = "wgpu_no_default_features")] return Self::Wgpu; diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index d8c209205..8a10a90ef 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -1,15 +1,15 @@ use egui::{TexturesDelta, UserData, ViewportCommand}; -use crate::{App, epi}; +use crate::{App, epi, web::web_painter::WebPainter}; -use super::{NeedRepaint, now_sec, text_agent::TextAgent, web_painter::WebPainter as _}; +use super::{NeedRepaint, now_sec, text_agent::TextAgent}; pub struct AppRunner { #[allow(dead_code, clippy::allow_attributes)] pub(crate) web_options: crate::WebOptions, pub(crate) frame: epi::Frame, egui_ctx: egui::Context, - painter: super::ActiveWebPainter, + painter: Box, pub(crate) input: super::WebInput, app: Box, pub(crate) needs_repaint: std::sync::Arc, @@ -34,6 +34,10 @@ impl Drop for AppRunner { impl AppRunner { /// # Errors /// Failure to initialize WebGL renderer, or failure to create app. + #[cfg_attr( + not(feature = "wgpu_no_default_features"), + expect(clippy::unused_async) + )] pub async fn new( canvas: web_sys::HtmlCanvasElement, web_options: crate::WebOptions, @@ -41,7 +45,41 @@ impl AppRunner { text_agent: TextAgent, ) -> Result { let egui_ctx = egui::Context::default(); - let painter = super::ActiveWebPainter::new(egui_ctx.clone(), canvas, &web_options).await?; + + #[allow(clippy::allow_attributes, unused_assignments)] + #[cfg(feature = "glow")] + let mut gl = None; + + #[allow(clippy::allow_attributes, unused_assignments)] + #[cfg(feature = "wgpu_no_default_features")] + let mut wgpu_render_state = None; + + let painter = match web_options.renderer { + #[cfg(feature = "glow")] + epi::Renderer::Glow => { + log::debug!("Using the glow renderer"); + let painter = super::web_painter_glow::WebPainterGlow::new( + egui_ctx.clone(), + canvas, + &web_options, + )?; + gl = Some(painter.gl().clone()); + Box::new(painter) as Box + } + + #[cfg(feature = "wgpu_no_default_features")] + epi::Renderer::Wgpu => { + log::debug!("Using the wgpu renderer"); + let painter = super::web_painter_wgpu::WebPainterWgpu::new( + egui_ctx.clone(), + canvas, + &web_options, + ) + .await?; + wgpu_render_state = painter.render_state(); + Box::new(painter) as Box + } + }; let info = epi::IntegrationInfo { web_info: epi::WebInfo { @@ -79,15 +117,13 @@ impl AppRunner { storage: Some(&storage), #[cfg(feature = "glow")] - gl: Some(painter.gl().clone()), + gl: gl.clone(), #[cfg(feature = "glow")] get_proc_address: None, - #[cfg(all(feature = "wgpu_no_default_features", not(feature = "glow")))] - wgpu_render_state: painter.render_state(), - #[cfg(all(feature = "wgpu_no_default_features", feature = "glow"))] - wgpu_render_state: None, + #[cfg(feature = "wgpu_no_default_features")] + wgpu_render_state: wgpu_render_state.clone(), }; let app = app_creator(&cc).map_err(|err| err.to_string())?; @@ -96,12 +132,10 @@ impl AppRunner { storage: Some(Box::new(storage)), #[cfg(feature = "glow")] - gl: Some(painter.gl().clone()), + gl, - #[cfg(all(feature = "wgpu_no_default_features", not(feature = "glow")))] - wgpu_render_state: painter.render_state(), - #[cfg(all(feature = "wgpu_no_default_features", feature = "glow"))] - wgpu_render_state: None, + #[cfg(feature = "wgpu_no_default_features")] + wgpu_render_state, }; let needs_repaint: std::sync::Arc = diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 1cfdbb3f3..ac4c637db 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -30,13 +30,9 @@ mod web_painter; #[cfg(feature = "glow")] mod web_painter_glow; -#[cfg(feature = "glow")] -pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow; #[cfg(feature = "wgpu_no_default_features")] mod web_painter_wgpu; -#[cfg(all(feature = "wgpu_no_default_features", not(feature = "glow")))] -pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; pub use backend::*; diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index ca6e11bf5..e2fc4a6f2 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -20,7 +20,7 @@ impl WebPainterGlow { self.painter.gl() } - pub async fn new( + pub fn new( _ctx: egui::Context, canvas: HtmlCanvasElement, options: &WebOptions, diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 9faba9dd7..efecd12ee 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -1,13 +1,15 @@ use std::sync::Arc; -use super::web_painter::WebPainter; -use crate::WebOptions; use egui::{Event, UserData, ViewportId}; -use egui_wgpu::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}; -use egui_wgpu::{RenderState, SurfaceErrorAction}; +use egui_wgpu::{ + RenderState, SurfaceErrorAction, + capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}, +}; use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; +use super::web_painter::WebPainter; + pub(crate) struct WebPainterWgpu { canvas: HtmlCanvasElement, surface: wgpu::Surface<'static>, @@ -23,7 +25,6 @@ pub(crate) struct WebPainterWgpu { } impl WebPainterWgpu { - #[expect(unused)] // only used if `wgpu` is the only active feature. pub fn render_state(&self) -> Option { self.render_state.clone() } @@ -55,11 +56,10 @@ impl WebPainterWgpu { }) } - #[expect(unused)] // only used if `wgpu` is the only active feature. pub async fn new( ctx: egui::Context, canvas: web_sys::HtmlCanvasElement, - options: &WebOptions, + options: &crate::WebOptions, ) -> Result { log::debug!("Creating wgpu painter"); diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 8eb441ac5..7cde46383 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -23,7 +23,7 @@ crate-type = ["cdylib", "rlib"] [features] -default = ["glow", "persistence"] +default = ["wgpu", "persistence"] # image_viewer adds about 0.9 MB of WASM web_app = ["http", "persistence"] diff --git a/scripts/build_demo_web.sh b/scripts/build_demo_web.sh index b6eb7197a..4ba3846db 100755 --- a/scripts/build_demo_web.sh +++ b/scripts/build_demo_web.sh @@ -13,13 +13,13 @@ OPEN=false OPTIMIZE=false BUILD=debug BUILD_FLAGS="" -WGPU=false +GLOW=false WASM_OPT_FLAGS="-O2 --fast-math" while test $# -gt 0; do case "$1" in -h|--help) - echo "build_demo_web.sh [--release] [--wgpu] [--open]" + echo "build_demo_web.sh [--release] [--glow] [--open]" echo "" echo " -g: Keep debug symbols even with --release." echo " These are useful profiling and size trimming." @@ -29,9 +29,7 @@ while test $# -gt 0; do echo " --release: Build with --release, and then run wasm-opt." echo " NOTE: --release also removes debug symbols, unless you also use -g." echo "" - echo " --wgpu: Build a binary using wgpu instead of glow/webgl." - echo " The resulting binary will automatically use WebGPU if available and" - echo " fall back to a WebGL emulation layer otherwise." + echo " --glow: Build a binary using glow instead of wgpu." exit 0 ;; @@ -52,9 +50,9 @@ while test $# -gt 0; do BUILD_FLAGS="--release" ;; - --wgpu) + --glow) shift - WGPU=true + GLOW=true ;; *) @@ -66,10 +64,10 @@ done OUT_FILE_NAME="egui_demo_app" -if [[ "${WGPU}" == true ]]; then - FEATURES="${FEATURES},wgpu" -else +if [[ "${GLOW}" == true ]]; then FEATURES="${FEATURES},glow" +else + FEATURES="${FEATURES},wgpu" fi FINAL_WASM_PATH=web_demo/${OUT_FILE_NAME}_bg.wasm From ecee85fc6b2dab422ca1a5fe5591238facae7fa2 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 13 Nov 2025 13:52:13 +0100 Subject: [PATCH 312/388] Fix `ui.response().interact(Sense::click())` being flakey (#7713) This fixes calls to `ui.response().interact(Sense::click())` being flakey. Since egui checks widget interactions at the beginning of the frame, based on the responses from last frame, we need to ensure that we always call `create_widget` on `interact` calls, otherwise there can be a feedback loop where the `Sense` egui acts on flips back and forth between frames. Without the fix in `interact`, both the asserts in the new test fail. Here is a video where I experienced the bug, showing the sense switching every frame. Every other click would fail to be detected. https://github.com/user-attachments/assets/6be7ca0e-b50f-4d30-bf87-bbb80c319f3b Also note, usually it's better to use `UiBuilder::sense()` to give a Ui some sense, but sometimes you don't have the flexibility, e.g. in a `Ui` callback from some code external to your project. --- crates/egui/src/response.rs | 7 ++-- tests/egui_tests/tests/regression_tests.rs | 45 +++++++++++++++++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 7df843dfc..e89cb5252 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -724,10 +724,9 @@ impl Response { /// ``` #[must_use] pub fn interact(&self, sense: Sense) -> Self { - if (self.sense | sense) == self.sense { - // Early-out: we already sense everything we need to sense. - return self.clone(); - } + // We could check here if the new Sense equals the old one to avoid the extra create_widget + // call. But that would break calling `interact` on a response from `Context::read_response` + // or `Ui::response`. (See https://github.com/emilk/egui/pull/7713 for more details.) self.ctx.create_widget( WidgetRect { diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs index 1ee197cb5..9e76394cb 100644 --- a/tests/egui_tests/tests/regression_tests.rs +++ b/tests/egui_tests/tests/regression_tests.rs @@ -1,5 +1,5 @@ use egui::accesskit::Role; -use egui::{Align, Color32, Image, Label, Layout, RichText, TextWrapMode, include_image}; +use egui::{Align, Color32, Image, Label, Layout, RichText, Sense, TextWrapMode, include_image}; use egui_kittest::Harness; use egui_kittest::kittest::Queryable as _; @@ -75,3 +75,46 @@ fn combobox_should_have_value() { Some("Option 1") ); } + +/// This test ensures that `ui.response().interact(...)` works correctly. +/// +/// This was broken, because there was an optimization in [`egui::Response::interact`] +/// which caused the [`Sense`] of the original response to flip-flop between `click` and `hover` +/// between frames. +/// +/// See for more details. +#[test] +fn interact_on_ui_response_should_be_stable() { + let mut first_frame = true; + let mut click_count = 0; + let mut harness = Harness::new_ui(|ui| { + let ui_response = ui.response(); + if !first_frame { + assert!( + ui_response.sense.contains(Sense::click()), + "ui.response() didn't have click sense even though we called interact(Sense::click()) last frame" + ); + } + + // Add a label so we have something to click with kittest + ui.add( + Label::new("senseless label") + .sense(Sense::hover()) + .selectable(false), + ); + + let click_response = ui_response.interact(Sense::click()); + if click_response.clicked() { + click_count += 1; + } + first_frame = false; + }); + + for i in 0..=10 { + harness.run_steps(i); + harness.get_by_label("senseless label").click(); + } + + drop(harness); + assert_eq!(click_count, 10, "We missed some clicks!"); +} From 01770be13ee4513a960a7db8118b6981e907eb64 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Nov 2025 11:58:03 +0100 Subject: [PATCH 313/388] Update changelogs and version for 0.33.2 --- CHANGELOG.md | 16 ++++++++++++ Cargo.lock | 32 ++++++++++++------------ Cargo.toml | 26 +++++++++---------- crates/ecolor/CHANGELOG.md | 4 +++ crates/eframe/CHANGELOG.md | 5 ++++ crates/egui-wgpu/CHANGELOG.md | 4 +++ crates/egui-winit/CHANGELOG.md | 4 +++ crates/egui_extras/CHANGELOG.md | 4 +++ crates/egui_glow/CHANGELOG.md | 4 +++ crates/egui_kittest/CHANGELOG.md | 4 +++ crates/egui_kittest/Cargo.toml | 2 +- crates/emath/CHANGELOG.md | 5 ++++ crates/epaint/CHANGELOG.md | 4 +++ crates/epaint_default_fonts/CHANGELOG.md | 4 +++ 14 files changed, 88 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc31e57b..856fe09da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,22 @@ 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.33.2 - 2025-11-13 +### ⭐ Added +* Add `Plugin::on_widget_under_pointer` to support widget inspector [#7652](https://github.com/emilk/egui/pull/7652) by [@juancampa](https://github.com/juancampa) +* Add `Response::total_drag_delta` and `PointerState::total_drag_delta` [#7708](https://github.com/emilk/egui/pull/7708) by [@emilk](https://github.com/emilk) + +### 🔧 Changed +* Improve accessibility and testability of `ComboBox` [#7658](https://github.com/emilk/egui/pull/7658) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🐛 Fixed +* Fix `profiling::scope` compile error when profiling using `tracing` backend [#7646](https://github.com/emilk/egui/pull/7646) by [@PPakalns](https://github.com/PPakalns) +* Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk) +* Hide scroll bars when dragging other things [#7689](https://github.com/emilk/egui/pull/7689) by [@emilk](https://github.com/emilk) +* Prevent widgets sometimes appearing to move relative to each other [#7710](https://github.com/emilk/egui/pull/7710) by [@emilk](https://github.com/emilk) +* Fix `ui.response().interact(Sense::click())` being flakey [#7713](https://github.com/emilk/egui/pull/7713) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 0.33.0 - 2025-10-09 - `egui::Plugin`, better kerning, kitdiff viewer Highlights from this release: - `egui::Plugin` a improved way to create and access egui plugins diff --git a/Cargo.lock b/Cargo.lock index cecaf6935..61bf459b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1248,7 +1248,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.33.0" +version = "0.33.2" dependencies = [ "bytemuck", "cint", @@ -1260,7 +1260,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ahash", "bytemuck", @@ -1299,7 +1299,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.33.0" +version = "0.33.2" dependencies = [ "accesskit", "ahash", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ahash", "bytemuck", @@ -1337,7 +1337,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.33.0" +version = "0.33.2" dependencies = [ "accesskit_winit", "arboard", @@ -1360,7 +1360,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.33.0" +version = "0.33.2" dependencies = [ "accesskit", "accesskit_consumer", @@ -1390,7 +1390,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.33.0" +version = "0.33.2" dependencies = [ "chrono", "criterion", @@ -1407,7 +1407,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ahash", "chrono", @@ -1426,7 +1426,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.33.0" +version = "0.33.2" dependencies = [ "bytemuck", "document-features", @@ -1445,7 +1445,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.33.1" +version = "0.33.2" dependencies = [ "dify", "document-features", @@ -1463,7 +1463,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.33.0" +version = "0.33.2" dependencies = [ "egui", "egui_extras", @@ -1493,7 +1493,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.33.0" +version = "0.33.2" dependencies = [ "bytemuck", "document-features", @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ab_glyph", "ahash", @@ -1613,7 +1613,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.33.0" +version = "0.33.2" [[package]] name = "equivalent" @@ -3425,7 +3425,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.33.0" +version = "0.33.2" dependencies = [ "eframe", "env_logger", @@ -5748,7 +5748,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.33.0" +version = "0.33.2" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index 1569299ec..b33ca445c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.88" -version = "0.33.0" +version = "0.33.2" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.33.0", path = "crates/emath", default-features = false } -ecolor = { version = "0.33.0", path = "crates/ecolor", default-features = false } -epaint = { version = "0.33.0", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.33.0", path = "crates/epaint_default_fonts" } -egui = { version = "0.33.0", path = "crates/egui", default-features = false } -egui-winit = { version = "0.33.0", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.33.0", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.33.0", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.33.0", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.33.0", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.33.1", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.33.0", path = "crates/eframe", default-features = false } +emath = { version = "0.33.2", path = "crates/emath", default-features = false } +ecolor = { version = "0.33.2", path = "crates/ecolor", default-features = false } +epaint = { version = "0.33.2", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.33.2", path = "crates/epaint_default_fonts" } +egui = { version = "0.33.2", path = "crates/egui", default-features = false } +egui-winit = { version = "0.33.2", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.33.2", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.33.2", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.33.2", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.33.2", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.33.2", path = "crates/eframe", default-features = false } accesskit = "0.21.1" accesskit_consumer = "0.30.1" diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 6996d838f..4dee81296 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,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.33.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 * Align `Color32` to 4 bytes [#7318](https://github.com/emilk/egui/pull/7318) by [@anti-social](https://github.com/anti-social) * Make the `hex_color` macro `const` [#7444](https://github.com/emilk/egui/pull/7444) by [@YgorSouza](https://github.com/YgorSouza) diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 142634d02..11f1c8fc9 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,11 @@ 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.33.2 - 2025-11-13 +* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman) +* Make sure `native_pixels_per_point` is set during app creation [#7683](https://github.com/emilk/egui/pull/7683) by [@emilk](https://github.com/emilk) + + ## 0.33.0 - 2025-10-09 ### ⭐ Added * Add an option to limit the repaint rate in the web runner [#7482](https://github.com/emilk/egui/pull/7482) by [@s-nie](https://github.com/s-nie) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 5f4fd78c1..be00dd049 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,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.33.2 - 2025-11-13 +* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman) + + ## 0.33.0 - 2025-10-09 ### 🔧 Changed * Update wgpu to 26 and wasm-bindgen to 0.2.100 [#7540](https://github.com/emilk/egui/pull/7540) by [@Kumpelinus](https://github.com/Kumpelinus) diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index e6b094502..8e555a7bc 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/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.33.2 - 2025-11-13 +* Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh) + + ## 0.33.0 - 2025-10-09 ### ⭐ Added * Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101) diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 726a1759c..9dbeb5879 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/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.33.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 * Fix: use unique id for resize columns in `Table` [#7414](https://github.com/emilk/egui/pull/7414) by [@zezic](https://github.com/zezic) * Feat: Add serde serialization to SyntectSettings [#7506](https://github.com/emilk/egui/pull/7506) by [@bircni](https://github.com/bircni) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 34c2133e9..4dd90a359 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.33.2 - 2025-11-13 +Nothing new + + ## 0.33.1 - 2025-10-15 * Add `egui_kittest::HarnessBuilder::with_options` [#7638](https://github.com/emilk/egui/pull/7638) by [@emilk](https://github.com/emilk) diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index 38ff7349e..1de8ce7ac 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egui_kittest" -version = "0.33.1" +version.workspace = true authors = ["Lucas Meurer ", "Emil Ernerfeldt "] description = "Testing library for egui based on kittest and AccessKit" edition.workspace = true diff --git a/crates/emath/CHANGELOG.md b/crates/emath/CHANGELOG.md index b7e0fec1f..334af345f 100644 --- a/crates/emath/CHANGELOG.md +++ b/crates/emath/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to the `emath` crate will be noted in this file. 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.33.2 - 2025-11-13 +* Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk) + + ## 0.33.0 - 2025-10-09 * Add `emath::fast_midpoint` [#7435](https://github.com/emilk/egui/pull/7435) by [@emilk](https://github.com/emilk) * Generate changelogs for emath [#7513](https://github.com/emilk/egui/pull/7513) by [@lucasmerlin](https://github.com/lucasmerlin) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 0524b87c0..b23c454b4 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/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.33.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 * Remove the `deadlock_detection` feature [#7497](https://github.com/emilk/egui/pull/7497) by [@lucasmerlin](https://github.com/lucasmerlin) * More even text kerning [#7431](https://github.com/emilk/egui/pull/7431) by [@valadaptive](https://github.com/valadaptive) diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index bb39e4784..3f131cb55 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.33.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 Nothing new From dc0acd2dd1c8fa0b34b7c03390d589a6206cfb0e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 17 Nov 2025 05:10:43 +0100 Subject: [PATCH 314/388] clippy +nightly fix (#7723) --- crates/egui/src/containers/menu.rs | 11 +++++------ crates/egui/src/memory/mod.rs | 3 +-- crates/egui/src/menu.rs | 2 +- crates/egui/src/ui_stack.rs | 2 +- crates/egui_demo_lib/src/demo/misc_demo_window.rs | 2 +- crates/epaint/src/shapes/shape.rs | 2 +- tests/test_viewports/src/main.rs | 2 +- 7 files changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index f2aaee046..756d68dd3 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -161,14 +161,13 @@ impl MenuState { if state.last_visible_pass + 1 < pass_nr { state.open_item = None; } - if let Some(item) = state.open_item { - if data + if let Some(item) = state.open_item + && data .get_temp(item.with(Self::ID)) .is_none_or(|item: Self| item.last_visible_pass + 1 < pass_nr) - { - // If the open item wasn't shown for at least a frame, reset the open item - state.open_item = None; - } + { + // If the open item wasn't shown for at least a frame, reset the open item + state.open_item = None; } let r = f(&mut state); data.insert_temp(state_id, state); diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 6192f3e72..d215a3bec 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -1272,8 +1272,7 @@ impl Areas { pub fn top_layer_id(&self, order: Order) -> Option { self.order .iter() - .filter(|layer| layer.order == order && !self.is_sublayer(layer)) - .next_back() + .rfind(|layer| layer.order == order && !self.is_sublayer(layer)) .copied() } diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 348f42c21..4d746c074 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -634,7 +634,7 @@ impl SubMenu { /// Usually you don't need to use it directly. pub struct MenuState { /// The opened sub-menu and its [`Id`] - sub_menu: Option<(Id, Arc>)>, + sub_menu: Option<(Id, Arc>)>, /// Bounding box of this menu (without the sub-menu), /// including the frame and everything. diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 0122f5681..4136218bd 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -209,7 +209,7 @@ pub struct UiStack { pub layout_direction: Direction, pub min_rect: Rect, pub max_rect: Rect, - pub parent: Option>, + pub parent: Option>, } // these methods act on this specific node 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 bb62f1822..b502fa767 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -451,7 +451,7 @@ enum Action { #[derive(Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -struct Tree(Vec); +struct Tree(Vec); impl Tree { pub fn demo() -> Self { diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 8ee852c61..fa8a3e75c 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -30,7 +30,7 @@ pub enum Shape { /// Recursively nest more shapes - sometimes a convenience to be able to do. /// For performance reasons it is better to avoid it. - Vec(Vec), + Vec(Vec), /// Circle with optional outline and fill. Circle(CircleShape), diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index ab31a4ece..a862dbd32 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -31,7 +31,7 @@ pub struct ViewportState { pub visible: bool, pub immediate: bool, pub title: String, - pub children: Vec>>, + pub children: Vec>>, } impl ViewportState { From f74b7c7e79c82c119141e97dccff6eed9b9b682c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 18 Nov 2025 06:24:03 +0100 Subject: [PATCH 315/388] Paint mouse cursor in kittest snapshot images (#7721) Very simple triangle shape, but helps understand why a widget has a hovered effect --- .../egui_demo_app/tests/snapshots/clock.png | 4 ++-- .../tests/snapshots/custom3d.png | 4 ++-- .../tests/snapshots/easymarkeditor.png | 4 ++-- .../tests/snapshots/imageviewer.png | 4 ++-- .../tests/snapshots/modals_2.png | 4 ++-- .../tests/snapshots/modals_3.png | 4 ++-- ...rop_should_prevent_focusing_lower_area.png | 4 ++-- crates/egui_kittest/src/lib.rs | 23 ++++++++++++++++++- .../tests/snapshots/combobox_opened.png | 4 ++-- .../tests/snapshots/menu/closed_hovered.png | 4 ++-- .../tests/snapshots/menu/opened.png | 4 ++-- .../tests/snapshots/menu/submenu.png | 4 ++-- .../tests/snapshots/menu/subsubmenu.png | 4 ++-- .../tests/snapshots/readme_example.png | 4 ++-- .../tests/snapshots/test_tooltip_shown.png | 4 ++-- .../hovering_should_preserve_text_format.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 ++-- .../tests/snapshots/visuals/checkbox.png | 4 ++-- .../snapshots/visuals/checkbox_checked.png | 4 ++-- .../tests/snapshots/visuals/drag_value.png | 4 ++-- .../tests/snapshots/visuals/radio.png | 4 ++-- .../tests/snapshots/visuals/radio_checked.png | 4 ++-- .../snapshots/visuals/selectable_value.png | 4 ++-- .../visuals/selectable_value_selected.png | 4 ++-- .../tests/snapshots/visuals/slider.png | 4 ++-- .../tests/snapshots/visuals/text_edit.png | 4 ++-- .../snapshots/visuals/text_edit_clip.png | 4 ++-- .../snapshots/visuals/text_edit_no_clip.png | 4 ++-- .../visuals/text_edit_placeholder_clip.png | 4 ++-- 32 files changed, 84 insertions(+), 63 deletions(-) diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 39d9bb5ce..ec50255fd 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:44a68dc4d3aeebeb2d296c5c8e03aac330e1e4552364084347b710326c88f70c -size 335794 +oid sha256:784cbcdfd8deaf61e7b663f9416d67724e6a6a189a20ba3351908aa5c5f2deff +size 336159 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index deed497b1..3fbf0ab56 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:e9a760fe4a695e6321f00e40bfa76fd0195bee7157a1217572765e3f146ea2cc -size 93640 +oid sha256:4cdde1dda0e64f584c769c72f5910a7035e6a4a86a074b590e88365f12570109 +size 94062 diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index a039b8c24..b9d8b2f22 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:a1670bbfc1f0a71e20cbbeb73625c148b680963bc503d9b48e9cc43e704d7c54 -size 181671 +oid sha256:824d941ea538fd44fc374f5df1893eee2309004c0ee5e69a97f1c84a74b2b423 +size 182128 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index b0d60672b..fee7ad891 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:128ca4e741995ffcdc07b027407d63911ded6c94fe3fe1dd0efecbf9408fb3af -size 99871 +oid sha256:44ea7ac8c8e22eb51fbcb63f00c8510de0e6ae126d19ab44c5d708d979b5362b +size 100345 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index c8e7cb55a..0aa16858a 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53c1509f7be264ed2947cd4ec0f10b555e9f710e949ed6fd8a73ca8ade53abd4 -size 48570 +oid sha256:e7bc441559ff2d8723cf344113ce5ff8158e41179e4c93abcacbe7b1b13b3723 +size 48998 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index 777b700c2..eaae2a758 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20eecafb998f69c2384afabc27eec1f97f413d603ece944adae9a99139be0b58 -size 44689 +oid sha256:3e092be54efaeb700a63d9b679894647159f39a0d3062692ac7056e98242cbee +size 45364 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index 700eaf46b..8b54bf99f 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a7f8282946761e6ab40193267e47a9421f5642bae67458a9aadb71ac1231c8f -size 44581 +oid sha256:88930779ac199e42fcc9ee25f29bd120478c129807713218370b617905340087 +size 45366 diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 33a188ea4..fc8b8efbc 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -657,7 +657,28 @@ impl<'a, State> Harness<'a, State> { /// Returns an error if the rendering fails. #[cfg(any(feature = "wgpu", feature = "snapshot"))] pub fn render(&mut self) -> Result { - self.renderer.render(&self.ctx, &self.output) + let mut output = self.output.clone(); + + if let Some(mouse_pos) = self.ctx.input(|i| i.pointer.hover_pos()) { + // Paint a mouse cursor: + let triangle = vec![ + mouse_pos, + mouse_pos + egui::vec2(16.0, 8.0), + mouse_pos + egui::vec2(8.0, 16.0), + ]; + + output.shapes.push(ClippedShape { + clip_rect: self.ctx.content_rect(), + shape: egui::epaint::PathShape::convex_polygon( + triangle, + Color32::WHITE, + egui::Stroke::new(1.0, Color32::BLACK), + ) + .into(), + }); + } + + self.renderer.render(&self.ctx, &output) } /// Get the root viewport output diff --git a/crates/egui_kittest/tests/snapshots/combobox_opened.png b/crates/egui_kittest/tests/snapshots/combobox_opened.png index aaa7198ce..e45a4aed3 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_opened.png +++ b/crates/egui_kittest/tests/snapshots/combobox_opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f70ef032c241cd63675a246de07886c5c822e6fe21525b3a6d3fee106a589c9 -size 7501 +oid sha256:42911cbb500fa49170aac0da8e4167641c5d7c9724a6accd4d400258fc74e2d7 +size 8061 diff --git a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png index d2969adee..c30b3fdd1 100644 --- a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png +++ b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd6e159a462dde10240c4ca51da5ca5badfb7fc170bad97a59106babb72f8ae3 -size 10795 +oid sha256:94ba2e648c981bf4afbd9b9d01eef0708f7067be6e4cefbdfacc13aa219c6289 +size 11253 diff --git a/crates/egui_kittest/tests/snapshots/menu/opened.png b/crates/egui_kittest/tests/snapshots/menu/opened.png index 30f26b446..7a2750454 100644 --- a/crates/egui_kittest/tests/snapshots/menu/opened.png +++ b/crates/egui_kittest/tests/snapshots/menu/opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f2a5873350f85457d599c1fd165ac756ed69758e7647e160c64f44d2f35c804 -size 21812 +oid sha256:436999f511dce318f29172f0b7e2007e1f0fedae58f5e0e85e19f1d8e0bee361 +size 22273 diff --git a/crates/egui_kittest/tests/snapshots/menu/submenu.png b/crates/egui_kittest/tests/snapshots/menu/submenu.png index 96ffaf97c..25453c8d9 100644 --- a/crates/egui_kittest/tests/snapshots/menu/submenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/submenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:facc05c499745594ac286f15645e40447633a176058337cad9edcb850ad578c7 -size 29379 +oid sha256:28435faf5c8c6d880cd50d52050c9f4cd6b992d0c621f01ca28fb5502eed16a1 +size 29863 diff --git a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png index d1d0b4cd3..c22c2b9b6 100644 --- a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f23ff8c6782befdbe7bd5f076dcdda15c38555f8e505282369bf52e43938c1b -size 34194 +oid sha256:f9a364b4b8c4ad3e78a80b0c6825d9de28c0e0d2e18dcfcd0ff18652ca86c859 +size 34750 diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index 2c8565718..f58e6faec 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:bb8d702361987803995c0f557ce94552a87b97dcd25bed5ee39af4c0e6090700 -size 1904 +oid sha256:87c76a9d07174e4e24ad3d08585c1df7bf3628bdc8f183d11beb6f9e14c4b2ec +size 2325 diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png index 8ff6bba67..4d00c924a 100644 --- a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png +++ b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c267530452adb4f1ed1440df476d576ef4c2d96e6c58068bb57fed4615f5e113 -size 4453 +oid sha256:e269ede9c0784d00c153d51a13566d9c8f0d61ce11565997691fa63be06ec889 +size 5075 diff --git a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png index 2b3ac7a50..038ce78db 100644 --- a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png +++ b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cac533a01c65c8eef093efcd4c9036da50f898ea2436612990f4c2365c98ad83 -size 12126 +oid sha256:c83e094b1f0dede0195cc77f5caa3b7d13249364612b03c02f0ef5f2af5e28ad +size 12512 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png index 4c81f62fc..4204dd1d3 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:1c05992e16c1abf6d174fed73d19cad6bb2266e0adb87b8232e765d75fcf3f14 -size 10310 +oid sha256:6d0c3773bc3698fbd1bd1eb1aa1ed45938d5cb94696bfcec56e4e7e865871baf +size 11143 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png index 00582f3ae..5d1e74292 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:7681d33a5a764187c084c966a4e47063136e2832094c44f62718447b1b3027ec -size 11292 +oid sha256:9764ab5549e0775380b1db3c9a9a1d47c6520bcd5b8781f922e97e3524c362aa +size 12133 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 1429bfd2d..b2f5646d3 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:b99a82e9f3dfa24c079545272d680b55c4285c276befa0efc492fe273422f541 -size 14195 +oid sha256:4d0c7d4b161f7a1f9cadb3e285edcd08588b9e47e10c5579183c824ae4e7be1b +size 15170 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 c73effead..3f20c4379 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:cdf079228b762949dbc67308103f8fe1328b6c0175f312ccc492d4e86d42127b -size 13868 +oid sha256:65359fcb0f01627876e697684b185c60812dd1591b0f42174673712939e2f193 +size 14852 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png index 16d88e546..2145ceee7 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bafe4c157696bfb52940b69501416d4da0b4eab52f34f52220d2e9ed01357cf -size 12901 +oid sha256:68347d7eb452a6f30fa93778f9ebd17f20c1425426472d3ebe4c8b55fc0ba8ea +size 13774 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index fd85297ac..ec012113b 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:72175bf108135b422d978b701d29e6d9a5348c536e25abc924234bc11b6b7f21 -size 14016 +oid sha256:2c323b3b530be2c4ff195e369e86df49ef28de0696fb33a74361d9dbd95e37ae +size 14889 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png index 5411009f0..05f63136b 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:129121534b5f1a2a668898ebb3560820fe50aa4d3546ef46cc764d5513787e9e -size 7529 +oid sha256:8a48d2014ed6295d61f3200389315662b89e7efba27a93fded255cce7bd21e05 +size 8675 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png index e00b42d8c..298841d6a 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9999c7921f8b277f456189ce0f1185120b4cde7c9a01485a5a7d83f12e95527 -size 11710 +oid sha256:cdaeee74db8c9527e6656b4a3026ed18cb58c4761f1155768a456d6d58dc79e2 +size 12549 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index 0021e7d0a..02590ce3d 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:8fec1fd9f80e5fa17b1cda690c0856e7e5fd674d113a10b1d60b14f5a6c6dd6b -size 12401 +oid sha256:3dfbfd35264e4d35a594c72ef0fb9575b090301e112a98228d3070fa85aa4e42 +size 13240 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png index a0b480be4..85cb2a451 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac18e2eef000a80858b2d0811f9ee31304c6ff96f7a91dc60cc1a404ae28ce38 -size 13246 +oid sha256:cbaa88e2769bd9dbffa9b3ced36585c00b4ad6ca91ae61a6becc63a495a812fc +size 14116 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 291263c44..8f0cfb4a9 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:c11fe0399c85db5a618580ec4c1f2fe76176c6ea0ead3710a430d9a2bf8acc5d -size 13352 +oid sha256:b1bac7bec0c22e9530ef2428c4233be7a1c3554c653b6344a2d7b981c5455920 +size 14142 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index 67b0b365b..fd9b15b73 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:a41bf44780feefa108a230ae617830445791bde16d712ac35530350d5d009481 -size 9045 +oid sha256:3667467ff1cf2ce210ec1e1555b40bba827008c5ee40d25ccaf082d2718c6d77 +size 10144 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png index d8e56eb2a..649a05fc4 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:a103b51df184d5480438e8b537106432205a6d86f2927ab1bd507fe8ed3bb29b -size 7656 +oid sha256:d06b03948190e2d6408c339b97ec3f3e2104ffc7da61f5935b7df8bb89c9d7aa +size 8813 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png index f44900fa5..70c4bfe8f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf4236b1a8f63d184cd780c334d9f996e4d47817a96a29f0d81658d2d897597f -size 10529 +oid sha256:b2be8ebcc7d8cc7b3824ae27c57969c0d1bc2d5affb8f3f9df687fb3d1860280 +size 11567 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png index 7329c49cf..a5bda4b8f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7a63953853f526b83f80d63335b03e60258ea9a3416d19f8ed57d746b5c551d -size 21557 +oid sha256:934263e4413e48ea3abf8b53e213f3a61459b697b30cf05436e2d2e6a3d48e3c +size 22356 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png index e1a15cf7d..e49bb4414 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f7d802a4de7e30f8d254cab6d9ca127866c104c1738103bc4a579917e8f42d3 -size 9850 +oid sha256:eb3230e609246415501d89984bb59ee1dad1241b8054009e7a5108efe3965904 +size 10880 From 178f3c91980c1d49b7b55be741dd8a0c8e735ebd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 18 Nov 2025 15:30:02 +0100 Subject: [PATCH 316/388] Add `ScrollArea::content_margin` (#7722) * Part of https://github.com/emilk/egui/issues/5605 * Part of https://github.com/emilk/egui/issues/3385 --- crates/egui/src/containers/scroll_area.rs | 36 +++++++++++++++++++++-- crates/egui/src/style.rs | 19 ++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 4d952d315..5ed4c31f3 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,8 +1,11 @@ +//! See [`ScrollArea`] for docs. + #![allow(clippy::needless_range_loop)] use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; use emath::GuiRounding as _; +use epaint::Margin; use crate::{ Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, @@ -258,7 +261,7 @@ impl AddAssign for ScrollSource { /// ### Coordinate system /// * content: size of contents (generally large; that's why we want scroll bars) /// * outer: size of scroll area including scroll bar(s) -/// * inner: excluding scroll bar(s). The area we clip the contents to. +/// * inner: excluding scroll bar(s). The area we clip the contents to. Includes `content_margin`. /// /// If the floating scroll bars settings is turned on then `inner == outer`. /// @@ -294,6 +297,8 @@ pub struct ScrollArea { scroll_source: ScrollSource, wheel_scroll_multiplier: Vec2, + content_margin: Option, + /// If true for vertical or horizontal the scroll wheel will stick to the /// end position until user manually changes position. It will become true /// again once scroll handle makes contact with end. @@ -346,6 +351,7 @@ impl ScrollArea { on_drag_cursor: None, scroll_source: ScrollSource::default(), wheel_scroll_multiplier: Vec2::splat(1.0), + content_margin: None, stick_to_end: Vec2b::FALSE, animated: true, } @@ -593,6 +599,18 @@ impl ScrollArea { self.direction_enabled[0] || self.direction_enabled[1] } + /// Extra margin added around the contents. + /// + /// The scroll bars will be either on top of this margin, or outside of it, + /// depending on the value of [`crate::style::ScrollStyle::floating`]. + /// + /// Default: [`crate::style::ScrollStyle::content_margin`]. + #[inline] + pub fn content_margin(mut self, margin: impl Into) -> Self { + self.content_margin = Some(margin.into()); + self + } + /// The scroll handle will stick to the rightmost position even while the content size /// changes dynamically. This can be useful to simulate text scrollers coming in from right /// hand side. The scroll handle remains stuck until user manually changes position. Once "unstuck" @@ -644,7 +662,7 @@ struct Prepared { scroll_bar_visibility: ScrollBarVisibility, scroll_bar_rect: Option, - /// Where on the screen the content is (excludes scroll bars). + /// Where on the screen the content is (excludes scroll bars; includes `content_margin`). inner_rect: Rect, content_ui: Ui, @@ -683,6 +701,7 @@ impl ScrollArea { on_drag_cursor, scroll_source, wheel_scroll_multiplier, + content_margin: _, // Used elsewhere stick_to_end, animated, } = self; @@ -983,10 +1002,21 @@ impl ScrollArea { ui: &mut Ui, add_contents: Box R + 'c>, ) -> ScrollAreaOutput { + let margin = self + .content_margin + .unwrap_or_else(|| ui.spacing().scroll.content_margin); + let mut prepared = self.begin(ui); let id = prepared.id; let inner_rect = prepared.inner_rect; - let inner = add_contents(&mut prepared.content_ui, prepared.viewport); + + let inner = crate::Frame::NONE + .inner_margin(margin) + .show(&mut prepared.content_ui, |ui| { + add_contents(ui, prepared.viewport) + }) + .inner; + let (content_size, state) = prepared.end(ui); ScrollAreaOutput { inner, diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 9982c05bb..b77536002 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -508,6 +508,12 @@ pub struct ScrollStyle { /// it more promiment. pub floating: bool, + /// Extra margin added around the contents of a [`crate::ScrollArea`]. + /// + /// The scroll bars will be either on top of this margin, or outside of it, + /// depending on the value of [`Self::floating`]. + pub content_margin: Margin, + /// The width of the scroll bars at it largest. pub bar_width: f32, @@ -591,6 +597,7 @@ impl ScrollStyle { pub fn solid() -> Self { Self { floating: false, + content_margin: Margin::ZERO, bar_width: 6.0, handle_min_length: 12.0, bar_inner_margin: 4.0, @@ -672,6 +679,9 @@ impl ScrollStyle { pub fn details_ui(&mut self, ui: &mut Ui) { let Self { floating, + + content_margin, + bar_width, handle_min_length, bar_inner_margin, @@ -695,6 +705,11 @@ impl ScrollStyle { ui.selectable_value(floating, true, "Floating"); }); + ui.horizontal(|ui| { + ui.label("Content margin:"); + content_margin.ui(ui); + }); + ui.horizontal(|ui| { ui.add(DragValue::new(bar_width).range(0.0..=32.0)); ui.label("Full bar width"); @@ -1824,6 +1839,10 @@ impl Spacing { ui.add(window_margin); ui.end_row(); + ui.label("ScrollArea margin"); + scroll.content_margin.ui(ui); + ui.end_row(); + ui.label("Menu margin"); ui.add(menu_margin); ui.end_row(); From 5b6a0196f91bfd5222d42c8f47595a8c297cc815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Par=C3=A9-Simard?= Date: Tue, 18 Nov 2025 09:46:01 -0500 Subject: [PATCH 317/388] Add `Panel` to replace `SidePanel` and `TopBottomPanel` (#5659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This combines `SidePanel` and `TopBottomPanel` into a single `Panel`. The old types are still there as type aliases, but are deprecated. `.min_width(…)` etc are now called `.min_size(…)` etc. Again, the old names are still there, but deprecated. (edited by @emilk) --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/epi.rs | 2 +- crates/egui/src/containers/mod.rs | 4 +- crates/egui/src/containers/panel.rs | 1395 ++++++++--------- crates/egui/src/context.rs | 2 +- crates/egui/src/lib.rs | 6 +- crates/egui/src/menu.rs | 2 +- crates/egui/src/ui.rs | 2 +- crates/egui/src/ui_stack.rs | 8 +- .../src/accessibility_inspector.rs | 11 +- crates/egui_demo_app/src/apps/http_app.rs | 2 +- crates/egui_demo_app/src/apps/image_viewer.rs | 6 +- crates/egui_demo_app/src/wrap_app.rs | 4 +- .../src/demo/demo_app_windows.rs | 10 +- crates/egui_demo_lib/src/demo/panels.rs | 20 +- crates/egui_demo_lib/src/demo/tooltips.rs | 2 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- crates/egui_glow/examples/pure_glow.rs | 2 +- examples/hello_android/src/lib.rs | 2 +- tests/test_size_pass/src/main.rs | 2 +- tests/test_ui_stack/src/main.rs | 6 +- 20 files changed, 695 insertions(+), 795 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index d6c9a10b8..a7bcfd6ef 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -137,7 +137,7 @@ impl CreationContext<'_> { pub trait App { /// Called each time the UI needs repainting, which may be many times per second. /// - /// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. + /// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. /// /// The [`egui::Context`] can be cloned and saved if you like. /// diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index 4312385da..a8f3306e9 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -1,4 +1,4 @@ -//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`SidePanel`], etc. +//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`Panel`], etc. //! //! For instance, a [`Frame`] adds a frame and background to some contained UI. @@ -27,7 +27,7 @@ pub use { frame::Frame, modal::{Modal, ModalResponse}, old_popup::*, - panel::{CentralPanel, SidePanel, TopBottomPanel}, + panel::*, popup::*, resize::Resize, scene::{DragPanButtons, Scene}, diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 6e582b428..eb60f5f21 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -15,7 +15,7 @@ //! //! Add your [`crate::Window`]:s after any top-level panels. -use emath::GuiRounding as _; +use emath::{GuiRounding as _, Pos2}; use crate::{ Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef, @@ -51,21 +51,25 @@ impl PanelState { // ---------------------------------------------------------------------------- -/// [`Left`](Side::Left) or [`Right`](Side::Right) +/// [`Left`](VerticalSide::Left) or [`Right`](VerticalSide::Right) #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Side { +enum VerticalSide { Left, Right, } -impl Side { - fn opposite(self) -> Self { +impl VerticalSide { + pub fn opposite(self) -> Self { match self { Self::Left => Self::Right, Self::Right => Self::Left, } } + /// `self` is the _fixed_ side. + /// + /// * Left panels are resized on their right side + /// * Right panels are resized on their left side fn set_rect_width(self, rect: &mut Rect, width: f32) { match self { Self::Left => rect.max.x = rect.min.x + width, @@ -73,22 +77,211 @@ impl Side { } } - fn side_x(self, rect: Rect) -> f32 { - match self { - Self::Left => rect.left(), - Self::Right => rect.right(), - } - } - fn sign(self) -> f32 { match self { Self::Left => -1.0, Self::Right => 1.0, } } + + fn side_x(self, rect: Rect) -> f32 { + match self { + Self::Left => rect.left(), + Self::Right => rect.right(), + } + } } -/// A panel that covers the entire left or right side of a [`Ui`] or screen. +/// [`Top`](HorizontalSide::Top) or [`Bottom`](HorizontalSide::Bottom) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum HorizontalSide { + Top, + Bottom, +} + +impl HorizontalSide { + pub fn opposite(self) -> Self { + match self { + Self::Top => Self::Bottom, + Self::Bottom => Self::Top, + } + } + + /// `self` is the _fixed_ side. + /// + /// * Top panels are resized on their bottom side + /// * Bottom panels are resized upwards + fn set_rect_height(self, rect: &mut Rect, height: f32) { + match self { + Self::Top => rect.max.y = rect.min.y + height, + Self::Bottom => rect.min.y = rect.max.y - height, + } + } + + fn sign(self) -> f32 { + match self { + Self::Top => -1.0, + Self::Bottom => 1.0, + } + } + + fn side_y(self, rect: Rect) -> f32 { + match self { + Self::Top => rect.top(), + Self::Bottom => rect.bottom(), + } + } +} + +// Intentionally private because I'm not sure of the naming. +// TODO(emilk): decide on good names and make public. +// "VerticalSide" and "HorizontalSide" feels inverted to me. +/// [`Horizontal`](PanelSide::Horizontal) or [`Vertical`](PanelSide::Vertical) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PanelSide { + /// Left or right. + Vertical(VerticalSide), + + /// Top or bottom + Horizontal(HorizontalSide), +} + +impl From for PanelSide { + fn from(side: HorizontalSide) -> Self { + Self::Horizontal(side) + } +} + +impl From for PanelSide { + fn from(side: VerticalSide) -> Self { + Self::Vertical(side) + } +} + +impl PanelSide { + pub const LEFT: Self = Self::Vertical(VerticalSide::Left); + pub const RIGHT: Self = Self::Vertical(VerticalSide::Right); + pub const TOP: Self = Self::Horizontal(HorizontalSide::Top); + pub const BOTTOM: Self = Self::Horizontal(HorizontalSide::Bottom); + + /// Resize by keeping the [`self`] side fixed, and moving the opposite side. + fn set_rect_size(self, rect: &mut Rect, size: f32) { + match self { + Self::Vertical(side) => side.set_rect_width(rect, size), + Self::Horizontal(side) => side.set_rect_height(rect, size), + } + } + + fn ui_kind(self) -> UiKind { + match self { + Self::Vertical(side) => match side { + VerticalSide::Left => UiKind::LeftPanel, + VerticalSide::Right => UiKind::RightPanel, + }, + Self::Horizontal(side) => match side { + HorizontalSide::Top => UiKind::TopPanel, + HorizontalSide::Bottom => UiKind::BottomPanel, + }, + } + } +} + +// ---------------------------------------------------------------------------- + +/// Intermediate structure to abstract some portion of [`Panel::show_inside`](Panel::show_inside). +struct PanelSizer<'a> { + panel: &'a Panel, + frame: Frame, + available_rect: Rect, + size: f32, + panel_rect: Rect, +} + +impl<'a> PanelSizer<'a> { + fn new(panel: &'a Panel, ui: &Ui) -> Self { + let frame = panel + .frame + .unwrap_or_else(|| Frame::side_top_panel(ui.style())); + let available_rect = ui.available_rect_before_wrap(); + let size = PanelSizer::get_size_from_state_or_default(panel, ui, frame); + let panel_rect = PanelSizer::panel_rect(panel, available_rect, size); + + Self { + panel, + frame, + available_rect, + size, + panel_rect, + } + } + + fn get_size_from_state_or_default(panel: &Panel, ui: &Ui, frame: Frame) -> f32 { + if let Some(state) = PanelState::load(ui.ctx(), panel.id) { + match panel.side { + PanelSide::Vertical(_) => state.rect.width(), + PanelSide::Horizontal(_) => state.rect.height(), + } + } else { + match panel.side { + PanelSide::Vertical(_) => panel.default_size.unwrap_or_else(|| { + ui.style().spacing.interact_size.x + frame.inner_margin.sum().x + }), + PanelSide::Horizontal(_) => panel.default_size.unwrap_or_else(|| { + ui.style().spacing.interact_size.y + frame.inner_margin.sum().y + }), + } + } + } + + fn panel_rect(panel: &Panel, available_rect: Rect, mut size: f32) -> Rect { + let side = panel.side; + let size_range = panel.size_range; + + let mut panel_rect = available_rect; + + match side { + PanelSide::Vertical(_) => { + size = clamp_to_range(size, size_range).at_most(available_rect.width()); + } + PanelSide::Horizontal(_) => { + size = clamp_to_range(size, size_range).at_most(available_rect.height()); + } + } + side.set_rect_size(&mut panel_rect, size); + panel_rect + } + + fn prepare_resizing_response(&mut self, is_resizing: bool, pointer: Option) { + let side = self.panel.side; + let size_range = self.panel.size_range; + + if is_resizing && pointer.is_some() { + let pointer = pointer.unwrap(); + + match side { + PanelSide::Vertical(side) => { + self.size = (pointer.x - side.side_x(self.panel_rect)).abs(); + self.size = + clamp_to_range(self.size, size_range).at_most(self.available_rect.width()); + } + PanelSide::Horizontal(side) => { + self.size = (pointer.y - side.side_y(self.panel_rect)).abs(); + self.size = + clamp_to_range(self.size, size_range).at_most(self.available_rect.height()); + } + } + + side.set_rect_size(&mut self.panel_rect, self.size); + } + } +} + +// ---------------------------------------------------------------------------- + +/// A panel that covers an entire side +/// ([`left`](Panel::left), [`right`](Panel::right), +/// [`top`](Panel::top) or [`bottom`](Panel::bottom)) +/// of a [`Ui`] or screen. /// /// The order in which you add panels matter! /// The first panel you add will always be the outermost, and the last you add will always be the innermost. @@ -99,45 +292,83 @@ impl Side { /// /// ``` /// # egui::__run_test_ctx(|ctx| { -/// egui::SidePanel::left("my_left_panel").show(ctx, |ui| { +/// egui::Panel::left("my_left_panel").show(ctx, |ui| { /// ui.label("Hello World!"); /// }); /// # }); /// ``` -/// -/// See also [`TopBottomPanel`]. #[must_use = "You should call .show()"] -pub struct SidePanel { - side: Side, +pub struct Panel { + side: PanelSide, id: Id, frame: Option, resizable: bool, show_separator_line: bool, - default_width: f32, - width_range: Rangef, + + /// The size is defined as being either the width for a Vertical Panel + /// or the height for a Horizontal Panel. + default_size: Option, + + /// The size is defined as being either the width for a Vertical Panel + /// or the height for a Horizontal Panel. + size_range: Rangef, } -impl SidePanel { +impl Panel { + /// Create a left panel. + /// /// The id should be globally unique, e.g. `Id::new("my_left_panel")`. pub fn left(id: impl Into) -> Self { - Self::new(Side::Left, id) + Self::new(PanelSide::LEFT, id) } + /// Create a right panel. + /// /// The id should be globally unique, e.g. `Id::new("my_right_panel")`. pub fn right(id: impl Into) -> Self { - Self::new(Side::Right, id) + Self::new(PanelSide::RIGHT, id) } + /// Create a top panel. + /// + /// The id should be globally unique, e.g. `Id::new("my_top_panel")`. + /// + /// By default this is NOT resizable. + pub fn top(id: impl Into) -> Self { + Self::new(PanelSide::TOP, id).resizable(false) + } + + /// Create a bottom panel. + /// + /// The id should be globally unique, e.g. `Id::new("my_bottom_panel")`. + /// + /// By default this is NOT resizable. + pub fn bottom(id: impl Into) -> Self { + Self::new(PanelSide::BOTTOM, id).resizable(false) + } + + /// Create a panel. + /// /// The id should be globally unique, e.g. `Id::new("my_panel")`. - pub fn new(side: Side, id: impl Into) -> Self { + fn new(side: PanelSide, id: impl Into) -> Self { + let default_size: Option = match side { + PanelSide::Vertical(_) => Some(200.0), + PanelSide::Horizontal(_) => None, + }; + + let size_range: Rangef = match side { + PanelSide::Vertical(_) => Rangef::new(96.0, f32::INFINITY), + PanelSide::Horizontal(_) => Rangef::new(20.0, f32::INFINITY), + }; + Self { side, id: id.into(), frame: None, resizable: true, show_separator_line: true, - default_width: 200.0, - width_range: Rangef::new(96.0, f32::INFINITY), + default_size, + size_range, } } @@ -170,45 +401,47 @@ impl SidePanel { self } - /// The initial wrapping width of the [`SidePanel`], including margins. + /// The initial wrapping width of the [`Panel`], including margins. #[inline] - pub fn default_width(mut self, default_width: f32) -> Self { - self.default_width = default_width; - self.width_range = Rangef::new( - self.width_range.min.at_most(default_width), - self.width_range.max.at_least(default_width), + pub fn default_size(mut self, default_size: f32) -> Self { + self.default_size = Some(default_size); + self.size_range = Rangef::new( + self.size_range.min.at_most(default_size), + self.size_range.max.at_least(default_size), ); self } - /// Minimum width of the panel, including margins. + /// Minimum size of the panel, including margins. #[inline] - pub fn min_width(mut self, min_width: f32) -> Self { - self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width)); + pub fn min_size(mut self, min_size: f32) -> Self { + self.size_range = Rangef::new(min_size, self.size_range.max.at_least(min_size)); self } - /// Maximum width of the panel, including margins. + /// Maximum size of the panel, including margins. #[inline] - pub fn max_width(mut self, max_width: f32) -> Self { - self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width); + pub fn max_size(mut self, max_size: f32) -> Self { + self.size_range = Rangef::new(self.size_range.min.at_most(max_size), max_size); self } - /// The allowable width range for the panel, including margins. + /// The allowable size range for the panel, including margins. #[inline] - pub fn width_range(mut self, width_range: impl Into) -> Self { - let width_range = width_range.into(); - self.default_width = clamp_to_range(self.default_width, width_range); - self.width_range = width_range; + pub fn size_range(mut self, size_range: impl Into) -> Self { + let size_range = size_range.into(); + self.default_size = self + .default_size + .map(|default_size| clamp_to_range(default_size, size_range)); + self.size_range = size_range; self } - /// Enforce this exact width, including margins. + /// Enforce this exact size, including margins. #[inline] - pub fn exact_width(mut self, width: f32) -> Self { - self.default_width = width; - self.width_range = Rangef::point(width); + pub fn exact_size(mut self, size: f32) -> Self { + self.default_size = Some(size); + self.size_range = Rangef::point(size); self } @@ -220,7 +453,61 @@ impl SidePanel { } } -impl SidePanel { +// Deprecated +impl Panel { + #[deprecated = "Renamed default_size"] + pub fn default_width(self, default_size: f32) -> Self { + self.default_size(default_size) + } + + #[deprecated = "Renamed min_size"] + pub fn min_width(self, min_size: f32) -> Self { + self.min_size(min_size) + } + + #[deprecated = "Renamed max_size"] + pub fn max_width(self, max_size: f32) -> Self { + self.max_size(max_size) + } + + #[deprecated = "Renamed size_range"] + pub fn width_range(self, size_range: impl Into) -> Self { + self.size_range(size_range) + } + + #[deprecated = "Renamed exact_size"] + pub fn exact_width(self, size: f32) -> Self { + self.exact_size(size) + } + + #[deprecated = "Renamed default_size"] + pub fn default_height(self, default_size: f32) -> Self { + self.default_size(default_size) + } + + #[deprecated = "Renamed min_size"] + pub fn min_height(self, min_size: f32) -> Self { + self.min_size(min_size) + } + + #[deprecated = "Renamed max_size"] + pub fn max_height(self, max_size: f32) -> Self { + self.max_size(max_size) + } + + #[deprecated = "Renamed size_range"] + pub fn height_range(self, size_range: impl Into) -> Self { + self.size_range(size_range) + } + + #[deprecated = "Renamed exact_size"] + pub fn exact_height(self, size: f32) -> Self { + self.exact_size(size) + } +} + +// Public showing methods +impl Panel { /// Show the panel inside a [`Ui`]. pub fn show_inside( self, @@ -230,70 +517,170 @@ impl SidePanel { self.show_inside_dyn(ui, Box::new(add_contents)) } + /// Show the panel at the top level. + pub fn show( + self, + ctx: &Context, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_dyn(ctx, Box::new(add_contents)) + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated( + self, + ctx: &Context, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); + + let animated_panel = self.get_animated_panel(ctx, is_expanded)?; + + if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + animated_panel.show(ctx, |_ui| {}); + None + } else { + // Show the real panel: + Some(animated_panel.show(ctx, add_contents)) + } + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated_inside( + self, + ui: &mut Ui, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded); + + // Get either the fake or the real panel to animate + let animated_panel = self.get_animated_panel(ui.ctx(), is_expanded)?; + + if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + animated_panel.show_inside(ui, |_ui| {}); + None + } else { + // Show the real panel: + Some(animated_panel.show_inside(ui, add_contents)) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between( + ctx: &Context, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> Option> { + let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); + + // Get either the fake or the real panel to animate + let animated_between_panel = + Self::get_animated_between_panel(ctx, is_expanded, collapsed_panel, expanded_panel); + + if 0.0 == how_expanded { + Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } else if how_expanded < 1.0 { + // Show animation: + animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded)); + None + } else { + Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between_inside( + ui: &mut Ui, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> InnerResponse { + let how_expanded = + animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded); + + let animated_between_panel = Self::get_animated_between_panel( + ui.ctx(), + is_expanded, + collapsed_panel, + expanded_panel, + ); + + if 0.0 == how_expanded { + animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else if how_expanded < 1.0 { + // Show animation: + animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else { + animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } + } +} + +// Private methods to support the various show methods +impl Panel { /// Show the panel inside a [`Ui`]. fn show_inside_dyn<'c, R>( self, ui: &mut Ui, add_contents: Box R + 'c>, ) -> InnerResponse { - let Self { - side, - id, - frame, - resizable, - show_separator_line, - default_width, - width_range, - } = self; + let side = self.side; + let id = self.id; + let resizable = self.resizable; + let show_separator_line = self.show_separator_line; + let size_range = self.size_range; - let available_rect = ui.available_rect_before_wrap(); - let mut panel_rect = available_rect; - let mut width = default_width; - { - if let Some(state) = PanelState::load(ui.ctx(), id) { - width = state.rect.width(); - } - width = clamp_to_range(width, width_range).at_most(available_rect.width()); - side.set_rect_width(&mut panel_rect, width); - ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel"); + // Define the sizing of the panel. + let mut panel_sizer = PanelSizer::new(&self, ui); + + // Check for duplicate id + ui.ctx() + .check_for_id_clash(id, panel_sizer.panel_rect, "Panel"); + + if self.resizable { + // Prepare the resizable panel to avoid frame latency in the resize + self.prepare_resizable_panel(&mut panel_sizer, ui); } - let resize_id = id.with("__resize"); - let mut resize_hover = false; - let mut is_resizing = false; - if resizable { - // First we read the resize interaction results, to avoid frame latency in the resize: - if let Some(resize_response) = ui.ctx().read_response(resize_id) { - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); - - if is_resizing && let Some(pointer) = resize_response.interact_pointer_pos() { - width = (pointer.x - side.side_x(panel_rect)).abs(); - width = clamp_to_range(width, width_range).at_most(available_rect.width()); - side.set_rect_width(&mut panel_rect, width); - } - } - } - - panel_rect = panel_rect.round_ui(); + // NOTE(shark98): This must be **after** the resizable preparation, as the size + // may change and round_ui() uses the size. + panel_sizer.panel_rect = panel_sizer.panel_rect.round_ui(); let mut panel_ui = ui.new_child( UiBuilder::new() .id_salt(id) - .ui_stack_info(UiStackInfo::new(match side { - Side::Left => UiKind::LeftPanel, - Side::Right => UiKind::RightPanel, - })) - .max_rect(panel_rect) + .ui_stack_info(UiStackInfo::new(side.ui_kind())) + .max_rect(panel_sizer.panel_rect) .layout(Layout::top_down(Align::Min)), ); - panel_ui.expand_to_include_rect(panel_rect); - panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) + panel_ui.expand_to_include_rect(panel_sizer.panel_rect); + panel_ui.set_clip_rect(panel_sizer.panel_rect); // If we overflow, don't do so visibly (#4475) + + let inner_response = panel_sizer.frame.show(&mut panel_ui, |ui| { + match side { + PanelSide::Vertical(_) => { + ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height + ui.set_min_width( + (size_range.min - panel_sizer.frame.inner_margin.sum().x).at_least(0.0), + ); + } + PanelSide::Horizontal(_) => { + ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width + ui.set_min_height( + (size_range.min - panel_sizer.frame.inner_margin.sum().y).at_least(0.0), + ); + } + } - let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); - let inner_response = frame.show(&mut panel_ui, |ui| { - ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height - ui.set_min_width((width_range.min - frame.inner_margin.sum().x).at_least(0.0)); add_contents(ui) }); @@ -302,44 +689,31 @@ impl SidePanel { { let mut cursor = ui.cursor(); match side { - Side::Left => { - cursor.min.x = rect.max.x; - } - Side::Right => { - cursor.max.x = rect.min.x; - } + PanelSide::Vertical(side) => match side { + VerticalSide::Left => cursor.min.x = rect.max.x, + VerticalSide::Right => cursor.max.x = rect.min.x, + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => cursor.min.y = rect.max.y, + HorizontalSide::Bottom => cursor.max.y = rect.min.y, + }, } ui.set_cursor(cursor); } + ui.expand_to_include_rect(rect); + let mut resize_hover = false; + let mut is_resizing = false; if resizable { - // Now we do the actual resize interaction, on top of all the contents. - // Otherwise its input could be eaten by the contents, e.g. a + // Now we do the actual resize interaction, on top of all the contents, + // otherwise its input could be eaten by the contents, e.g. a // `ScrollArea` on either side of the panel boundary. - let resize_x = side.opposite().side_x(panel_rect); - let resize_rect = Rect::from_x_y_ranges(resize_x..=resize_x, panel_rect.y_range()) - .expand2(vec2(ui.style().interaction.resize_grab_radius_side, 0.0)); - let resize_response = ui.interact(resize_rect, resize_id, Sense::drag()); - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); + (resize_hover, is_resizing) = self.resize_panel(&panel_sizer, ui); } if resize_hover || is_resizing { - let cursor_icon = if width <= width_range.min { - match self.side { - Side::Left => CursorIcon::ResizeEast, - Side::Right => CursorIcon::ResizeWest, - } - } else if width < width_range.max { - CursorIcon::ResizeHorizontal - } else { - match self.side { - Side::Left => CursorIcon::ResizeWest, - Side::Right => CursorIcon::ResizeEast, - } - }; - ui.ctx().set_cursor_icon(cursor_icon); + ui.ctx().set_cursor_icon(self.cursor_icon(&panel_sizer)); } PanelState { rect }.store(ui.ctx(), id); @@ -356,25 +730,23 @@ impl SidePanel { Stroke::NONE }; // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done - let resize_x = side.opposite().side_x(rect); - - // Make sure the line is on the inside of the panel: - let resize_x = resize_x + 0.5 * side.sign() * stroke.width; - ui.painter().vline(resize_x, panel_rect.y_range(), stroke); + match side { + PanelSide::Vertical(side) => { + let x = side.opposite().side_x(rect) + 0.5 * side.sign() * stroke.width; + ui.painter() + .vline(x, panel_sizer.panel_rect.y_range(), stroke); + } + PanelSide::Horizontal(side) => { + let y = side.opposite().side_y(rect) + 0.5 * side.sign() * stroke.width; + ui.painter() + .hline(panel_sizer.panel_rect.x_range(), y, stroke); + } + } } inner_response } - /// Show the panel at the top level. - pub fn show( - self, - ctx: &Context, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_dyn(ctx, Box::new(add_contents)) - } - /// Show the panel at the top level. fn show_dyn<'c, R>( self, @@ -399,663 +771,177 @@ impl SidePanel { let rect = inner_response.response.rect; match side { - Side::Left => ctx.pass_state_mut(|state| { - state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)); - }), - Side::Right => ctx.pass_state_mut(|state| { - state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)); - }), + PanelSide::Vertical(side) => match side { + VerticalSide::Left => ctx.pass_state_mut(|state| { + state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)); + }), + VerticalSide::Right => ctx.pass_state_mut(|state| { + state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)); + }), + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => { + ctx.pass_state_mut(|state| { + state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max)); + }); + } + HorizontalSide::Bottom => { + ctx.pass_state_mut(|state| { + state.allocate_bottom_panel(Rect::from_min_max( + rect.min, + available_rect.max, + )); + }); + } + }, } inner_response } - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated( - self, - ctx: &Context, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); + fn prepare_resizable_panel(&self, panel_sizer: &mut PanelSizer<'_>, ui: &Ui) { + let resize_id = self.id.with("__resize"); + let resize_response = ui.ctx().read_response(resize_id); - if 0.0 == how_expanded { - None - } else if how_expanded < 1.0 { - // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its width. - // Then we can actually paint it as it animates. - let expanded_width = PanelState::load(ctx, self.id) - .map_or(self.default_width, |state| state.rect.width()); - let fake_width = how_expanded * expanded_width; - Self { - id: self.id.with("animating_panel"), - ..self + if resize_response.is_some() { + let resize_response = resize_response.unwrap(); + + // NOTE(sharky98): The original code was initializing to + // false first, but it doesn't seem necessary. + let is_resizing = resize_response.dragged(); + let pointer = resize_response.interact_pointer_pos(); + panel_sizer.prepare_resizing_response(is_resizing, pointer); + } + } + + fn resize_panel(&self, panel_sizer: &PanelSizer<'_>, ui: &Ui) -> (bool, bool) { + let (resize_x, resize_y, amount): (Rangef, Rangef, Vec2) = match self.side { + PanelSide::Vertical(side) => { + let resize_x = side.opposite().side_x(panel_sizer.panel_rect); + let resize_y = panel_sizer.panel_rect.y_range(); + ( + Rangef::from(resize_x..=resize_x), + resize_y, + vec2(ui.style().interaction.resize_grab_radius_side, 0.0), + ) } - .resizable(false) - .exact_width(fake_width) - .show(ctx, |_ui| {}); - None - } else { - // Show the real panel: - Some(self.show(ctx, add_contents)) - } - } - - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated_inside( - self, - ui: &mut Ui, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - None - } else if how_expanded < 1.0 { - // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its width. - // Then we can actually paint it as it animates. - let expanded_width = PanelState::load(ui.ctx(), self.id) - .map_or(self.default_width, |state| state.rect.width()); - let fake_width = how_expanded * expanded_width; - Self { - id: self.id.with("animating_panel"), - ..self + PanelSide::Horizontal(side) => { + let resize_x = panel_sizer.panel_rect.x_range(); + let resize_y = side.opposite().side_y(panel_sizer.panel_rect); + ( + resize_x, + Rangef::from(resize_y..=resize_y), + vec2(0.0, ui.style().interaction.resize_grab_radius_side), + ) } - .resizable(false) - .exact_width(fake_width) - .show_inside(ui, |_ui| {}); - None - } else { - // Show the real panel: - Some(self.show_inside(ui, add_contents)) - } - } - - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between( - ctx: &Context, - is_expanded: bool, - collapsed_panel: Self, - expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded))) - } else if how_expanded < 1.0 { - // Show animation: - let collapsed_width = PanelState::load(ctx, collapsed_panel.id) - .map_or(collapsed_panel.default_width, |state| state.rect.width()); - let expanded_width = PanelState::load(ctx, expanded_panel.id) - .map_or(expanded_panel.default_width, |state| state.rect.width()); - let fake_width = lerp(collapsed_width..=expanded_width, how_expanded); - Self { - id: expanded_panel.id.with("animating_panel"), - ..expanded_panel - } - .resizable(false) - .exact_width(fake_width) - .show(ctx, |ui| add_contents(ui, how_expanded)); - None - } else { - Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded))) - } - } - - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between_inside( - ui: &mut Ui, - is_expanded: bool, - collapsed_panel: Self, - expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> InnerResponse { - let how_expanded = - animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else if how_expanded < 1.0 { - // Show animation: - let collapsed_width = PanelState::load(ui.ctx(), collapsed_panel.id) - .map_or(collapsed_panel.default_width, |state| state.rect.width()); - let expanded_width = PanelState::load(ui.ctx(), expanded_panel.id) - .map_or(expanded_panel.default_width, |state| state.rect.width()); - let fake_width = lerp(collapsed_width..=expanded_width, how_expanded); - Self { - id: expanded_panel.id.with("animating_panel"), - ..expanded_panel - } - .resizable(false) - .exact_width(fake_width) - .show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else { - expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } - } -} - -// ---------------------------------------------------------------------------- - -/// [`Top`](TopBottomSide::Top) or [`Bottom`](TopBottomSide::Bottom) -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum TopBottomSide { - Top, - Bottom, -} - -impl TopBottomSide { - fn opposite(self) -> Self { - match self { - Self::Top => Self::Bottom, - Self::Bottom => Self::Top, - } - } - - fn set_rect_height(self, rect: &mut Rect, height: f32) { - match self { - Self::Top => rect.max.y = rect.min.y + height, - Self::Bottom => rect.min.y = rect.max.y - height, - } - } - - fn side_y(self, rect: Rect) -> f32 { - match self { - Self::Top => rect.top(), - Self::Bottom => rect.bottom(), - } - } - - fn sign(self) -> f32 { - match self { - Self::Top => -1.0, - Self::Bottom => 1.0, - } - } -} - -/// A panel that covers the entire top or bottom of a [`Ui`] or screen. -/// -/// The order in which you add panels matter! -/// The first panel you add will always be the outermost, and the last you add will always be the innermost. -/// -/// ⚠ Always add any [`CentralPanel`] last. -/// -/// See the [module level docs](crate::containers::panel) for more details. -/// -/// ``` -/// # egui::__run_test_ctx(|ctx| { -/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| { -/// ui.label("Hello World!"); -/// }); -/// # }); -/// ``` -/// -/// See also [`SidePanel`]. -#[must_use = "You should call .show()"] -pub struct TopBottomPanel { - side: TopBottomSide, - id: Id, - frame: Option, - resizable: bool, - show_separator_line: bool, - default_height: Option, - height_range: Rangef, -} - -impl TopBottomPanel { - /// The id should be globally unique, e.g. `Id::new("my_top_panel")`. - pub fn top(id: impl Into) -> Self { - Self::new(TopBottomSide::Top, id) - } - - /// The id should be globally unique, e.g. `Id::new("my_bottom_panel")`. - pub fn bottom(id: impl Into) -> Self { - Self::new(TopBottomSide::Bottom, id) - } - - /// The id should be globally unique, e.g. `Id::new("my_panel")`. - pub fn new(side: TopBottomSide, id: impl Into) -> Self { - Self { - side, - id: id.into(), - frame: None, - resizable: false, - show_separator_line: true, - default_height: None, - height_range: Rangef::new(20.0, f32::INFINITY), - } - } - - /// Can panel be resized by dragging the edge of it? - /// - /// Default is `false`. - /// - /// If you want your panel to be resizable you also need to make the ui use - /// the available space. - /// - /// This can be done by using [`Ui::take_available_space`], or using a - /// widget in it that takes up more space as you resize it, such as: - /// * Wrapping text ([`Ui::horizontal_wrapped`]). - /// * A [`crate::ScrollArea`]. - /// * A [`crate::Separator`]. - /// * A [`crate::TextEdit`]. - /// * … - #[inline] - pub fn resizable(mut self, resizable: bool) -> Self { - self.resizable = resizable; - self - } - - /// Show a separator line, even when not interacting with it? - /// - /// Default: `true`. - #[inline] - pub fn show_separator_line(mut self, show_separator_line: bool) -> Self { - self.show_separator_line = show_separator_line; - self - } - - /// The initial height of the [`TopBottomPanel`], including margins. - /// Defaults to [`crate::style::Spacing::interact_size`].y, plus frame margins. - #[inline] - pub fn default_height(mut self, default_height: f32) -> Self { - self.default_height = Some(default_height); - self.height_range = Rangef::new( - self.height_range.min.at_most(default_height), - self.height_range.max.at_least(default_height), - ); - self - } - - /// Minimum height of the panel, including margins. - #[inline] - pub fn min_height(mut self, min_height: f32) -> Self { - self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height)); - self - } - - /// Maximum height of the panel, including margins. - #[inline] - pub fn max_height(mut self, max_height: f32) -> Self { - self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height); - self - } - - /// The allowable height range for the panel, including margins. - #[inline] - pub fn height_range(mut self, height_range: impl Into) -> Self { - let height_range = height_range.into(); - self.default_height = self - .default_height - .map(|default_height| clamp_to_range(default_height, height_range)); - self.height_range = height_range; - self - } - - /// Enforce this exact height, including margins. - #[inline] - pub fn exact_height(mut self, height: f32) -> Self { - self.default_height = Some(height); - self.height_range = Rangef::point(height); - self - } - - /// Change the background color, margins, etc. - #[inline] - pub fn frame(mut self, frame: Frame) -> Self { - self.frame = Some(frame); - self - } -} - -impl TopBottomPanel { - /// Show the panel inside a [`Ui`]. - pub fn show_inside( - self, - ui: &mut Ui, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_inside_dyn(ui, Box::new(add_contents)) - } - - /// Show the panel inside a [`Ui`]. - fn show_inside_dyn<'c, R>( - self, - ui: &mut Ui, - add_contents: Box R + 'c>, - ) -> InnerResponse { - let Self { - side, - id, - frame, - resizable, - show_separator_line, - default_height, - height_range, - } = self; - - let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); - - let available_rect = ui.available_rect_before_wrap(); - let mut panel_rect = available_rect; - - let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { - state.rect.height() - } else { - default_height - .unwrap_or_else(|| ui.style().spacing.interact_size.y + frame.inner_margin.sum().y) }; - { - height = clamp_to_range(height, height_range).at_most(available_rect.height()); - side.set_rect_height(&mut panel_rect, height); - ui.ctx() - .check_for_id_clash(id, panel_rect, "TopBottomPanel"); - } - let resize_id = id.with("__resize"); - let mut resize_hover = false; - let mut is_resizing = false; - if resizable { - // First we read the resize interaction results, to avoid frame latency in the resize: - if let Some(resize_response) = ui.ctx().read_response(resize_id) { - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); + let resize_id = self.id.with("__resize"); + let resize_rect = Rect::from_x_y_ranges(resize_x, resize_y).expand2(amount); + let resize_response = ui.interact(resize_rect, resize_id, Sense::drag()); - if is_resizing && let Some(pointer) = resize_response.interact_pointer_pos() { - height = (pointer.y - side.side_y(panel_rect)).abs(); - height = clamp_to_range(height, height_range).at_most(available_rect.height()); - side.set_rect_height(&mut panel_rect, height); - } - } - } - - panel_rect = panel_rect.round_ui(); - - let mut panel_ui = ui.new_child( - UiBuilder::new() - .id_salt(id) - .ui_stack_info(UiStackInfo::new(match side { - TopBottomSide::Top => UiKind::TopPanel, - TopBottomSide::Bottom => UiKind::BottomPanel, - })) - .max_rect(panel_rect) - .layout(Layout::top_down(Align::Min)), - ); - panel_ui.expand_to_include_rect(panel_rect); - panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) - - let inner_response = frame.show(&mut panel_ui, |ui| { - ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width - ui.set_min_height((height_range.min - frame.inner_margin.sum().y).at_least(0.0)); - add_contents(ui) - }); - - let rect = inner_response.response.rect; - - { - let mut cursor = ui.cursor(); - match side { - TopBottomSide::Top => { - cursor.min.y = rect.max.y; - } - TopBottomSide::Bottom => { - cursor.max.y = rect.min.y; - } - } - ui.set_cursor(cursor); - } - ui.expand_to_include_rect(rect); - - if resizable { - // Now we do the actual resize interaction, on top of all the contents. - // Otherwise its input could be eaten by the contents, e.g. a - // `ScrollArea` on either side of the panel boundary. - - let resize_y = side.opposite().side_y(panel_rect); - let resize_rect = Rect::from_x_y_ranges(panel_rect.x_range(), resize_y..=resize_y) - .expand2(vec2(0.0, ui.style().interaction.resize_grab_radius_side)); - let resize_response = ui.interact(resize_rect, resize_id, Sense::drag()); - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); - } - - if resize_hover || is_resizing { - let cursor_icon = if height <= height_range.min { - match self.side { - TopBottomSide::Top => CursorIcon::ResizeSouth, - TopBottomSide::Bottom => CursorIcon::ResizeNorth, - } - } else if height < height_range.max { - CursorIcon::ResizeVertical - } else { - match self.side { - TopBottomSide::Top => CursorIcon::ResizeNorth, - TopBottomSide::Bottom => CursorIcon::ResizeSouth, - } - }; - ui.ctx().set_cursor_icon(cursor_icon); - } - - PanelState { rect }.store(ui.ctx(), id); - - { - let stroke = if is_resizing { - ui.style().visuals.widgets.active.fg_stroke // highly visible - } else if resize_hover { - ui.style().visuals.widgets.hovered.fg_stroke // highly visible - } else if show_separator_line { - // TODO(emilk): distinguish resizable from non-resizable - ui.style().visuals.widgets.noninteractive.bg_stroke // dim - } else { - Stroke::NONE - }; - // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done - let resize_y = side.opposite().side_y(rect); - - // Make sure the line is on the inside of the panel: - let resize_y = resize_y + 0.5 * side.sign() * stroke.width; - ui.painter().hline(panel_rect.x_range(), resize_y, stroke); - } - - inner_response + (resize_response.hovered(), resize_response.dragged()) } - /// Show the panel at the top level. - pub fn show( - self, - ctx: &Context, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_dyn(ctx, Box::new(add_contents)) - } - - /// Show the panel at the top level. - fn show_dyn<'c, R>( - self, - ctx: &Context, - add_contents: Box R + 'c>, - ) -> InnerResponse { - let available_rect = ctx.available_rect(); - let side = self.side; - - let mut panel_ui = Ui::new( - ctx.clone(), - self.id, - UiBuilder::new() - .layer_id(LayerId::background()) - .max_rect(available_rect), - ); - panel_ui.set_clip_rect(ctx.content_rect()); - - let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); - let rect = inner_response.response.rect; - - match side { - TopBottomSide::Top => { - ctx.pass_state_mut(|state| { - state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max)); - }); + fn cursor_icon(&self, panel_sizer: &PanelSizer<'_>) -> CursorIcon { + if panel_sizer.size <= self.size_range.min { + match self.side { + PanelSide::Vertical(side) => match side { + VerticalSide::Left => CursorIcon::ResizeEast, + VerticalSide::Right => CursorIcon::ResizeWest, + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => CursorIcon::ResizeSouth, + HorizontalSide::Bottom => CursorIcon::ResizeNorth, + }, } - TopBottomSide::Bottom => { - ctx.pass_state_mut(|state| { - state.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max)); - }); + } else if panel_sizer.size < self.size_range.max { + match self.side { + PanelSide::Vertical(_) => CursorIcon::ResizeHorizontal, + PanelSide::Horizontal(_) => CursorIcon::ResizeVertical, + } + } else { + match self.side { + PanelSide::Vertical(side) => match side { + VerticalSide::Left => CursorIcon::ResizeWest, + VerticalSide::Right => CursorIcon::ResizeEast, + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => CursorIcon::ResizeNorth, + HorizontalSide::Bottom => CursorIcon::ResizeSouth, + }, } } - - inner_response } - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated( - self, - ctx: &Context, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { + /// Get the real or fake panel to animate if `is_expanded` is `true`. + fn get_animated_panel(self, ctx: &Context, is_expanded: bool) -> Option { let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); if 0.0 == how_expanded { None } else if how_expanded < 1.0 { // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its height. + // TODO(emilk): move the panel out-of-screen instead of changing its width. // Then we can actually paint it as it animates. - let expanded_height = PanelState::load(ctx, self.id) - .map(|state| state.rect.height()) - .or(self.default_height) - .unwrap_or_else(|| ctx.style().spacing.interact_size.y); - let fake_height = how_expanded * expanded_height; - Self { - id: self.id.with("animating_panel"), - ..self - } - .resizable(false) - .exact_height(fake_height) - .show(ctx, |_ui| {}); - None + let expanded_size = Self::animated_size(ctx, &self); + let fake_size = how_expanded * expanded_size; + Some( + Self { + id: self.id.with("animating_panel"), + ..self + } + .resizable(false) + .exact_size(fake_size), + ) } else { // Show the real panel: - Some(self.show(ctx, add_contents)) + Some(self) } } - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated_inside( - self, - ui: &mut Ui, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - None - } else if how_expanded < 1.0 { - // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its height. - // Then we can actually paint it as it animates. - let expanded_height = PanelState::load(ui.ctx(), self.id) - .map(|state| state.rect.height()) - .or(self.default_height) - .unwrap_or_else(|| ui.style().spacing.interact_size.y); - let fake_height = how_expanded * expanded_height; - Self { - id: self.id.with("animating_panel"), - ..self - } - .resizable(false) - .exact_height(fake_height) - .show_inside(ui, |_ui| {}); - None - } else { - // Show the real panel: - Some(self.show_inside(ui, add_contents)) - } - } - - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between( + /// Get either the collapsed or expended panel to animate. + fn get_animated_between_panel( ctx: &Context, is_expanded: bool, collapsed_panel: Self, expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> Option> { + ) -> Self { let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); if 0.0 == how_expanded { - Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + collapsed_panel } else if how_expanded < 1.0 { - // Show animation: - let collapsed_height = PanelState::load(ctx, collapsed_panel.id) - .map(|state| state.rect.height()) - .or(collapsed_panel.default_height) - .unwrap_or_else(|| ctx.style().spacing.interact_size.y); + let collapsed_size = Self::animated_size(ctx, &collapsed_panel); + let expanded_size = Self::animated_size(ctx, &expanded_panel); - let expanded_height = PanelState::load(ctx, expanded_panel.id) - .map(|state| state.rect.height()) - .or(expanded_panel.default_height) - .unwrap_or_else(|| ctx.style().spacing.interact_size.y); + let fake_size = lerp(collapsed_size..=expanded_size, how_expanded); - let fake_height = lerp(collapsed_height..=expanded_height, how_expanded); Self { id: expanded_panel.id.with("animating_panel"), ..expanded_panel } .resizable(false) - .exact_height(fake_height) - .show(ctx, |ui| add_contents(ui, how_expanded)); - None + .exact_size(fake_size) } else { - Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + expanded_panel } } - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between_inside( - ui: &mut Ui, - is_expanded: bool, - collapsed_panel: Self, - expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> InnerResponse { - let how_expanded = - animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded); + fn animated_size(ctx: &Context, panel: &Self) -> f32 { + let get_rect_state_size = |state: PanelState| match panel.side { + PanelSide::Vertical(_) => state.rect.width(), + PanelSide::Horizontal(_) => state.rect.height(), + }; - if 0.0 == how_expanded { - collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else if how_expanded < 1.0 { - // Show animation: - let collapsed_height = PanelState::load(ui.ctx(), collapsed_panel.id) - .map(|state| state.rect.height()) - .or(collapsed_panel.default_height) - .unwrap_or_else(|| ui.style().spacing.interact_size.y); + let get_spacing_size = || match panel.side { + PanelSide::Vertical(_) => ctx.style().spacing.interact_size.x, + PanelSide::Horizontal(_) => ctx.style().spacing.interact_size.y, + }; - let expanded_height = PanelState::load(ui.ctx(), expanded_panel.id) - .map(|state| state.rect.height()) - .or(expanded_panel.default_height) - .unwrap_or_else(|| ui.style().spacing.interact_size.y); - - let fake_height = lerp(collapsed_height..=expanded_height, how_expanded); - Self { - id: expanded_panel.id.with("animating_panel"), - ..expanded_panel - } - .resizable(false) - .exact_height(fake_height) - .show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else { - expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } + PanelState::load(ctx, panel.id) + .map(get_rect_state_size) + .or(panel.default_size) + .unwrap_or(get_spacing_size()) } } @@ -1075,8 +961,8 @@ impl TopBottomPanel { /// /// ``` /// # egui::__run_test_ctx(|ctx| { -/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| { -/// ui.label("Hello World! From `TopBottomPanel`, that must be before `CentralPanel`!"); +/// egui::Panel::top("my_panel").show(ctx, |ui| { +/// ui.label("Hello World! From `Panel`, that must be before `CentralPanel`!"); /// }); /// egui::CentralPanel::default().show(ctx, |ui| { /// ui.label("Hello World!"); @@ -1158,6 +1044,13 @@ impl CentralPanel { ); panel_ui.set_clip_rect(ctx.content_rect()); + if false { + // TODO(emilk): @lucasmerlin shouldn't we enable this? + panel_ui + .response() + .widget_info(|| WidgetInfo::new(WidgetType::Panel)); + } + let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); // Only inform ctx about what we actually used, so we can shrink the native window to fit. @@ -1171,3 +1064,11 @@ fn clamp_to_range(x: f32, range: Rangef) -> f32 { let range = range.as_positive(); x.clamp(range.min, range.max) } + +// ---------------------------------------------------------------------------- + +#[deprecated = "Use Panel::left or Panel::right instead"] +pub type SidePanel = super::Panel; + +#[deprecated = "Use Panel::top or Panel::bottom instead"] +pub type TopBottomPanel = super::Panel; diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 587c3b377..2ca7af4fc 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -764,7 +764,7 @@ impl Context { /// and only on the rare occasion that [`Context::request_discard`] is called. /// Usually, it `run_ui` will only be called once. /// - /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. + /// Put your widgets into a [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// /// Instead of calling `run`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`]. /// diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 3071f7196..bd4319f0b 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -9,7 +9,7 @@ //! which uses [`eframe`](https://docs.rs/eframe). //! //! To create a GUI using egui you first need a [`Context`] (by convention referred to by `ctx`). -//! Then you add a [`Window`] or a [`SidePanel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need. +//! Then you add a [`Window`] or a [`Panel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need. //! //! //! ## Feature flags @@ -45,7 +45,7 @@ //! //! ### Getting a [`Ui`] //! -//! Use one of [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`] to +//! Use one of [`Panel`], [`CentralPanel`], [`Window`] or [`Area`] to //! get access to an [`Ui`] where you can put widgets. For example: //! //! ``` @@ -322,7 +322,7 @@ //! when you release the panel/window shrinks again. //! This is an artifact of immediate mode, and here are some alternatives on how to avoid it: //! -//! 1. Turn off resizing with [`Window::resizable`], [`SidePanel::resizable`], [`TopBottomPanel::resizable`]. +//! 1. Turn off resizing with [`Window::resizable`], [`Panel::resizable`]. //! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`]. //! 3. Use a justified layout: //! diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 4d746c074..e5fb04b0d 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -84,7 +84,7 @@ fn set_menu_style(style: &mut Style) { } } -/// The menu bar goes well in a [`crate::TopBottomPanel::top`], +/// The menu bar goes well in a [`crate::Panel::top`], /// but can also be placed in a [`crate::Window`]. /// In the latter case you may want to wrap it in [`Frame`]. #[deprecated = "Use `egui::MenuBar::new().ui(` instead"] diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 08bb9cee5..3c7fca2f3 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -119,7 +119,7 @@ impl Ui { /// Create a new top-level [`Ui`]. /// /// Normally you would not use this directly, but instead use - /// [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. + /// [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self { let UiBuilder { id_salt, diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 4136218bd..07026c45b 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -12,16 +12,16 @@ pub enum UiKind { /// A [`crate::CentralPanel`]. CentralPanel, - /// A left [`crate::SidePanel`]. + /// A left [`crate::Panel`]. LeftPanel, - /// A right [`crate::SidePanel`]. + /// A right [`crate::Panel`]. RightPanel, - /// A top [`crate::TopBottomPanel`]. + /// A top [`crate::Panel`]. TopPanel, - /// A bottom [`crate::TopBottomPanel`]. + /// A bottom [`crate::Panel`]. BottomPanel, /// A modal [`crate::Modal`]. diff --git a/crates/egui_demo_app/src/accessibility_inspector.rs b/crates/egui_demo_app/src/accessibility_inspector.rs index f721b710c..9ba3a8082 100644 --- a/crates/egui_demo_app/src/accessibility_inspector.rs +++ b/crates/egui_demo_app/src/accessibility_inspector.rs @@ -1,12 +1,13 @@ +use std::mem; + use accesskit::{Action, ActionRequest, NodeId}; use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler}; + use eframe::epaint::text::TextWrapMode; -use egui::collapsing_header::CollapsingState; use egui::{ Button, Color32, Context, Event, Frame, FullOutput, Id, Key, KeyboardShortcut, Label, - Modifiers, RawInput, RichText, ScrollArea, SidePanel, TopBottomPanel, Ui, + Modifiers, Panel, RawInput, RichText, ScrollArea, Ui, collapsing_header::CollapsingState, }; -use std::mem; /// This [`egui::Plugin`] adds an inspector Panel. /// @@ -86,10 +87,10 @@ impl egui::Plugin for AccessibilityInspectorPlugin { ctx.enable_accesskit(); - SidePanel::right(Self::id()).show(ctx, |ui| { + Panel::right(Self::id()).show(ctx, |ui| { ui.heading("🔎 AccessKit Inspector"); if let Some(selected_node) = self.selected_node { - TopBottomPanel::bottom(Self::id().with("details_panel")) + Panel::bottom(Self::id().with("details_panel")) .frame(Frame::new()) .show_separator_line(false) .show_inside(ui, |ui| { diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index f16aa5969..2630fa862 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -61,7 +61,7 @@ impl Default for HttpApp { impl eframe::App for HttpApp { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| { + egui::Panel::bottom("http_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.add(egui_demo_lib::egui_github_link_file!()) diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index c341d2385..052996eef 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -2,8 +2,6 @@ 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 { @@ -52,7 +50,7 @@ impl Default for ImageViewer { impl eframe::App for ImageViewer { fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - egui::TopBottomPanel::new(TopBottomSide::Top, "url bar").show(ctx, |ui| { + egui::Panel::top("url bar").show(ctx, |ui| { ui.horizontal_centered(|ui| { let label = ui.label("URI:"); ui.text_edit_singleline(&mut self.uri_edit_text) @@ -73,7 +71,7 @@ impl eframe::App for ImageViewer { }); }); - egui::SidePanel::new(Side::Left, "controls").show(ctx, |ui| { + egui::Panel::left("controls").show(ctx, |ui| { // uv ui.label("UV"); ui.add(Slider::new(&mut self.image_options.uv.min.x, 0.0..=1.0).text("min x")); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index bec0aacf3..c118f280c 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -295,7 +295,7 @@ impl eframe::App for WrapApp { } let mut cmd = Command::Nothing; - egui::TopBottomPanel::top("wrap_app_top_bar") + egui::Panel::top("wrap_app_top_bar") .frame(egui::Frame::new().inner_margin(4)) .show(ctx, |ui| { ui.horizontal_wrapped(|ui| { @@ -341,7 +341,7 @@ impl WrapApp { let mut cmd = Command::Nothing; - egui::SidePanel::left("backend_panel") + egui::Panel::left("backend_panel") .resizable(false) .show_animated(ctx, is_open, |ui| { ui.add_space(4.0); 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 8033539dd..179543680 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -236,7 +236,7 @@ impl DemoWindows { } fn mobile_top_bar(&mut self, ctx: &Context) { - egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { + egui::Panel::top("menu_bar").show(ctx, |ui| { menu::MenuBar::new() .config(menu::MenuConfig::new().style(StyleModifier::default())) .ui(ui, |ui| { @@ -262,10 +262,10 @@ impl DemoWindows { } fn desktop_ui(&mut self, ctx: &Context) { - egui::SidePanel::right("egui_demo_panel") + egui::Panel::right("egui_demo_panel") .resizable(false) - .default_width(160.0) - .min_width(160.0) + .default_size(160.0) + .min_size(160.0) .show(ctx, |ui| { ui.add_space(4.0); ui.vertical_centered(|ui| { @@ -289,7 +289,7 @@ impl DemoWindows { self.demo_list_ui(ui); }); - egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { + egui::Panel::top("menu_bar").show(ctx, |ui| { menu::MenuBar::new().ui(ui, |ui| { file_menu_button(ui); }); diff --git a/crates/egui_demo_lib/src/demo/panels.rs b/crates/egui_demo_lib/src/demo/panels.rs index f94513866..55771c1a1 100644 --- a/crates/egui_demo_lib/src/demo/panels.rs +++ b/crates/egui_demo_lib/src/demo/panels.rs @@ -22,9 +22,9 @@ impl crate::View for Panels { fn ui(&mut self, ui: &mut egui::Ui) { // Note that the order we add the panels is very important! - egui::TopBottomPanel::top("top_panel") + egui::Panel::top("top_panel") .resizable(true) - .min_height(32.0) + .min_size(32.0) .show_inside(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| { ui.vertical_centered(|ui| { @@ -34,10 +34,10 @@ impl crate::View for Panels { }); }); - egui::SidePanel::left("left_panel") + egui::Panel::left("left_panel") .resizable(true) - .default_width(150.0) - .width_range(80.0..=200.0) + .default_size(150.0) + .size_range(80.0..=200.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Left Panel"); @@ -47,10 +47,10 @@ impl crate::View for Panels { }); }); - egui::SidePanel::right("right_panel") + egui::Panel::right("right_panel") .resizable(true) - .default_width(150.0) - .width_range(80.0..=200.0) + .default_size(150.0) + .size_range(80.0..=200.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Right Panel"); @@ -60,9 +60,9 @@ impl crate::View for Panels { }); }); - egui::TopBottomPanel::bottom("bottom_panel") + egui::Panel::bottom("bottom_panel") .resizable(false) - .min_height(0.0) + .min_size(0.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Bottom Panel"); diff --git a/crates/egui_demo_lib/src/demo/tooltips.rs b/crates/egui_demo_lib/src/demo/tooltips.rs index 0e391c553..cc474bd4d 100644 --- a/crates/egui_demo_lib/src/demo/tooltips.rs +++ b/crates/egui_demo_lib/src/demo/tooltips.rs @@ -35,7 +35,7 @@ impl crate::View for Tooltips { ui.add(crate::egui_github_link_file_line!()); }); - egui::SidePanel::right("scroll_test").show_inside(ui, |ui| { + egui::Panel::right("scroll_test").show_inside(ui, |ui| { ui.label( "The scroll area below has many labels with interactive tooltips. \ The purpose is to test that the tooltips close when you scroll.", 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 9a66b8bc5..28e12630e 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 @@ -33,7 +33,7 @@ impl Default for EasyMarkEditor { impl EasyMarkEditor { pub fn panels(&mut self, ctx: &egui::Context) { - egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| { + egui::Panel::bottom("easy_mark_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.add(crate::egui_github_link_file!()) diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index a56b85bc1..3671c8d79 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -218,7 +218,7 @@ impl winit::application::ApplicationHandler for GlowApp { self.egui_glow.as_mut().unwrap().run( self.gl_window.as_mut().unwrap().window(), |egui_ctx| { - egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { + egui::Panel::left("my_side_panel").show(egui_ctx, |ui| { ui.heading("Hello World!"); if ui.button("Quit").clicked() { quit = true; diff --git a/examples/hello_android/src/lib.rs b/examples/hello_android/src/lib.rs index c138b97e1..1de7684f5 100644 --- a/examples/hello_android/src/lib.rs +++ b/examples/hello_android/src/lib.rs @@ -41,7 +41,7 @@ impl eframe::App for MyApp { // TODO(lucasmerlin): This is a pretty big hack, should be fixed once safe_area implemented // for android: // https://github.com/rust-windowing/winit/issues/3910 - egui::TopBottomPanel::top("status_bar_space").show(ctx, |ui| { + egui::Panel::top("status_bar_space").show(ctx, |ui| { ui.set_height(32.0); }); diff --git a/tests/test_size_pass/src/main.rs b/tests/test_size_pass/src/main.rs index 6bb293302..ce645eb98 100644 --- a/tests/test_size_pass/src/main.rs +++ b/tests/test_size_pass/src/main.rs @@ -9,7 +9,7 @@ fn main() -> eframe::Result { let options = eframe::NativeOptions::default(); eframe::run_simple_native("My egui App", options, move |ctx, _frame| { // A bottom panel to force the tooltips to consider if the fit below or under the widget: - egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| { + egui::Panel::bottom("bottom").show(ctx, |ui| { ui.horizontal(|ui| { ui.vertical(|ui| { ui.label("Single tooltips:"); diff --git a/tests/test_ui_stack/src/main.rs b/tests/test_ui_stack/src/main.rs index 47b4ee5ca..bb2158297 100644 --- a/tests/test_ui_stack/src/main.rs +++ b/tests/test_ui_stack/src/main.rs @@ -34,7 +34,7 @@ impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { ctx.all_styles_mut(|style| style.interaction.tooltip_delay = 0.0); - egui::SidePanel::left("side_panel_left").show(ctx, |ui| { + egui::Panel::left("side_panel_left").show(ctx, |ui| { ui.heading("Information"); ui.label( "This is a demo/test environment of the `UiStack` feature. The tables display \ @@ -82,7 +82,7 @@ impl eframe::App for MyApp { }); }); - egui::SidePanel::right("side_panel_right").show(ctx, |ui| { + egui::Panel::right("side_panel_right").show(ctx, |ui| { egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { stack_ui(ui); @@ -170,7 +170,7 @@ impl eframe::App for MyApp { }); }); - egui::TopBottomPanel::bottom("bottom_panel") + egui::Panel::bottom("bottom_panel") .resizable(true) .show(ctx, |ui| { egui::ScrollArea::vertical() From d53a4a9c1d36bc723754f55b074ab976f0920556 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 18 Nov 2025 15:56:35 +0100 Subject: [PATCH 318/388] Update docs to reflect that wgpu is the default renderer (#7719) I missed a few parts when merging * https://github.com/emilk/egui/pull/7615 --- .github/workflows/rust.yml | 2 +- ARCHITECTURE.md | 5 ++++- README.md | 2 +- crates/eframe/README.md | 6 +++--- scripts/wasm_bindgen_check.sh | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5d30d7e31..f71588545 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -89,7 +89,7 @@ jobs: run: cargo clippy -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features - name: clippy wasm32 eframe - run: cargo clippy -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown + run: cargo clippy -p eframe --lib --no-default-features --features wgpu,persistence --target wasm32-unknown-unknown - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 51d6d41d1..be98b3308 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](CONTRIBUTING.md) for what to do before opening a PR ## Crate overview -The crates in this repository are: `egui, emath, epaint, epaint_default_fonts, egui_extras, egui-winit, egui_glow, egui_demo_lib, egui_demo_app`. +The crates in this repository are: `egui, emath, epaint, epaint_default_fonts, egui_extras, egui-winit, egui_glow, egui-wgpu, egui_demo_lib, egui_demo_app`. ### `egui`: The main GUI library. Example code: `if ui.button("Click me").clicked() { … }` @@ -37,6 +37,9 @@ The library translates winit events to egui, handled copy/paste, updates the cur ### `egui_glow` Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow). +### `egui-wgpu` +Paints the triangles that egui outputs using [wgpu](https://github.com/grovesNL/wgpu). + ### `eframe` `eframe` is the official `egui` framework, built so you can compile the same app for either web or native. diff --git a/README.md b/README.md index c1d25f913..f4a094465 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/di To test the demo app locally, run `cargo run --release -p egui_demo_app`. -The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run: +The native backend is [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu) (using [`wgpu`](https://crates.io/crates/wgpu)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run: `sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev` diff --git a/crates/eframe/README.md b/crates/eframe/README.md index 9dbf42caf..63d5872c6 100644 --- a/crates/eframe/README.md +++ b/crates/eframe/README.md @@ -16,7 +16,7 @@ For how to use `egui`, see [the egui docs](https://docs.rs/egui). --- -`eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/main/crates/egui-winit). +`eframe` defaults to using [wgpu](https://crates.io/crates/wgpu) for rendering (with an option to change to [glow](https://crates.io/crates/glow)), and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/main/crates/egui-winit). To use on Linux, first run: @@ -26,7 +26,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib 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`. +You can opt-in to the using [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) for rendering by enabling the `glow` feature and setting `NativeOptions::renderer` to `Renderer::Glow`. ## Alternatives `eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others. @@ -35,7 +35,7 @@ You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/win ## Limitations when running egui on the web -`eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and Wasm, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides. +`eframe` and egui compiles to Wasm using either WebGPU (when available) or WebGL2 for rendering, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides. * Rendering: Getting pixel-perfect rendering right on the web is very difficult. * Search: you cannot search an egui web page like you would a normal web page. diff --git a/scripts/wasm_bindgen_check.sh b/scripts/wasm_bindgen_check.sh index 5f90c99c6..5043d98e0 100755 --- a/scripts/wasm_bindgen_check.sh +++ b/scripts/wasm_bindgen_check.sh @@ -12,7 +12,7 @@ else fi CRATE_NAME="egui_demo_app" -FEATURES="glow,http,persistence" +FEATURES="wgpu,http,persistence" echo "Building rust…" BUILD=debug # debug builds are faster From d5869bfeaf0a76a70a0abb723f340aa06fff618a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 21 Nov 2025 20:22:01 +0100 Subject: [PATCH 319/388] Remove some uses of top-level panels in our examples (#7729) We're phasing out top-level panels (panels that use `Context` directly, instead of being inside another `Ui`). As a first step, stop using them in our demo library and application. * Part of https://github.com/emilk/egui/issues/3524 --- crates/egui/src/containers/panel.rs | 14 ++- .../egui_demo_app/src/apps/custom3d_glow.rs | 39 ++++---- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 39 ++++---- crates/egui_demo_app/src/apps/http_app.rs | 10 +-- crates/egui_demo_app/src/apps/image_viewer.rs | 12 +-- crates/egui_demo_app/src/lib.rs | 8 ++ crates/egui_demo_app/src/wrap_app.rs | 88 +++++++++---------- crates/egui_demo_lib/benches/benchmark.rs | 16 +++- .../src/demo/demo_app_windows.rs | 56 ++++++------ crates/egui_demo_lib/src/demo/panels.rs | 1 + .../src/easy_mark/easy_mark_editor.rs | 6 +- crates/egui_demo_lib/src/lib.rs | 8 +- examples/hello_android/src/lib.rs | 4 +- 13 files changed, 165 insertions(+), 136 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index eb60f5f21..670e4758b 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -976,15 +976,25 @@ pub struct CentralPanel { } impl CentralPanel { + /// A central panel with no margin or background color + pub fn no_frame() -> Self { + Self { + frame: Some(Frame::NONE), + } + } + + /// A central panel with a background color and some inner margins + pub fn default_margins() -> Self { + Self { frame: None } + } + /// Change the background color, margins, etc. #[inline] pub fn frame(mut self, frame: Frame) -> Self { self.frame = Some(frame); self } -} -impl CentralPanel { /// Show the panel inside a [`Ui`]. pub fn show_inside( self, diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index 803f6156f..30380e31f 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -22,26 +22,27 @@ impl Custom3d { } } -impl eframe::App for Custom3d { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::both() - .auto_shrink(false) - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("The triangle is being painted using "); - ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); - ui.label(" (OpenGL)."); - }); - ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui."); - - egui::Frame::canvas(ui.style()).show(ui, |ui| { - self.custom_painting(ui); - }); - ui.label("Drag to rotate!"); - ui.add(egui_demo_lib::egui_github_link_file!()); +impl crate::DemoApp for Custom3d { + fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + // TODO(emilk): Use `ScrollArea::inner_margin` + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("The triangle is being painted using "); + ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); + ui.label(" (OpenGL)."); }); + ui.label( + "It's not a very impressive demo, but it shows you can embed 3D inside of egui.", + ); + + egui::Frame::canvas(ui.style()).show(ui, |ui| { + self.custom_painting(ui); + }); + ui.label("Drag to rotate!"); + ui.add(egui_demo_lib::egui_github_link_file!()); + }); }); } diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index d3b10d480..0be1ed19b 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -98,26 +98,27 @@ impl Custom3d { } } -impl eframe::App for Custom3d { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::both() - .auto_shrink(false) - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("The triangle is being painted using "); - ui.hyperlink_to("WGPU", "https://wgpu.rs"); - ui.label(" (Portable Rust graphics API awesomeness)"); - }); - ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui."); - - egui::Frame::canvas(ui.style()).show(ui, |ui| { - self.custom_painting(ui); - }); - ui.label("Drag to rotate!"); - ui.add(egui_demo_lib::egui_github_link_file!()); +impl crate::DemoApp for Custom3d { + fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + // TODO(emilk): Use `ScrollArea::inner_margin` + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("The triangle is being painted using "); + ui.hyperlink_to("WGPU", "https://wgpu.rs"); + ui.label(" (Portable Rust graphics API awesomeness)"); }); + ui.label( + "It's not a very impressive demo, but it shows you can embed 3D inside of egui.", + ); + + egui::Frame::canvas(ui.style()).show(ui, |ui| { + self.custom_painting(ui); + }); + ui.label("Drag to rotate!"); + ui.add(egui_demo_lib::egui_github_link_file!()); + }); }); } } diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index 2630fa862..8953f09e7 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -59,16 +59,16 @@ impl Default for HttpApp { } } -impl eframe::App for HttpApp { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - egui::Panel::bottom("http_bottom").show(ctx, |ui| { +impl crate::DemoApp for HttpApp { + fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { + egui::Panel::bottom("http_bottom").show_inside(ui, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.add(egui_demo_lib::egui_github_link_file!()) }) }); - egui::CentralPanel::default().show(ctx, |ui| { + egui::CentralPanel::default().show_inside(ui, |ui| { let prev_url = self.url.clone(); let trigger_fetch = ui_url(ui, frame, &mut self.url); @@ -80,7 +80,7 @@ impl eframe::App for HttpApp { }); if trigger_fetch { - let ctx = ctx.clone(); + let ctx = ui.ctx().clone(); let (sender, promise) = Promise::new(); let request = ehttp::Request::get(&self.url); ehttp::fetch(request, move |response| { diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index 052996eef..11cc68b6b 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -48,15 +48,15 @@ impl Default for ImageViewer { } } -impl eframe::App for ImageViewer { - fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - egui::Panel::top("url bar").show(ctx, |ui| { +impl crate::DemoApp for ImageViewer { + fn demo_ui(&mut self, ui: &mut egui::Ui, _: &mut eframe::Frame) { + egui::Panel::top("url bar").show_inside(ui, |ui| { ui.horizontal_centered(|ui| { let label = ui.label("URI:"); ui.text_edit_singleline(&mut self.uri_edit_text) .labelled_by(label.id); if ui.small_button("✔").clicked() { - ctx.forget_image(&self.current_uri); + ui.ctx().forget_image(&self.current_uri); self.uri_edit_text = self.uri_edit_text.trim().to_owned(); self.current_uri = self.uri_edit_text.clone(); } @@ -71,7 +71,7 @@ impl eframe::App for ImageViewer { }); }); - egui::Panel::left("controls").show(ctx, |ui| { + egui::Panel::left("controls").show_inside(ui, |ui| { // uv ui.label("UV"); ui.add(Slider::new(&mut self.image_options.uv.min.x, 0.0..=1.0).text("min x")); @@ -197,7 +197,7 @@ impl eframe::App for ImageViewer { } }); - egui::CentralPanel::default().show(ctx, |ui| { + egui::CentralPanel::default().show_inside(ui, |ui| { egui::ScrollArea::both().show(ui, |ui| { let mut image = egui::Image::from_uri(&self.current_uri); image = image.uv(self.image_options.uv); diff --git a/crates/egui_demo_app/src/lib.rs b/crates/egui_demo_app/src/lib.rs index 05b3c4bd6..40264fd8e 100644 --- a/crates/egui_demo_app/src/lib.rs +++ b/crates/egui_demo_app/src/lib.rs @@ -15,6 +15,14 @@ pub(crate) fn seconds_since_midnight() -> f64 { time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64) } +/// Trait that wraps different parts of the demo app. +pub trait DemoApp { + fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame); + + #[cfg(feature = "glow")] + fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {} +} + // ---------------------------------------------------------------------------- #[cfg(feature = "accessibility_inspector")] pub mod accessibility_inspector; diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index c118f280c..1d0a2390e 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -1,4 +1,4 @@ -use egui_demo_lib::is_mobile; +use egui_demo_lib::{DemoWindows, is_mobile}; #[cfg(feature = "glow")] use eframe::glow; @@ -6,29 +6,25 @@ use eframe::glow; #[cfg(target_arch = "wasm32")] use core::any::Any; +use crate::DemoApp; + #[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] struct EasyMarkApp { editor: egui_demo_lib::easy_mark::EasyMarkEditor, } -impl eframe::App for EasyMarkApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - self.editor.panels(ctx); +impl DemoApp for EasyMarkApp { + fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + self.editor.panels(ui); } } // ---------------------------------------------------------------------------- -#[derive(Default)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct DemoApp { - demo_windows: egui_demo_lib::DemoWindows, -} - -impl eframe::App for DemoApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - self.demo_windows.ui(ctx); +impl DemoApp for DemoWindows { + fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + self.ui(ui); } } @@ -41,15 +37,12 @@ pub struct FractalClockApp { pub mock_time: Option, } -impl eframe::App for FractalClockApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::CentralPanel::default() - .frame( - egui::Frame::dark_canvas(&ctx.style()) - .stroke(egui::Stroke::NONE) - .corner_radius(0), - ) - .show(ctx, |ui| { +impl DemoApp for FractalClockApp { + fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + egui::Frame::dark_canvas(ui.style()) + .stroke(egui::Stroke::NONE) + .corner_radius(0) + .show(ui, |ui| { self.fractal_clock .ui(ui, self.mock_time.or(Some(crate::seconds_since_midnight()))); }); @@ -64,13 +57,13 @@ pub struct ColorTestApp { color_test: egui_demo_lib::ColorTest, } -impl eframe::App for ColorTestApp { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - egui::CentralPanel::default().show(ctx, |ui| { +impl DemoApp for ColorTestApp { + fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { + egui::CentralPanel::default().show_inside(ui, |ui| { if frame.is_web() { ui.label( - "NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.", - ); + "NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.", + ); ui.separator(); } egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { @@ -155,7 +148,7 @@ enum Command { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct State { - demo: DemoApp, + demo: DemoWindows, easy_mark_editor: EasyMarkApp, #[cfg(feature = "http")] http: crate::apps::HttpApp, @@ -209,34 +202,34 @@ impl WrapApp { pub fn apps_iter_mut( &mut self, - ) -> impl Iterator { + ) -> impl Iterator { let mut vec = vec![ ( "✨ Demos", Anchor::Demo, - &mut self.state.demo as &mut dyn eframe::App, + &mut self.state.demo as &mut dyn DemoApp, ), ( "🖹 EasyMark editor", Anchor::EasyMarkEditor, - &mut self.state.easy_mark_editor as &mut dyn eframe::App, + &mut self.state.easy_mark_editor as &mut dyn DemoApp, ), #[cfg(feature = "http")] ( "⬇ HTTP", Anchor::Http, - &mut self.state.http as &mut dyn eframe::App, + &mut self.state.http as &mut dyn DemoApp, ), ( "🕑 Fractal Clock", Anchor::Clock, - &mut self.state.clock as &mut dyn eframe::App, + &mut self.state.clock as &mut dyn DemoApp, ), #[cfg(feature = "image_viewer")] ( "🖼 Image Viewer", Anchor::ImageViewer, - &mut self.state.image_viewer as &mut dyn eframe::App, + &mut self.state.image_viewer as &mut dyn DemoApp, ), ]; @@ -245,14 +238,14 @@ impl WrapApp { vec.push(( "🔺 3D painting", Anchor::Custom3d, - custom3d as &mut dyn eframe::App, + custom3d as &mut dyn DemoApp, )); } vec.push(( "🎨 Rendering test", Anchor::Rendering, - &mut self.state.rendering_test as &mut dyn eframe::App, + &mut self.state.rendering_test as &mut dyn DemoApp, )); vec.into_iter() @@ -306,11 +299,13 @@ impl eframe::App for WrapApp { self.state.backend_panel.update(ctx, frame); - if !is_mobile(ctx) { - cmd = self.backend_panel(ctx, frame); - } + egui::CentralPanel::no_frame().show(ctx, |ui| { + if !is_mobile(ctx) { + cmd = self.backend_panel(ui, frame); + } - self.show_selected_app(ctx, frame); + self.show_selected_app(ui, frame); + }); self.state.backend_panel.end_of_frame(ctx); @@ -333,17 +328,16 @@ impl eframe::App for WrapApp { } impl WrapApp { - fn backend_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) -> Command { + fn backend_panel(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) -> Command { // The backend-panel can be toggled on/off. // We show a little animation when the user switches it. - let is_open = - self.state.backend_panel.open || ctx.memory(|mem| mem.everything_is_visible()); + let is_open = self.state.backend_panel.open || ui.memory(|mem| mem.everything_is_visible()); let mut cmd = Command::Nothing; egui::Panel::left("backend_panel") .resizable(false) - .show_animated(ctx, is_open, |ui| { + .show_animated_inside(ui, is_open, |ui| { ui.add_space(4.0); ui.vertical_centered(|ui| { ui.heading("💻 Backend"); @@ -393,11 +387,11 @@ impl WrapApp { }); } - fn show_selected_app(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + fn show_selected_app(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { let selected_anchor = self.state.selected_anchor; for (_name, anchor, app) in self.apps_iter_mut() { - if anchor == selected_anchor || ctx.memory(|mem| mem.everything_is_visible()) { - app.update(ctx, frame); + if anchor == selected_anchor || ui.memory(|mem| mem.everything_is_visible()) { + app.demo_ui(ui, frame); } } } diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 48e7d5207..90468770b 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -29,7 +29,9 @@ pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("demo_with_tessellate__realistic", |b| { b.iter(|| { let full_output = ctx.run(RawInput::default(), |ctx| { - demo_windows.ui(ctx); + egui::CentralPanel::default().show(ctx, |ui| { + demo_windows.ui(ui); + }); }); ctx.tessellate(full_output.shapes, full_output.pixels_per_point) }); @@ -38,13 +40,17 @@ pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("demo_no_tessellate", |b| { b.iter(|| { ctx.run(RawInput::default(), |ctx| { - demo_windows.ui(ctx); + egui::CentralPanel::default().show(ctx, |ui| { + demo_windows.ui(ui); + }); }) }); }); let full_output = ctx.run(RawInput::default(), |ctx| { - demo_windows.ui(ctx); + egui::CentralPanel::default().show(ctx, |ui| { + demo_windows.ui(ui); + }); }); c.bench_function("demo_only_tessellate", |b| { b.iter(|| ctx.tessellate(full_output.shapes.clone(), full_output.pixels_per_point)); @@ -58,7 +64,9 @@ pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("demo_full_no_tessellate", |b| { b.iter(|| { ctx.run(RawInput::default(), |ctx| { - demo_windows.ui(ctx); + egui::CentralPanel::default().show(ctx, |ui| { + demo_windows.ui(ui); + }); }) }); }); 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 179543680..d6f92b284 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -195,11 +195,11 @@ impl Default for DemoWindows { impl DemoWindows { /// Show the app ui (menu bar and windows). - pub fn ui(&mut self, ctx: &Context) { - if is_mobile(ctx) { - self.mobile_ui(ctx); + pub fn ui(&mut self, ui: &mut egui::Ui) { + if is_mobile(ui.ctx()) { + self.mobile_ui(ui); } else { - self.desktop_ui(ctx); + self.desktop_ui(ui); } } @@ -207,36 +207,36 @@ impl DemoWindows { self.open.contains(About::default().name()) } - fn mobile_ui(&mut self, ctx: &Context) { + fn mobile_ui(&mut self, ui: &mut egui::Ui) { if self.about_is_open() { let mut close = false; - egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::vertical() - .auto_shrink(false) - .show(ui, |ui| { - self.groups.about.ui(ui); - ui.add_space(12.0); - ui.vertical_centered_justified(|ui| { - if ui - .button(egui::RichText::new("Continue to the demo!").size(20.0)) - .clicked() - { - close = true; - } - }); + + egui::ScrollArea::vertical() + .auto_shrink(false) + .show(ui, |ui| { + self.groups.about.ui(ui); + ui.add_space(12.0); + ui.vertical_centered_justified(|ui| { + if ui + .button(egui::RichText::new("Continue to the demo!").size(20.0)) + .clicked() + { + close = true; + } }); - }); + }); + if close { set_open(&mut self.open, About::default().name(), false); } } else { - self.mobile_top_bar(ctx); - self.groups.windows(ctx, &mut self.open); + self.mobile_top_bar(ui); + self.groups.windows(ui.ctx(), &mut self.open); } } - fn mobile_top_bar(&mut self, ctx: &Context) { - egui::Panel::top("menu_bar").show(ctx, |ui| { + fn mobile_top_bar(&mut self, ui: &mut egui::Ui) { + egui::Panel::top("menu_bar").show_inside(ui, |ui| { menu::MenuBar::new() .config(menu::MenuConfig::new().style(StyleModifier::default())) .ui(ui, |ui| { @@ -261,12 +261,12 @@ impl DemoWindows { }); } - fn desktop_ui(&mut self, ctx: &Context) { + fn desktop_ui(&mut self, ui: &mut egui::Ui) { egui::Panel::right("egui_demo_panel") .resizable(false) .default_size(160.0) .min_size(160.0) - .show(ctx, |ui| { + .show_inside(ui, |ui| { ui.add_space(4.0); ui.vertical_centered(|ui| { ui.heading("✒ egui demos"); @@ -289,13 +289,13 @@ impl DemoWindows { self.demo_list_ui(ui); }); - egui::Panel::top("menu_bar").show(ctx, |ui| { + egui::Panel::top("menu_bar").show_inside(ui, |ui| { menu::MenuBar::new().ui(ui, |ui| { file_menu_button(ui); }); }); - self.groups.windows(ctx, &mut self.open); + self.groups.windows(ui.ctx(), &mut self.open); } fn demo_list_ui(&mut self, ui: &mut egui::Ui) { diff --git a/crates/egui_demo_lib/src/demo/panels.rs b/crates/egui_demo_lib/src/demo/panels.rs index 55771c1a1..957166c4f 100644 --- a/crates/egui_demo_lib/src/demo/panels.rs +++ b/crates/egui_demo_lib/src/demo/panels.rs @@ -72,6 +72,7 @@ impl crate::View for Panels { }); }); + // TODO(emilk): This extra panel is superfluous - just use what's left of `ui` instead egui::CentralPanel::default().show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Central Panel"); 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 28e12630e..2969c6d3d 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 @@ -32,15 +32,15 @@ impl Default for EasyMarkEditor { } impl EasyMarkEditor { - pub fn panels(&mut self, ctx: &egui::Context) { - egui::Panel::bottom("easy_mark_bottom").show(ctx, |ui| { + pub fn panels(&mut self, ui: &mut egui::Ui) { + egui::Panel::bottom("easy_mark_bottom").show_inside(ui, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.add(crate::egui_github_link_file!()) }) }); - egui::CentralPanel::default().show(ctx, |ui| { + egui::CentralPanel::default().show_inside(ui, |ui| { self.ui(ui); }); } diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index 76be28859..7ba48b0d8 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -74,7 +74,9 @@ fn test_egui_e2e() { const NUM_FRAMES: usize = 5; for _ in 0..NUM_FRAMES { let full_output = ctx.run(raw_input.clone(), |ctx| { - demo_windows.ui(ctx); + egui::CentralPanel::default().show(ctx, |ui| { + demo_windows.ui(ui); + }); }); let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point); assert!(!clipped_primitives.is_empty()); @@ -93,7 +95,9 @@ fn test_egui_zero_window_size() { const NUM_FRAMES: usize = 5; for _ in 0..NUM_FRAMES { let full_output = ctx.run(raw_input.clone(), |ctx| { - demo_windows.ui(ctx); + egui::CentralPanel::default().show(ctx, |ui| { + demo_windows.ui(ui); + }); }); let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point); assert!( diff --git a/examples/hello_android/src/lib.rs b/examples/hello_android/src/lib.rs index 1de7684f5..87007c110 100644 --- a/examples/hello_android/src/lib.rs +++ b/examples/hello_android/src/lib.rs @@ -45,6 +45,8 @@ impl eframe::App for MyApp { ui.set_height(32.0); }); - self.demo.ui(ctx); + egui::CentralPanel::default().show(ctx, |ui| { + self.demo.ui(ui); + }); } } From 8d3539b6da372e966b6d57a474e3a9683c7818f8 Mon Sep 17 00:00:00 2001 From: Yichi Zhang <109252977+YichiZhang0613@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:56:24 +0800 Subject: [PATCH 320/388] test_viewports: fix assertion (#7693) * [x] I have followed the instructions in the PR template These assertions allows col == COLS, while when col == COLS, array may be out of bounds. In `fn init`, `for i in 0..COLS {self.insert(...` confirms the assertions' predicate col <= COLS should be changed into col < COLS. --------- Co-authored-by: Emil Ernerfeldt --- tests/test_viewports/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index a862dbd32..49b212e4b 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -339,7 +339,7 @@ fn drag_and_drop_test(ui: &mut egui::Ui) { } fn insert(&mut self, container: Id, col: usize, value: impl Into) { - assert!(col <= COLS, "The coll should be less then: {COLS}"); + assert!(col < COLS, "The coll should be less than: {COLS}"); let value: String = value.into(); let id = Id::new(format!("%{}% {}", self.counter, &value)); @@ -355,7 +355,7 @@ fn drag_and_drop_test(ui: &mut egui::Ui) { } fn cols(&self, container: Id, col: usize) -> Vec<(Id, String)> { - assert!(col <= COLS, "The col should be less then: {COLS}"); + assert!(col < COLS, "The col should be less than: {COLS}"); let container_data = &self.containers_data[&container]; container_data[col] .iter() @@ -368,7 +368,7 @@ fn drag_and_drop_test(ui: &mut egui::Ui) { let Some(id) = self.is_dragged.take() else { return; }; - assert!(col <= COLS, "The col should be less then: {COLS}"); + assert!(col < COLS, "The col should be less than: {COLS}"); // Should be a better way to do this! #[expect(clippy::iter_over_hash_type)] From a624f37e2da32da4b166f71bc95b3d68836673fe Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 25 Nov 2025 08:52:16 +0100 Subject: [PATCH 321/388] Add `Context::run_ui` (#7736) * Part of https://github.com/emilk/egui/issues/3524 Adds `Context::run_ui` as a convenience wrapper around `Context::run`. This on the path to deprecate `run` and use a top-level `Ui` as the entry-point for all of egui. --- crates/egui/src/context.rs | 51 ++++++++++++++++++++++- crates/egui/src/lib.rs | 10 ++--- crates/egui_demo_lib/benches/benchmark.rs | 24 ++++------- crates/egui_demo_lib/src/lib.rs | 12 ++---- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 2ca7af4fc..d644b9610 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -758,6 +758,47 @@ impl Context { writer(&mut self.0.write()) } + /// Run the ui code for one frame. + /// + /// At most [`Options::max_passes`] calls will be issued to `run_ui`, + /// and only on the rare occasion that [`Context::request_discard`] is called. + /// Usually, it `run_ui` will only be called once. + /// + /// The [`Ui`] given to the callback will cover the entire [`Self::content_rect`], + /// with no margin or background color. Use [`crate::Frame`] to add that. + /// + /// You can organize your GUI using [`crate::Panel`]. + /// + /// Instead of calling `run_ui`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`]. + /// + /// ``` + /// // One egui context that you keep reusing: + /// let mut ctx = egui::Context::default(); + /// + /// // Each frame: + /// let input = egui::RawInput::default(); + /// let full_output = ctx.run_ui(input, |ui| { + /// ui.label("Hello egui!"); + /// }); + /// // handle full_output + /// ``` + /// + /// ## See also + /// * [`Self::run`] + #[must_use] + pub fn run_ui(&self, new_input: RawInput, mut run_ui: impl FnMut(&mut Ui)) -> FullOutput { + self.run_ui_dyn(new_input, &mut run_ui) + } + + #[must_use] + fn run_ui_dyn(&self, new_input: RawInput, run_ui: &mut dyn FnMut(&mut Ui)) -> FullOutput { + self.run(new_input, |ctx| { + crate::CentralPanel::no_frame().show(ctx, |ui| { + run_ui(ui); + }); + }) + } + /// Run the ui code for one frame. /// /// At most [`Options::max_passes`] calls will be issued to `run_ui`, @@ -781,8 +822,16 @@ impl Context { /// }); /// // handle full_output /// ``` + /// + /// ## See also + /// * [`Self::run_ui`] #[must_use] - pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { + pub fn run(&self, new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { + self.run_dyn(new_input, &mut run_ui) + } + + #[must_use] + fn run_dyn(&self, mut new_input: RawInput, run_ui: &mut dyn FnMut(&Self)) -> FullOutput { profiling::function_scope!(); let viewport_id = new_input.viewport_id; let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get()); diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index bd4319f0b..d756caf75 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -691,8 +691,8 @@ pub enum WidgetType { pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) { let ctx = Context::default(); ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time) - let _ = ctx.run(Default::default(), |ctx| { - run_ui(ctx); + let _ = ctx.run_ui(Default::default(), |ui| { + run_ui(ui.ctx()); }); } @@ -700,10 +700,8 @@ pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) { pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) { let ctx = Context::default(); ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time) - let _ = ctx.run(Default::default(), |ctx| { - crate::CentralPanel::default().show(ctx, |ui| { - add_contents(ui); - }); + let _ = ctx.run_ui(Default::default(), |ui| { + add_contents(ui); }); } diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 90468770b..1d791cd6d 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -28,10 +28,8 @@ pub fn criterion_benchmark(c: &mut Criterion) { // The most end-to-end benchmark. c.bench_function("demo_with_tessellate__realistic", |b| { b.iter(|| { - let full_output = ctx.run(RawInput::default(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - demo_windows.ui(ui); - }); + let full_output = ctx.run_ui(RawInput::default(), |ui| { + demo_windows.ui(ui); }); ctx.tessellate(full_output.shapes, full_output.pixels_per_point) }); @@ -39,18 +37,14 @@ pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("demo_no_tessellate", |b| { b.iter(|| { - ctx.run(RawInput::default(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - demo_windows.ui(ui); - }); + ctx.run_ui(RawInput::default(), |ui| { + demo_windows.ui(ui); }) }); }); - let full_output = ctx.run(RawInput::default(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - demo_windows.ui(ui); - }); + let full_output = ctx.run_ui(RawInput::default(), |ui| { + demo_windows.ui(ui); }); c.bench_function("demo_only_tessellate", |b| { b.iter(|| ctx.tessellate(full_output.shapes.clone(), full_output.pixels_per_point)); @@ -63,10 +57,8 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut demo_windows = egui_demo_lib::DemoWindows::default(); c.bench_function("demo_full_no_tessellate", |b| { b.iter(|| { - ctx.run(RawInput::default(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - demo_windows.ui(ui); - }); + ctx.run_ui(RawInput::default(), |ui| { + demo_windows.ui(ui); }) }); }); diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index 7ba48b0d8..e0a257224 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -73,10 +73,8 @@ fn test_egui_e2e() { const NUM_FRAMES: usize = 5; for _ in 0..NUM_FRAMES { - let full_output = ctx.run(raw_input.clone(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - demo_windows.ui(ui); - }); + let full_output = ctx.run_ui(raw_input.clone(), |ui| { + demo_windows.ui(ui); }); let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point); assert!(!clipped_primitives.is_empty()); @@ -94,10 +92,8 @@ fn test_egui_zero_window_size() { const NUM_FRAMES: usize = 5; for _ in 0..NUM_FRAMES { - let full_output = ctx.run(raw_input.clone(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - demo_windows.ui(ui); - }); + let full_output = ctx.run_ui(raw_input.clone(), |ui| { + demo_windows.ui(ui); }); let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point); assert!( From 8b8595b45b4c283a2a654ada081342079170e3ab Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 25 Nov 2025 13:15:28 +0100 Subject: [PATCH 322/388] Treat `.` as a word-splitter in text navigation (#7741) When using option + arrow keys (Mac) or ctrl + arrow keys (Windows), you navigate a full word. Previously egui would ignore `.` in the text, so that `www.example.com` would be considered a full word. This is inconsistent with how the rest of macOS works. With this PR, cursor navigation in `www.example.com` will move the cursor between the dots. This makes editing code with egui a lot nicer. --- .../src/text_selection/text_cursor_state.rs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index 2a02e4577..9c9b0a263 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -208,25 +208,33 @@ fn ccursor_previous_line(text: &str, ccursor: CCursor) -> CCursor { } } -fn next_word_boundary_char_index(text: &str, index: usize) -> usize { - for word in text.split_word_bound_indices() { +fn next_word_boundary_char_index(text: &str, cursor_ci: usize) -> usize { + for (word_byte_index, word) in text.split_word_bound_indices() { + let word_ci = char_index_from_byte_index(text, word_byte_index); + + // We consider `.` a word boundary. + // At least that's how Mac works when navigating something like `www.example.com`. + for (dot_ci_offset, chr) in word.chars().enumerate() { + let dot_ci = word_ci + dot_ci_offset; + if chr == '.' && cursor_ci < dot_ci { + return dot_ci; + } + } + // Splitting considers contiguous whitespace as one word, such words must be skipped, // this handles cases for example ' abc' (a space and a word), the cursor is at the beginning // (before space) - this jumps at the end of 'abc' (this is consistent with text editors // or browsers) - let ci = char_index_from_byte_index(text, word.0); - if ci > index && !skip_word(word.1) { - return ci; + if cursor_ci < word_ci && !all_word_chars(word) { + return word_ci; } } char_index_from_byte_index(text, text.len()) } -fn skip_word(text: &str) -> bool { - // skip words that contain anything other than alphanumeric characters and underscore - // (i.e. whitespace, dashes, etc.) - !text.chars().any(|c| !is_word_char(c)) +fn all_word_chars(text: &str) -> bool { + text.chars().all(is_word_char) } fn next_line_boundary_char_index(it: impl Iterator, mut index: usize) -> usize { @@ -337,6 +345,12 @@ mod test { assert_eq!(next_word_boundary_char_index("", 0), 0); assert_eq!(next_word_boundary_char_index("", 1), 0); + // ASCII only + let text = "abc.def.ghi"; + assert_eq!(next_word_boundary_char_index(text, 1), 3); + assert_eq!(next_word_boundary_char_index(text, 3), 7); + assert_eq!(next_word_boundary_char_index(text, 7), 11); + // Unicode graphemes, some of which consist of multiple Unicode characters, // !!! Unicode character is not always what is tranditionally considered a character, // the values below are correct despite not seeming that way on the first look, From a19629ef4ac55e36fbaf82ccb871f0a4407b3a84 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 25 Nov 2025 14:51:18 +0100 Subject: [PATCH 323/388] Add `kittest.toml` config file (#7643) * part of https://github.com/rerun-io/rerun/issues/10991 --------- Co-authored-by: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com> --- Cargo.lock | 32 +++- Cargo.toml | 1 + .../snapshots/rendering_test/dpi_1.67.png | 4 +- crates/egui_kittest/Cargo.toml | 6 +- crates/egui_kittest/README.md | 27 +++ crates/egui_kittest/src/config.rs | 154 ++++++++++++++++++ crates/egui_kittest/src/lib.rs | 1 + crates/egui_kittest/src/snapshot.rs | 55 +++++-- kittest.toml | 10 ++ 9 files changed, 273 insertions(+), 17 deletions(-) create mode 100644 crates/egui_kittest/src/config.rs create mode 100644 kittest.toml diff --git a/Cargo.lock b/Cargo.lock index 61bf459b5..404f3563c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1457,7 +1457,9 @@ dependencies = [ "kittest", "open", "pollster", + "serde", "tempfile", + "toml", "wgpu", ] @@ -4036,6 +4038,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serial_windows" version = "0.1.0" @@ -4491,11 +4502,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -4504,6 +4530,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -5645,9 +5673,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b33ca445c..b9a3432ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,6 +131,7 @@ syntect = { version = "5.3.0", default-features = false } tempfile = "3.23.0" thiserror = "2.0.17" tokio = "1.47.1" +toml = "0.8" type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } unicode-segmentation = "1.12.0" 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 1344edcfb..0fc009f8a 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:9b7d7e290b97a8042af3af3cd9ceb274950cf607dd7e9cd6c71d5a113d3b57a5 -size 1206155 +oid sha256:3a3a9aa8383abfe4580be2cc9987f8123aeabf36bf8ec06029a9af64b9500ec9 +size 1206157 diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index 1de8ce7ac..33c895617 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "egui_kittest" version.workspace = true -authors = ["Lucas Meurer ", "Emil Ernerfeldt "] +authors = ["Lucas Meurer ", "Emil Ernerfeldt "] description = "Testing library for egui based on kittest and AccessKit" edition.workspace = true rust-version.workspace = true @@ -34,9 +34,11 @@ x11 = ["eframe?/x11"] [dependencies] -kittest.workspace = true egui.workspace = true eframe = { workspace = true, optional = true } +kittest.workspace = true +serde.workspace = true +toml.workspace = true # wgpu dependencies egui-wgpu = { workspace = true, optional = true } diff --git a/crates/egui_kittest/README.md b/crates/egui_kittest/README.md index b774572f6..8c3c4bc30 100644 --- a/crates/egui_kittest/README.md +++ b/crates/egui_kittest/README.md @@ -38,6 +38,33 @@ fn main() { } ``` +## Configuration + +You can configure test settings via a `kittest.toml` file in your workspace root. +All possible settings and their defaults: +```toml +# path to the snapshot directory +output_path = "tests/snapshots" + +# default threshold for image comparison tests +threshold = 0.6 + +# default failed_pixel_count_threshold +failed_pixel_count_threshold = 0 + +[windows] +threshold = 0.6 +failed_pixel_count_threshold = 0 + +[macos] +threshold = 0.6 +failed_pixel_count_threshold = 0 + +[linux] +threshold = 0.6 +failed_pixel_count_threshold = 0 +``` + ## Snapshot testing There is a snapshot testing feature. To create snapshot tests, enable the `snapshot` and `wgpu` features. Once enabled, you can call `Harness::snapshot` to render the ui and save the image to the `tests/snapshots` directory. diff --git a/crates/egui_kittest/src/config.rs b/crates/egui_kittest/src/config.rs new file mode 100644 index 000000000..2565ceabf --- /dev/null +++ b/crates/egui_kittest/src/config.rs @@ -0,0 +1,154 @@ +use std::io; +use std::path::PathBuf; + +/// Configuration for `egui_kittest`. +/// +/// It's loaded once (per process) by searching for a `kittest.toml` file in the project root +/// (the directory containing `Cargo.lock`). +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct Config { + /// The output path for image snapshots. + /// + /// Default is "tests/snapshots" (relative to the working directory / crate root). + output_path: PathBuf, + + /// The per-pixel threshold. + /// + /// Default is 0.6. + threshold: f32, + + /// The number of pixels that can differ before the test is considered failed. + /// + /// Default is 0. + failed_pixel_count_threshold: usize, + + windows: OsConfig, + mac: OsConfig, + linux: OsConfig, +} + +impl Default for Config { + fn default() -> Self { + Self { + output_path: PathBuf::from("tests/snapshots"), + threshold: 0.6, + failed_pixel_count_threshold: 0, + windows: Default::default(), + mac: Default::default(), + linux: Default::default(), + } + } +} +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct OsConfig { + /// Override the per-pixel threshold for this OS. + threshold: Option, + + /// Override the failed pixel count threshold for this OS. + failed_pixel_count_threshold: Option, +} + +fn find_kittest_toml() -> io::Result { + let mut current_dir = std::env::current_dir()?; + + loop { + let current_kittest = current_dir.join("kittest.toml"); + // Check if Cargo.toml exists in this directory + if current_kittest.exists() { + return Ok(current_kittest); + } + + // Move up one directory + if !current_dir.pop() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "kittest.toml not found", + )); + } + } +} + +fn load_config() -> Config { + if let Ok(config_path) = find_kittest_toml() { + match std::fs::read_to_string(&config_path) { + Ok(config_str) => match toml::from_str(&config_str) { + Ok(config) => config, + Err(e) => panic!("Failed to parse {}: {e}", &config_path.display()), + }, + Err(err) => { + panic!("Failed to read {}: {}", config_path.display(), err); + } + } + } else { + Config::default() + } +} + +/// Get the global configuration. +/// +/// See [`Config::global`] for details. +pub fn config() -> &'static Config { + Config::global() +} + +impl Config { + /// Get or load the global configuration. + /// + /// This is either + /// - Based on a `kittest.toml`, found by searching from the current working directory + /// (for tests that is the crate root) upwards. + /// - The default [Config], if no `kittest.toml` is found. + pub fn global() -> &'static Self { + static INSTANCE: std::sync::LazyLock = std::sync::LazyLock::new(load_config); + &INSTANCE + } + + /// The output path for image snapshots. + /// + /// Default is "tests/snapshots". + pub fn output_path(&self) -> PathBuf { + self.output_path.clone() + } +} + +#[cfg(feature = "snapshot")] +impl Config { + pub fn os_threshold(&self) -> crate::OsThreshold { + let fallback = self.threshold; + crate::OsThreshold { + windows: self.windows.threshold.unwrap_or(fallback), + macos: self.mac.threshold.unwrap_or(fallback), + linux: self.linux.threshold.unwrap_or(fallback), + fallback, + } + } + + pub fn os_failed_pixel_count_threshold(&self) -> crate::OsThreshold { + let fallback = self.failed_pixel_count_threshold; + crate::OsThreshold { + windows: self + .windows + .failed_pixel_count_threshold + .unwrap_or(fallback), + macos: self.mac.failed_pixel_count_threshold.unwrap_or(fallback), + linux: self.linux.failed_pixel_count_threshold.unwrap_or(fallback), + fallback, + } + } + + /// The threshold. + /// + /// Default is 1.0. + pub fn threshold(&self) -> f32 { + self.os_threshold().threshold() + } + + /// The number of pixels that can differ before the test is considered failed. + /// + /// Default is 0. + pub fn failed_pixel_count_threshold(&self) -> usize { + self.os_failed_pixel_count_threshold().threshold() + } +} diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index fc8b8efbc..6b196484a 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -11,6 +11,7 @@ mod snapshot; pub use crate::snapshot::*; mod app_kind; +mod config; mod node; mod renderer; #[cfg(feature = "wgpu")] diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index f6511c451..f26741323 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -1,28 +1,35 @@ -use crate::Harness; -use image::ImageError; use std::fmt::Display; use std::io::ErrorKind; use std::path::PathBuf; +use image::ImageError; + +use crate::{Harness, config::config}; + pub type SnapshotResult = Result<(), SnapshotError>; #[non_exhaustive] #[derive(Clone, Debug)] pub struct SnapshotOptions { /// The threshold for the image comparison. - /// The default is `0.6` (which is enough for most egui tests to pass across different - /// wgpu backends). + /// + /// Can be configured via kittest.toml. The fallback is `0.6` (which is enough for most egui + /// tests to pass across different 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 + /// Can be configured via kittest.toml. The fallback is `0` (meaning no pixels can differ). pub failed_pixel_count_threshold: usize, /// The path where the snapshots will be saved. - /// The default is `tests/snapshots`. + /// + /// This is relative to the current working directory (usually the crate root when + /// running tests). + /// + /// Can be configured via kittest.toml. The fallback is `tests/snapshots`. pub output_path: PathBuf, } @@ -30,7 +37,9 @@ pub struct SnapshotOptions { /// /// This is useful if you want to set different thresholds for different operating systems. /// -/// The default values are 0 / 0.0 +/// [`OsThreshold::default`] gets the default from the config file (`kittest.toml`). +/// For `usize`, it's the `failed_pixel_count_threshold` value. +/// For `f32`, it's the `threshold` value. /// /// Example usage: /// ```no_run @@ -53,12 +62,36 @@ pub struct OsThreshold { pub fallback: T, } +impl Default for OsThreshold { + /// Returns the default `failed_pixel_count_threshold` as configured in `kittest.toml` + /// + /// The fallback is `0`. + fn default() -> Self { + config().os_failed_pixel_count_threshold() + } +} + +impl Default for OsThreshold { + /// Returns the default `threshold` as configured in `kittest.toml` + /// + /// The fallback is `0.6`. + fn default() -> Self { + config().os_threshold() + } +} + impl From for OsThreshold { fn from(value: usize) -> Self { Self::new(value) } } +impl From for OsThreshold { + fn from(value: f32) -> Self { + Self::new(value) + } +} + impl OsThreshold where T: Copy, @@ -123,9 +156,9 @@ impl From> for f32 { 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 + threshold: config().threshold(), + output_path: config().output_path(), + failed_pixel_count_threshold: config().failed_pixel_count_threshold(), } } } diff --git a/kittest.toml b/kittest.toml new file mode 100644 index 000000000..4c9076b66 --- /dev/null +++ b/kittest.toml @@ -0,0 +1,10 @@ +output_path = "tests/snapshots" + +# Other OSes get a higher threshold so they can still run tests locally without failures due to small rendering +# differences. +# To update snapshots, update them via ./scripts/update_snapshots_from_ci.sh or via kitdiff +threshold = 2.0 + +[mac] +# Since our CI runs snapshot tests on macOS, this is our source of truth. +threshold = 0.6 From de907612b7dc09351d3e8c3b150fdfcf40f12cbd Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 26 Nov 2025 14:56:19 +0100 Subject: [PATCH 324/388] Enforce consistent snapshot updates (#7744) * Closes https://github.com/emilk/egui/issues/7647 This collects SnapshotResults within the Harness and adds a check to enforce snapshot results are merged in case multiple Harnesses are constructed within a test. This should make snapshot updates via kitdiff/accept_snapshots.sh way more useful since it should now always update all snapshots instead of only the first one per test. --- .../src/demo/tests/tessellation_test.rs | 3 + .../egui_demo_lib/src/demo/widget_gallery.rs | 5 +- crates/egui_demo_lib/tests/image_blending.rs | 2 + crates/egui_demo_lib/tests/misc.rs | 4 + crates/egui_kittest/src/builder.rs | 5 ++ crates/egui_kittest/src/lib.rs | 11 +++ crates/egui_kittest/src/snapshot.rs | 87 +++++++++++++++---- 7 files changed, 100 insertions(+), 17 deletions(-) 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 cb08cf24e..78af853ef 100644 --- a/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/tessellation_test.rs @@ -357,11 +357,13 @@ fn rect_shape_ui(ui: &mut egui::Ui, shape: &mut RectShape) { #[cfg(test)] mod tests { use crate::View as _; + use egui_kittest::SnapshotResults; use super::*; #[test] fn snapshot_tessellation_test() { + let mut results = SnapshotResults::new(); for (name, shape) in TessellationTest::interesting_shapes() { let mut test = TessellationTest { shape, @@ -375,6 +377,7 @@ mod tests { harness.run(); harness.snapshot(format!("tessellation_test/{name}")); + results.extend_harness(&mut harness); } } } diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 214646d49..b277b6d12 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -310,7 +310,7 @@ mod tests { use super::*; use crate::View as _; use egui::Vec2; - use egui_kittest::Harness; + use egui_kittest::{Harness, SnapshotResults}; #[test] pub fn should_match_screenshot() { @@ -320,6 +320,8 @@ mod tests { ..Default::default() }; + let mut results = SnapshotResults::new(); + for pixels_per_point in [1, 2] { for theme in [egui::Theme::Light, egui::Theme::Dark] { let mut harness = Harness::builder() @@ -339,6 +341,7 @@ mod tests { }; let image_name = format!("widget_gallery_{theme_name}_x{pixels_per_point}"); harness.snapshot(&image_name); + results.extend_harness(&mut harness); } } } diff --git a/crates/egui_demo_lib/tests/image_blending.rs b/crates/egui_demo_lib/tests/image_blending.rs index c8e5775a8..5cf129efc 100644 --- a/crates/egui_demo_lib/tests/image_blending.rs +++ b/crates/egui_demo_lib/tests/image_blending.rs @@ -3,6 +3,7 @@ use egui_kittest::Harness; #[test] fn test_image_blending() { + let mut results = egui_kittest::SnapshotResults::new(); for pixels_per_point in [1.0, 2.0] { let mut harness = Harness::builder() .with_pixels_per_point(pixels_per_point) @@ -21,5 +22,6 @@ fn test_image_blending() { harness.run(); harness.fit_contents(); harness.snapshot(format!("image_blending/image_x{pixels_per_point}")); + results.extend_harness(&mut harness); } } diff --git a/crates/egui_demo_lib/tests/misc.rs b/crates/egui_demo_lib/tests/misc.rs index af8858bca..8abc69d19 100644 --- a/crates/egui_demo_lib/tests/misc.rs +++ b/crates/egui_demo_lib/tests/misc.rs @@ -3,6 +3,7 @@ use egui_kittest::{Harness, kittest::Queryable as _}; #[test] fn test_kerning() { + let mut results = egui_kittest::SnapshotResults::new(); for pixels_per_point in [1.0, 2.0] { for theme in [egui::Theme::Dark, egui::Theme::Light] { let mut harness = Harness::builder() @@ -24,12 +25,14 @@ fn test_kerning() { egui::Theme::Light => "light", } )); + results.extend_harness(&mut harness); } } } #[test] fn test_italics() { + let mut results = egui_kittest::SnapshotResults::new(); for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] { for theme in [egui::Theme::Dark, egui::Theme::Light] { let mut harness = Harness::builder() @@ -49,6 +52,7 @@ fn test_italics() { egui::Theme::Light => "light", } )); + results.extend_harness(&mut harness); } } } diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index 09b91d26d..87b199c6d 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -166,6 +166,7 @@ impl HarnessBuilder { /// /// assert_eq!(*harness.state(), true); /// ``` + #[track_caller] pub fn build_state<'a>( self, app: impl FnMut(&egui::Context, &mut State) + 'a, @@ -195,6 +196,7 @@ impl HarnessBuilder { /// /// assert_eq!(*harness.state(), true); /// ``` + #[track_caller] pub fn build_ui_state<'a>( self, app: impl FnMut(&mut egui::Ui, &mut State) + 'a, @@ -206,6 +208,7 @@ impl HarnessBuilder { /// Create a new [Harness] from the given eframe creation closure. /// The app can be accessed via the [`Harness::state`] / [`Harness::state_mut`] methods. #[cfg(feature = "eframe")] + #[track_caller] pub fn build_eframe<'a>( self, build: impl FnOnce(&mut eframe::CreationContext<'a>) -> State, @@ -247,6 +250,7 @@ impl HarnessBuilder { /// }); /// ``` #[must_use] + #[track_caller] pub fn build<'a>(self, app: impl FnMut(&egui::Context) + 'a) -> Harness<'a> { Harness::from_builder(self, AppKind::Context(Box::new(app)), (), None) } @@ -267,6 +271,7 @@ impl HarnessBuilder { /// }); /// ``` #[must_use] + #[track_caller] pub fn build_ui<'a>(self, app: impl FnMut(&mut egui::Ui) + 'a) -> Harness<'a> { Harness::from_builder(self, AppKind::Ui(Box::new(app)), (), None) } diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 6b196484a..71312c352 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -84,6 +84,8 @@ pub struct Harness<'a, State = ()> { #[cfg(feature = "snapshot")] default_snapshot_options: SnapshotOptions, + #[cfg(feature = "snapshot")] + snapshot_results: SnapshotResults, } impl Debug for Harness<'_, State> { @@ -93,6 +95,7 @@ impl Debug for Harness<'_, State> { } impl<'a, State> Harness<'a, State> { + #[track_caller] pub(crate) fn from_builder( builder: HarnessBuilder, mut app: AppKind<'a, State>, @@ -162,6 +165,9 @@ impl<'a, State> Harness<'a, State> { #[cfg(feature = "snapshot")] default_snapshot_options, + + #[cfg(feature = "snapshot")] + snapshot_results: SnapshotResults::default(), }; // Run the harness until it is stable, ensuring that all Areas are shown and animations are done harness.run_ok(); @@ -197,6 +203,7 @@ impl<'a, State> Harness<'a, State> { /// /// assert_eq!(*harness.state(), true); /// ``` + #[track_caller] pub fn new_state(app: impl FnMut(&egui::Context, &mut State) + 'a, state: State) -> Self { Self::builder().build_state(app, state) } @@ -222,12 +229,14 @@ impl<'a, State> Harness<'a, State> { /// /// assert_eq!(*harness.state(), true); /// ``` + #[track_caller] pub fn new_ui_state(app: impl FnMut(&mut egui::Ui, &mut State) + 'a, state: State) -> Self { Self::builder().build_ui_state(app, state) } /// Create a new [Harness] from the given eframe creation closure. #[cfg(feature = "eframe")] + #[track_caller] pub fn new_eframe(builder: impl FnOnce(&mut eframe::CreationContext<'a>) -> State) -> Self where State: eframe::App, @@ -725,6 +734,7 @@ impl<'a> Harness<'a> { /// }); /// }); /// ``` + #[track_caller] pub fn new(app: impl FnMut(&egui::Context) + 'a) -> Self { Self::builder().build(app) } @@ -745,6 +755,7 @@ impl<'a> Harness<'a> { /// ui.label("Hello, world!"); /// }); /// ``` + #[track_caller] pub fn new_ui(app: impl FnMut(&mut egui::Ui) + 'a) -> Self { Self::builder().build_ui(app) } diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index f26741323..ede19e5bf 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -663,16 +663,16 @@ impl Harness<'_, State> { /// If the new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`. /// /// # Panics - /// Panics if the image does not match the snapshot, if there was an error reading or writing the + /// The result is added to the [`Harness`]'s internal [`SnapshotResults`]. + /// + /// The harness will panic when dropped if there were any snapshot errors. + /// + /// Errors happen 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: impl Into, options: &SnapshotOptions) { - match self.try_snapshot_options(name, options) { - Ok(_) => {} - Err(err) => { - panic!("{err}"); - } - } + let result = self.try_snapshot_options(name, options); + self.snapshot_results.add(result); } /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot. @@ -688,12 +688,8 @@ impl Harness<'_, State> { /// snapshot, if the rendering fails or if no default renderer is available. #[track_caller] pub fn snapshot(&mut self, name: impl Into) { - match self.try_snapshot(name) { - Ok(_) => {} - Err(err) => { - panic!("{err}"); - } - } + let result = self.try_snapshot(name); + self.snapshot_results.add(result); } /// Render a snapshot, save it to a temp file and open it in the default image viewer. @@ -739,6 +735,12 @@ impl Harness<'_, State> { } } } + + /// This removes the snapshot results from the harness. Useful if you e.g. want to merge it + /// with the results from another harness (using [`SnapshotResults::add`]). + pub fn take_snapshot_results(&mut self) -> SnapshotResults { + std::mem::take(&mut self.snapshot_results) + } } /// Utility to collect snapshot errors and display them at the end of the test. @@ -765,9 +767,22 @@ impl Harness<'_, State> { /// Panics if there are any errors when dropped (this way it is impossible to forget to call `unwrap`). /// If you don't want to panic, you can use [`SnapshotResults::into_result`] or [`SnapshotResults::into_inner`]. /// If you want to panic early, you can use [`SnapshotResults::unwrap`]. -#[derive(Debug, Default)] +#[derive(Debug)] pub struct SnapshotResults { errors: Vec, + handled: bool, + location: std::panic::Location<'static>, +} + +impl Default for SnapshotResults { + #[track_caller] + fn default() -> Self { + Self { + errors: Vec::new(), + handled: true, // If no snapshots were added, we should consider this handled. + location: *std::panic::Location::caller(), + } + } } impl Display for SnapshotResults { @@ -785,17 +800,30 @@ impl Display for SnapshotResults { } impl SnapshotResults { + #[track_caller] pub fn new() -> Self { Default::default() } /// Check if the result is an error and add it to the list of errors. pub fn add(&mut self, result: SnapshotResult) { + self.handled = false; if let Err(err) = result { self.errors.push(err); } } + /// Add all errors from another `SnapshotResults`. + pub fn extend(&mut self, other: Self) { + self.handled = false; + self.errors.extend(other.into_inner()); + } + + /// Add all errors from a [`Harness`]. + pub fn extend_harness(&mut self, harness: &mut Harness<'_, T>) { + self.extend(harness.take_snapshot_results()); + } + /// Check if there are any errors. pub fn has_errors(&self) -> bool { !self.errors.is_empty() @@ -807,13 +835,14 @@ impl SnapshotResults { if self.has_errors() { Err(self) } else { Ok(()) } } + /// Consume this and return the list of errors. pub fn into_inner(mut self) -> Vec { + self.handled = true; std::mem::take(&mut self.errors) } /// Panics if there are any errors, displaying each. #[expect(clippy::unused_self)] - #[track_caller] pub fn unwrap(self) { // Panic is handled in drop } @@ -826,7 +855,6 @@ impl From for Vec { } impl Drop for SnapshotResults { - #[track_caller] fn drop(&mut self) { // Don't panic if we are already panicking (the test probably failed for another reason) if std::thread::panicking() { @@ -836,5 +864,32 @@ impl Drop for SnapshotResults { if self.has_errors() { panic!("{}", self); } + + thread_local! { + static UNHANDLED_SNAPSHOT_RESULTS_COUNTER: std::cell::RefCell = const { std::cell::RefCell::new(0) }; + } + + if !self.handled { + let count = UNHANDLED_SNAPSHOT_RESULTS_COUNTER.with(|counter| { + let mut count = counter.borrow_mut(); + *count += 1; + *count + }); + + #[expect(clippy::manual_assert)] + if count >= 2 { + panic!( + r#" +Multiple SnapshotResults were dropped without being handled. + +In order to allow consistent snapshot updates, all snapshot results within a test should be merged in a single SnapshotResults instance. +Usually this is handled internally in a harness. If you have multiple harnesses, you can merge the results using `Harness::take_snapshot_results` and `SnapshotResults::extend`. + +The SnapshotResult was constructed at {} + "#, + self.location + ); + } + } } } From 3fcdab4ebd8a5985c969780880eb003367820a16 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 5 Dec 2025 02:44:06 -0700 Subject: [PATCH 325/388] Typo fix in drag-and-drop documentation (#7750) * [x] I have followed the instructions in the PR template Adding periods to the end of sentences and fixes a grammar mistake on documentation for the drag-and-drop code to become consistent with the rest of the documentation. The documentation for `Ui::dnd_drop_zone` could've used the word "its" instead of "the" to replace "it", but I think "the" more clearly refers to the `Frame` since "its" has been used to refer to the drop-zone already. --- crates/egui/src/context.rs | 4 ++-- crates/egui/src/response.rs | 2 +- crates/egui/src/ui.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index d644b9610..1369ef616 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -4057,7 +4057,7 @@ impl Context { /// Is this specific widget being dragged? /// /// A widget that sense both clicks and drags is only marked as "dragged" - /// when the mouse has moved a bit + /// when the mouse has moved a bit. /// /// See also: [`crate::Response::dragged`]. pub fn is_being_dragged(&self, id: Id) -> bool { @@ -4071,7 +4071,7 @@ impl Context { self.interaction_snapshot(|i| i.drag_started) } - /// This widget was being dragged, but was released this pass + /// This widget was being dragged, but was released this pass. pub fn drag_stopped_id(&self) -> Option { self.interaction_snapshot(|i| i.drag_stopped) } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index e89cb5252..6b5daead0 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -472,7 +472,7 @@ impl Response { /// /// Only returns something if [`Self::contains_pointer`] is true, /// the user is drag-dropping something of this type, - /// and they released it this frame + /// and they released it this frame. #[doc(alias = "drag and drop")] pub fn dnd_release_payload(&self) -> Option> { // NOTE: we use `response.contains_pointer` here instead of `hovered`, because diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 3c7fca2f3..07bd512d1 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -3004,7 +3004,7 @@ impl Ui { /// /// Returns the dropped item, if it was released this frame. /// - /// The given frame is used for its margins, but it color is ignored. + /// The given frame is used for its margins, but the color is ignored. #[doc(alias = "drag and drop")] pub fn dnd_drop_zone( &mut self, From 2dbfe3a0838ac23b69a5e051cf0b30448d042486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20G=C3=B6rtler?= Date: Fri, 5 Dec 2025 10:46:34 +0100 Subject: [PATCH 326/388] Enable `or_fun_call` lint to avoid unnecessary allocations (#7754) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What From the [lint description](https://rust-lang.github.io/rust-clippy/master/index.html?search=or_fu#or_fun_call): > The function will always be called. This is only bad if it allocates or does some non-trivial amount of work. But also: > If the function has side-effects, not calling it will change the semantic of the program, but you shouldn’t rely on that. > > The lint also cannot figure out whether the function you call is actually expensive to call or not. Still worth it to keep our happy paths clean, imo. --- Cargo.toml | 1 + crates/eframe/src/native/glow_integration.rs | 8 ++++---- crates/egui/src/atomics/atom_kind.rs | 2 +- crates/egui/src/atomics/atom_layout.rs | 4 ++-- crates/egui/src/containers/panel.rs | 2 +- crates/egui/src/containers/popup.rs | 3 ++- crates/egui/src/context.rs | 2 +- crates/egui/src/response.rs | 2 +- crates/egui/src/ui.rs | 8 ++++---- crates/egui/src/widgets/image.rs | 2 +- crates/egui/src/widgets/label.rs | 4 +++- crates/egui/src/widgets/progress_bar.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 4 ++-- crates/egui_demo_app/src/accessibility_inspector.rs | 4 ++-- crates/egui_demo_app/src/wrap_app.rs | 7 +++++-- crates/egui_extras/src/table.rs | 4 ++-- crates/egui_kittest/src/snapshot.rs | 7 ++++--- crates/egui_kittest/src/wgpu.rs | 2 +- crates/epaint/src/shapes/bezier_shape.rs | 9 ++++++--- 19 files changed, 44 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b9a3432ba..0a78d4354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -278,6 +278,7 @@ non_zero_suggestions = "warn" nonstandard_macro_braces = "warn" option_as_ref_cloned = "warn" option_option = "warn" +or_fun_call = "warn" path_buf_push_overwrite = "warn" pathbuf_init_then_push = "warn" precedence_bits = "warn" diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index c5358527a..e448c6c19 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -719,11 +719,11 @@ impl GlowWinitRunning<'_> { // vsync - don't count as frame-time: frame_timer.pause(); profiling::scope!("swap_buffers"); - let context = current_gl_context - .as_ref() - .ok_or(egui_glow::PainterError::from( + let context = current_gl_context.as_ref().ok_or_else(|| { + egui_glow::PainterError::from( "failed to get current context to swap buffers".to_owned(), - ))?; + ) + })?; gl_surface.swap_buffers(context)?; frame_timer.resume(); diff --git a/crates/egui/src/atomics/atom_kind.rs b/crates/egui/src/atomics/atom_kind.rs index 3c54c496b..10ca3353b 100644 --- a/crates/egui/src/atomics/atom_kind.rs +++ b/crates/egui/src/atomics/atom_kind.rs @@ -82,7 +82,7 @@ impl<'a> AtomKind<'a> { ) -> (Vec2, SizedAtomKind<'a>) { match self { AtomKind::Text(text) => { - let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); + let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font); (galley.intrinsic_size(), SizedAtomKind::Text(galley)) } diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs index 1df890250..8132a7dc9 100644 --- a/crates/egui/src/atomics/atom_layout.rs +++ b/crates/egui/src/atomics/atom_layout.rs @@ -168,7 +168,7 @@ impl<'a> AtomLayout<'a> { let fallback_font = fallback_font.unwrap_or_default(); - let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); + let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); // If the TextWrapMode is not Extend, ensure there is some item marked as `shrink`. // If none is found, mark the first text item as `shrink`. @@ -188,7 +188,7 @@ impl<'a> AtomLayout<'a> { let fallback_text_color = fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color()); - let gap = gap.unwrap_or(ui.spacing().icon_spacing); + let gap = gap.unwrap_or_else(|| ui.spacing().icon_spacing); // The size available for the content let available_inner_size = ui.available_size() - frame.total_margin().sum(); diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 670e4758b..3c52f63a3 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -941,7 +941,7 @@ impl Panel { PanelState::load(ctx, panel.id) .map(get_rect_state_size) .or(panel.default_size) - .unwrap_or(get_spacing_size()) + .unwrap_or_else(get_spacing_size) } } diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index a9c00661d..0fb2a9f2a 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -465,7 +465,7 @@ impl<'a> Popup<'a> { pub fn get_best_align(&self) -> RectAlign { let expected_popup_size = self .get_expected_size() - .unwrap_or(vec2(self.width.unwrap_or(0.0), 0.0)); + .unwrap_or_else(|| vec2(self.width.unwrap_or(0.0), 0.0)); let Some(anchor_rect) = self.anchor.rect(self.id, &self.ctx) else { return self.rect_align; @@ -473,6 +473,7 @@ impl<'a> Popup<'a> { RectAlign::find_best_align( #[expect(clippy::iter_on_empty_collections)] + #[expect(clippy::or_fun_call)] once(self.rect_align).chain( self.alternative_aligns // Need the empty slice so the iters have the same type so we can unwrap_or diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 1369ef616..9d9d4b53f 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -611,7 +611,7 @@ impl ContextImpl { } let parent_id = find_accesskit_parent(&state.parent_map, builders, id) - .unwrap_or(crate::accesskit_root_id()); + .unwrap_or_else(crate::accesskit_root_id); let parent_builder = builders.get_mut(&parent_id).unwrap(); parent_builder.push_child(id.accesskit_id()); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 6b5daead0..8190f0006 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -433,7 +433,7 @@ impl Response { pub fn drag_motion(&self) -> Vec2 { if self.dragged() { self.ctx - .input(|i| i.pointer.motion().unwrap_or(i.pointer.delta())) + .input(|i| i.pointer.motion().unwrap_or_else(|| i.pointer.delta())) } else { Vec2::ZERO } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 07bd512d1..d230ed736 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -136,7 +136,7 @@ impl Ui { accessibility_parent, } = ui_builder; - let layer_id = layer_id.unwrap_or(LayerId::background()); + let layer_id = layer_id.unwrap_or_else(LayerId::background); debug_assert!( id_salt.is_none(), @@ -148,7 +148,7 @@ impl Ui { let layout = layout.unwrap_or_default(); let disabled = disabled || invisible; let style = style.unwrap_or_else(|| ctx.style()); - let sense = sense.unwrap_or(Sense::hover()); + let sense = sense.unwrap_or_else(Sense::hover); let placer = Placer::new(max_rect, layout); let ui_stack = UiStack { @@ -277,7 +277,7 @@ impl Ui { let id_salt = id_salt.unwrap_or_else(|| Id::from("child")); let max_rect = max_rect.unwrap_or_else(|| self.available_rect_before_wrap()); - let mut layout = layout.unwrap_or(*self.layout()); + let mut layout = layout.unwrap_or_else(|| *self.layout()); let enabled = self.enabled && !disabled && !invisible; if let Some(layer_id) = layer_id { painter.set_layer_id(layer_id); @@ -287,7 +287,7 @@ impl Ui { } let sizing_pass = self.sizing_pass || sizing_pass; let style = style.unwrap_or_else(|| self.style.clone()); - let sense = sense.unwrap_or(Sense::hover()); + let sense = sense.unwrap_or_else(Sense::hover); if sizing_pass { // During the sizing pass we want widgets to use up as little space as possible, diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 167920adc..8a7c49209 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -681,7 +681,7 @@ pub fn paint_texture_load_result( } Ok(TexturePoll::Pending { .. }) => { let show_loading_spinner = - show_loading_spinner.unwrap_or(ui.visuals().image_loading_spinners); + show_loading_spinner.unwrap_or_else(|| ui.visuals().image_loading_spinners); if show_loading_spinner { Spinner::new().paint_at(ui, rect); } diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 86259ab2a..284cfd12c 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -248,7 +248,9 @@ impl Label { layout_job.halign = Align::LEFT; layout_job.justify = false; } else { - layout_job.halign = self.halign.unwrap_or(ui.layout().horizontal_placement()); + layout_job.halign = self + .halign + .unwrap_or_else(|| ui.layout().horizontal_placement()); layout_job.justify = ui.layout().horizontal_justify(); } diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index bba6be8ef..fb7a79ffe 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -118,7 +118,7 @@ impl Widget for ProgressBar { let desired_width = desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0)); - let height = desired_height.unwrap_or(ui.spacing().interact_size.y); + let height = desired_height.unwrap_or_else(|| ui.spacing().interact_size.y); let (outer_rect, response) = ui.allocate_exact_size(vec2(desired_width, height), Sense::hover()); diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 6f2da1baa..e364b4a00 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -496,7 +496,7 @@ impl TextEdit<'_> { } = self; let text_color = text_color - .or(ui.visuals().override_text_color) + .or_else(|| ui.visuals().override_text_color) // .unwrap_or_else(|| ui.style().interact(&response).text_color()); // too bright .unwrap_or_else(|| ui.visuals().widgets.inactive.text_color()); @@ -691,7 +691,7 @@ impl TextEdit<'_> { if ui.is_rect_visible(rect) { if text.as_str().is_empty() && !hint_text.is_empty() { let hint_text_color = ui.visuals().weak_text_color(); - let hint_text_font_id = hint_text_font.unwrap_or(font_id.into()); + let hint_text_font_id = hint_text_font.unwrap_or_else(|| font_id.into()); let galley = if multiline { hint_text.into_galley( ui, diff --git a/crates/egui_demo_app/src/accessibility_inspector.rs b/crates/egui_demo_app/src/accessibility_inspector.rs index 9ba3a8082..ab8b9270d 100644 --- a/crates/egui_demo_app/src/accessibility_inspector.rs +++ b/crates/egui_demo_app/src/accessibility_inspector.rs @@ -199,8 +199,8 @@ impl AccessibilityInspectorPlugin { } let label = node .label() - .or(node.value()) - .unwrap_or(node.id().0.to_string()); + .or_else(|| node.value()) + .unwrap_or_else(|| node.id().0.to_string()); let label = format!("({:?}) {}", node.role(), label); // Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`. diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 1d0a2390e..87394b503 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -43,8 +43,11 @@ impl DemoApp for FractalClockApp { .stroke(egui::Stroke::NONE) .corner_radius(0) .show(ui, |ui| { - self.fractal_clock - .ui(ui, self.mock_time.or(Some(crate::seconds_since_midnight()))); + self.fractal_clock.ui( + ui, + self.mock_time + .or_else(|| Some(crate::seconds_since_midnight())), + ); }); } } diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index a984226ae..f2a42b850 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -479,7 +479,7 @@ impl<'a> TableBuilder<'a> { } } - let striped = striped.unwrap_or(ui.visuals().striped); + let striped = striped.unwrap_or_else(|| ui.visuals().striped); let state_id = ui.id().with(id_salt); @@ -548,7 +548,7 @@ impl<'a> TableBuilder<'a> { sense, } = self; - let striped = striped.unwrap_or(ui.visuals().striped); + let striped = striped.unwrap_or_else(|| ui.visuals().striped); let state_id = ui.id().with(id_salt); diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index ede19e5bf..4d139fcbb 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -264,7 +264,8 @@ impl Display for SnapshotError { diff, diff_path, } => { - let diff_path = std::path::absolute(diff_path).unwrap_or(diff_path.clone()); + let diff_path = + std::path::absolute(diff_path).unwrap_or_else(|_| diff_path.clone()); write!( f, "'{name}' Image did not match snapshot. Diff: {diff}, {}. {HOW_TO_UPDATE_SCREENSHOTS}", @@ -272,7 +273,7 @@ impl Display for SnapshotError { ) } Self::OpenSnapshot { path, err } => { - let path = std::path::absolute(path).unwrap_or(path.clone()); + let path = std::path::absolute(path).unwrap_or_else(|_| path.clone()); match err { ImageError::IoError(io) => match io.kind() { ErrorKind::NotFound => { @@ -310,7 +311,7 @@ impl Display for SnapshotError { ) } Self::WriteSnapshot { path, err } => { - let path = std::path::absolute(path).unwrap_or(path.clone()); + let path = std::path::absolute(path).unwrap_or_else(|_| path.clone()); write!(f, "Error writing snapshot: {err}\nAt: {}", path.display()) } Self::RenderError { err } => { diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index ae773095d..3f97e0036 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -51,7 +51,7 @@ pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup { adapters .first() .map(|a| (*a).clone()) - .ok_or("No adapter found".to_owned()) + .ok_or_else(|| "No adapter found".to_owned()) })); egui_wgpu::WgpuSetup::CreateNew(setup) diff --git a/crates/epaint/src/shapes/bezier_shape.rs b/crates/epaint/src/shapes/bezier_shape.rs index 20f7a4e18..002612dbb 100644 --- a/crates/epaint/src/shapes/bezier_shape.rs +++ b/crates/epaint/src/shapes/bezier_shape.rs @@ -298,7 +298,8 @@ impl CubicBezierShape { /// the number of points is determined by the tolerance. /// the points may not be evenly distributed in the range [0.0,1.0] (t value) pub fn flatten(&self, tolerance: Option) -> Vec { - let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001); + let tolerance = + tolerance.unwrap_or_else(|| (self.points[0].x - self.points[3].x).abs() * 0.001); let mut result = vec![self.points[0]]; self.for_each_flattened_with_t(tolerance, &mut |p, _t| { result.push(p); @@ -313,7 +314,8 @@ impl CubicBezierShape { /// The result will be a vec of vec of Pos2. it will store two closed aren in different vec. /// The epsilon is used to compare a float value. pub fn flatten_closed(&self, tolerance: Option, epsilon: Option) -> Vec> { - let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001); + let tolerance = + tolerance.unwrap_or_else(|| (self.points[0].x - self.points[3].x).abs() * 0.001); let epsilon = epsilon.unwrap_or(1.0e-5); let mut result = Vec::new(); let mut first_half = Vec::new(); @@ -519,7 +521,8 @@ impl QuadraticBezierShape { /// the number of points is determined by the tolerance. /// the points may not be evenly distributed in the range [0.0,1.0] (t value) pub fn flatten(&self, tolerance: Option) -> Vec { - let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[2].x).abs() * 0.001); + let tolerance = + tolerance.unwrap_or_else(|| (self.points[0].x - self.points[2].x).abs() * 0.001); let mut result = vec![self.points[0]]; self.for_each_flattened_with_t(tolerance, &mut |p, _t| { result.push(p); From 2174b309bdc5a0b767f84839ecec4b993e8ce48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Sat, 6 Dec 2025 11:33:56 +0100 Subject: [PATCH 327/388] Bump `ehttp` to 0.6.0 (#7757) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 404f3563c..ae8de2169 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1475,9 +1475,9 @@ dependencies = [ [[package]] name = "ehttp" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a81c221a1e4dad06cb9c9deb19aea1193a5eea084e8cd42d869068132bf876" +checksum = "04499d3c719edecfad5c9b46031726c8540905d73be6d7e4f9788c4a298da908" dependencies = [ "document-features", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 0a78d4354..c647614c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ criterion = { version = "0.7.0", default-features = false } dify = { version = "0.7.4", default-features = false } directories = "6.0.0" document-features = "0.2.11" -ehttp = { version = "0.5.0", default-features = false } +ehttp = { version = "0.6.0", default-features = false } enum-map = "2.7.3" env_logger = { version = "0.11.8", default-features = false } glow = "0.16.0" From 609dd2d28edfadd544f53cec39b38564eb4fcb75 Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Sat, 6 Dec 2025 10:11:33 -0500 Subject: [PATCH 328/388] Replace ab_glyph with Skrifa + vello_cpu; enable font hinting (#7694) * Closes N/A * [x] I have followed the instructions in the PR template I'll probably come back to this and clean it up a bit. This PR reimplements ab_glyph's functionality on top of Skrifa, a somewhat lower-level font API that's being used in Chrome now. Skrifa doesn't perform rasterization itself, so I'm using [vello_cpu](https://github.com/linebender/vello) from the Linebender project for rasterization. It's still in its early days, but I believe it's already quite fast. It also supports color and gradient fills, so color emoji support will be easier. Skrifa also supports font hinting, which should make text look a bit nicer / less blurry. Here's the current ab_glyph rendering: image Here's Skrifa *without* hinting--it looks almost identical, but there are some subpixel differences, probably due to rasterizer behavior: image Here's Skrifa *with* hinting: image Hinting does make the horizontal strokes look a bit bolder, which makes me wonder once again about increasing the font weight from "light" to "regular". --------- Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 128 +++++- Cargo.toml | 4 +- crates/egui/src/context.rs | 33 +- crates/egui/src/style.rs | 59 ++- .../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_lib/benches/benchmark.rs | 10 +- .../tests/snapshots/demos/Bézier Curve.png | 4 +- .../tests/snapshots/demos/Clipboard Test.png | 4 +- .../tests/snapshots/demos/Code Editor.png | 4 +- .../tests/snapshots/demos/Code Example.png | 4 +- .../tests/snapshots/demos/Cursor Test.png | 4 +- .../tests/snapshots/demos/Dancing Strings.png | 4 +- .../tests/snapshots/demos/Drag and Drop.png | 4 +- .../tests/snapshots/demos/Extra Viewport.png | 4 +- .../tests/snapshots/demos/Font Book.png | 4 +- .../tests/snapshots/demos/Frame.png | 4 +- .../tests/snapshots/demos/Grid Test.png | 4 +- .../tests/snapshots/demos/Highlighting.png | 4 +- .../tests/snapshots/demos/ID Test.png | 4 +- .../snapshots/demos/Input Event History.png | 4 +- .../tests/snapshots/demos/Input Test.png | 4 +- .../snapshots/demos/Interactive Container.png | 4 +- .../tests/snapshots/demos/Layout Test.png | 4 +- .../snapshots/demos/Manual Layout Test.png | 4 +- .../tests/snapshots/demos/Misc Demos.png | 4 +- .../tests/snapshots/demos/Modals.png | 4 +- .../tests/snapshots/demos/Multi Touch.png | 4 +- .../tests/snapshots/demos/Painting.png | 4 +- .../tests/snapshots/demos/Panels.png | 4 +- .../tests/snapshots/demos/Popups.png | 4 +- .../tests/snapshots/demos/SVG Test.png | 4 +- .../tests/snapshots/demos/Scene.png | 4 +- .../tests/snapshots/demos/Screenshot.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 4 +- .../tests/snapshots/demos/Sliders.png | 4 +- .../tests/snapshots/demos/Strip.png | 4 +- .../tests/snapshots/demos/Table.png | 4 +- .../snapshots/demos/Tessellation Test.png | 4 +- .../tests/snapshots/demos/Text Layout.png | 4 +- .../tests/snapshots/demos/TextEdit.png | 4 +- .../tests/snapshots/demos/Tooltips.png | 4 +- .../tests/snapshots/demos/Undo Redo.png | 4 +- .../tests/snapshots/demos/Window Options.png | 4 +- .../snapshots/demos/Window Resize Test.png | 4 +- .../snapshots/image_kerning/image_dark_x1.png | 4 +- .../snapshots/image_kerning/image_dark_x2.png | 4 +- .../image_kerning/image_light_x1.png | 4 +- .../image_kerning/image_light_x2.png | 4 +- .../snapshots/italics/image_dark_x1.00.png | 4 +- .../snapshots/italics/image_dark_x1.41.png | 4 +- .../snapshots/italics/image_dark_x2.00.png | 4 +- .../snapshots/italics/image_light_x1.00.png | 4 +- .../snapshots/italics/image_light_x1.41.png | 4 +- .../snapshots/italics/image_light_x2.00.png | 4 +- .../tests/snapshots/modals_1.png | 4 +- .../tests/snapshots/modals_2.png | 4 +- .../tests/snapshots/modals_3.png | 4 +- ...rop_should_prevent_focusing_lower_area.png | 4 +- .../snapshots/rendering_test/dpi_1.00.png | 4 +- .../snapshots/rendering_test/dpi_1.25.png | 4 +- .../snapshots/rendering_test/dpi_1.50.png | 4 +- .../snapshots/rendering_test/dpi_1.67.png | 4 +- .../snapshots/rendering_test/dpi_1.75.png | 4 +- .../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 +- .../tessellation_test/Thin filled.png | 4 +- .../tessellation_test/Thin stroked.png | 4 +- .../tests/snapshots/text_selection.png | 4 +- .../snapshots/widget_gallery_dark_x1.png | 4 +- .../snapshots/widget_gallery_dark_x2.png | 4 +- .../snapshots/widget_gallery_light_x1.png | 4 +- .../snapshots/widget_gallery_light_x2.png | 4 +- .../tests/snapshots/combobox_closed.png | 4 +- .../tests/snapshots/combobox_opened.png | 4 +- .../tests/snapshots/image_snapshots.png | 4 +- .../tests/snapshots/menu/closed_hovered.png | 4 +- .../tests/snapshots/menu/opened.png | 4 +- .../tests/snapshots/menu/submenu.png | 4 +- .../tests/snapshots/menu/subsubmenu.png | 4 +- .../override_text_color_interactive.png | 4 +- .../tests/snapshots/readme_example.png | 4 +- .../snapshots/should_wait_for_images.png | 4 +- .../tests/snapshots/test_masking.png | 4 +- .../tests/snapshots/test_scroll_initial.png | 4 +- .../tests/snapshots/test_scroll_scrolled.png | 4 +- .../tests/snapshots/test_shrink.png | 4 +- .../tests/snapshots/test_tooltip_hidden.png | 4 +- .../tests/snapshots/test_tooltip_shown.png | 4 +- crates/epaint/Cargo.toml | 6 +- crates/epaint/benches/benchmark.rs | 6 +- crates/epaint/src/lib.rs | 2 +- crates/epaint/src/shapes/text_shape.rs | 6 +- crates/epaint/src/text/font.rs | 409 ++++++++++++------ crates/epaint/src/text/fonts.rs | 134 +++--- crates/epaint/src/text/mod.rs | 28 ++ crates/epaint/src/text/text_layout.rs | 99 ++--- crates/epaint/src/text/text_layout_types.rs | 8 +- crates/epaint/src/texture_atlas.rs | 14 +- deny.toml | 1 + .../tests/snapshots/button_shortcut.png | 4 +- tests/egui_tests/tests/snapshots/grow_all.png | 4 +- .../hovering_should_preserve_text_format.png | 4 +- .../tests/snapshots/layout/atoms_image.png | 4 +- .../tests/snapshots/layout/atoms_minimal.png | 4 +- .../snapshots/layout/atoms_multi_grow.png | 4 +- .../tests/snapshots/layout/button.png | 4 +- .../tests/snapshots/layout/button_image.png | 4 +- .../layout/button_image_shortcut.png | 4 +- .../tests/snapshots/layout/checkbox.png | 4 +- .../snapshots/layout/checkbox_checked.png | 4 +- .../tests/snapshots/layout/drag_value.png | 4 +- .../tests/snapshots/layout/radio.png | 4 +- .../tests/snapshots/layout/radio_checked.png | 4 +- .../snapshots/layout/selectable_value.png | 4 +- .../layout/selectable_value_selected.png | 4 +- .../tests/snapshots/layout/slider.png | 4 +- .../tests/snapshots/layout/text_edit.png | 4 +- .../tests/snapshots/layout/text_edit_clip.png | 4 +- .../snapshots/layout/text_edit_no_clip.png | 4 +- .../layout/text_edit_placeholder_clip.png | 4 +- .../egui_tests/tests/snapshots/max_width.png | 4 +- .../tests/snapshots/max_width_and_grow.png | 4 +- .../tests/snapshots/shrink_first_text.png | 4 +- .../tests/snapshots/shrink_last_text.png | 4 +- .../tests/snapshots/sides/default_long.png | 4 +- .../sides/default_long_fit_contents.png | 4 +- .../tests/snapshots/sides/default_short.png | 4 +- .../sides/default_short_fit_contents.png | 4 +- .../snapshots/sides/shrink_left_long.png | 4 +- .../sides/shrink_left_long_fit_contents.png | 4 +- .../snapshots/sides/shrink_left_short.png | 4 +- .../sides/shrink_left_short_fit_contents.png | 4 +- .../snapshots/sides/shrink_right_long.png | 4 +- .../sides/shrink_right_long_fit_contents.png | 4 +- .../snapshots/sides/shrink_right_short.png | 4 +- .../sides/shrink_right_short_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_left_long.png | 4 +- .../sides/wrap_left_long_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_left_short.png | 4 +- .../sides/wrap_left_short_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_right_long.png | 4 +- .../sides/wrap_right_long_fit_contents.png | 4 +- .../snapshots/sides/wrap_right_short.png | 4 +- .../sides/wrap_right_short_fit_contents.png | 4 +- .../tests/snapshots/size_max_size.png | 4 +- .../tests/snapshots/text_edit_rtl_0.png | 4 +- .../tests/snapshots/text_edit_rtl_1.png | 4 +- .../tests/snapshots/text_edit_rtl_2.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 +- .../tests/snapshots/visuals/checkbox.png | 4 +- .../snapshots/visuals/checkbox_checked.png | 4 +- .../tests/snapshots/visuals/drag_value.png | 4 +- .../tests/snapshots/visuals/radio.png | 4 +- .../tests/snapshots/visuals/radio_checked.png | 4 +- .../snapshots/visuals/selectable_value.png | 4 +- .../visuals/selectable_value_selected.png | 4 +- .../tests/snapshots/visuals/slider.png | 4 +- .../tests/snapshots/visuals/text_edit.png | 4 +- .../snapshots/visuals/text_edit_clip.png | 4 +- .../snapshots/visuals/text_edit_no_clip.png | 4 +- .../visuals/text_edit_placeholder_clip.png | 4 +- 172 files changed, 928 insertions(+), 643 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae8de2169..f75e31381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,6 +863,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" +dependencies = [ + "bytemuck", +] + [[package]] name = "color-hex" version = "0.2.0" @@ -1595,7 +1604,6 @@ dependencies = [ name = "epaint" version = "0.33.2" dependencies = [ - "ab_glyph", "ahash", "bytemuck", "criterion", @@ -1609,8 +1617,11 @@ dependencies = [ "parking_lot", "profiling", "rayon", + "self_cell", "serde", "similar-asserts", + "skrifa", + "vello_cpu", ] [[package]] @@ -1639,6 +1650,15 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -1706,6 +1726,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fearless_simd" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903" +dependencies = [ + "bytemuck", +] + [[package]] name = "file_dialog" version = "0.1.0" @@ -1749,6 +1778,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "font-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" +dependencies = [ + "bytemuck", +] + [[package]] name = "fontconfig-parser" version = "0.5.7" @@ -2528,6 +2566,17 @@ dependencies = [ "smallvec", ] +[[package]] +name = "kurbo" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2577,6 +2626,12 @@ dependencies = [ "redox_syscall 0.5.7", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3253,6 +3308,19 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "peniko" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c76095c9a636173600478e0373218c7b955335048c2bcd12dc6a79657649d8" +dependencies = [ + "bytemuck", + "color", + "kurbo 0.12.0", + "linebender_resource_handle", + "smallvec", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3383,9 +3451,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3692,6 +3760,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -3985,6 +4063,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + [[package]] name = "serde" version = "1.0.228" @@ -4112,6 +4196,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "skrifa" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.9" @@ -4233,7 +4327,7 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ - "kurbo", + "kurbo 0.11.1", "siphasher", ] @@ -4735,7 +4829,7 @@ dependencies = [ "flate2", "fontdb", "imagesize", - "kurbo", + "kurbo 0.11.1", "log", "pico-args", "roxmltree", @@ -4763,6 +4857,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vello_common" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a235ba928b3109ad9e7696270edb09445a52ae1c7c08e6d31a19b1cdd6cbc24a" +dependencies = [ + "bytemuck", + "fearless_simd", + "log", + "peniko", + "skrifa", + "smallvec", +] + +[[package]] +name = "vello_cpu" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0bd1fcf9c1814f17a491e07113623d44e3ec1125a9f3401f5e047d6d326da21" +dependencies = [ + "bytemuck", + "vello_common", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index c647614c0..b1822d58a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,6 @@ eframe = { version = "0.33.2", path = "crates/eframe", default-features = false accesskit = "0.21.1" accesskit_consumer = "0.30.1" accesskit_winit = "0.29.1" -ab_glyph = "0.2.32" ahash = { version = "0.8.12", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", @@ -122,8 +121,10 @@ rayon = "1.11.0" resvg = { version = "0.45.1", default-features = false } rfd = "0.15.4" ron = "0.11.0" +self_cell = "1.2.1" serde = { version = "1.0.228", features = ["derive"] } similar-asserts = "1.7.0" +skrifa = "0.37.0" smallvec = "1.15.1" smithay-clipboard = "0.7.2" static_assertions = "1.1.0" @@ -135,6 +136,7 @@ toml = "0.8" type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } unicode-segmentation = "1.12.0" +vello_cpu = { version = "0.0.4", default-features = false, features = ["std"] } wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml wasm-bindgen-futures = "0.4.0" wayland-cursor = { version = "0.31.11", default-features = false } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 9d9d4b53f..988226e2c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -20,7 +20,7 @@ use crate::{ ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, - ViewportOutput, Widget as _, WidgetRect, WidgetText, + ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText, animation_manager::AnimationManager, containers::{self, area::AreaState}, data::output::PlatformOutput, @@ -34,8 +34,7 @@ use crate::{ os::OperatingSystem, output::FullOutput, pass_state::PassState, - plugin, - plugin::TypedPluginHandle, + plugin::{self, TypedPluginHandle}, resize, response, scroll_area, util::IdTypeMap, viewport::ViewportClass, @@ -564,7 +563,10 @@ impl ContextImpl { log::trace!("Adding new fonts"); } - let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage; + let Visuals { + mut text_options, .. + } = self.memory.options.style().visuals; + text_options.max_texture_side = max_texture_side; let mut is_new = false; @@ -573,16 +575,12 @@ impl ContextImpl { is_new = true; profiling::scope!("Fonts::new"); - Fonts::new( - max_texture_side, - text_alpha_from_coverage, - self.font_definitions.clone(), - ) + Fonts::new(text_options, self.font_definitions.clone()) }); { profiling::scope!("Fonts::begin_pass"); - fonts.begin_pass(max_texture_side, text_alpha_from_coverage); + fonts.begin_pass(text_options); } } @@ -2006,15 +2004,12 @@ impl Context { pub fn set_fonts(&self, font_definitions: FontDefinitions) { profiling::function_scope!(); - let mut update_fonts = true; - - self.read(|ctx| { - if let Some(current_fonts) = ctx.fonts.as_ref() { - // NOTE: this comparison is expensive since it checks TTF data for equality - if current_fonts.definitions() == &font_definitions { - update_fonts = false; // no need to update - } - } + let update_fonts = self.read(|ctx| { + // NOTE: this comparison is expensive since it checks TTF data for equality + // TODO(valadaptive): add_font only checks the *names* for equality. Change this? + ctx.fonts + .as_ref() + .is_none_or(|fonts| fonts.definitions() != &font_definitions) }); if update_fonts { diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index b77536002..b0cff0019 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::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak}; +use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, text::FontTweak}; use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc}; use crate::{ @@ -948,8 +948,11 @@ 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, + /// Controls how we render text. + /// + /// The [`TextOptions::max_texture_side`] is ignored and overruled by + /// [`crate::RawInput::max_texture_side`]. + pub text_options: TextOptions, /// Override default text color for all text. /// @@ -1407,7 +1410,10 @@ impl Visuals { pub fn dark() -> Self { Self { dark_mode: true, - text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT, + text_options: TextOptions { + alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT, + ..Default::default() + }, override_text_color: None, weak_text_alpha: 0.6, weak_text_color: None, @@ -1470,7 +1476,10 @@ impl Visuals { pub fn light() -> Self { Self { dark_mode: false, - text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT, + text_options: TextOptions { + alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT, + ..Default::default() + }, widgets: Widgets::light(), selection: Selection::light(), hyperlink_color: Color32::from_rgb(0, 155, 255), @@ -2107,7 +2116,7 @@ impl Visuals { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { dark_mode, - text_alpha_from_coverage, + text_options, override_text_color: _, weak_text_alpha, weak_text_color, @@ -2207,7 +2216,7 @@ impl Visuals { }); }); - ui.collapsing("Text color", |ui| { + ui.collapsing("Text rendering", |ui| { 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); @@ -2259,7 +2268,15 @@ impl Visuals { ui.add_space(4.0); - text_alpha_from_coverage_ui(ui, text_alpha_from_coverage); + let TextOptions { + max_texture_side: _, + alpha_from_coverage, + font_hinting, + } = text_options; + + text_alpha_from_coverage_ui(ui, alpha_from_coverage); + + ui.checkbox(font_hinting, "Enable font hinting"); }); ui.collapsing("Text cursor", |ui| { @@ -2370,9 +2387,9 @@ impl Visuals { } } -fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) { +fn text_alpha_from_coverage_ui(ui: &mut Ui, alpha_from_coverage: &mut AlphaFromCoverage) { let mut dark_mode_special = - *text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq; + *alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq; ui.horizontal(|ui| { ui.label("Text rendering:"); @@ -2380,9 +2397,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha ui.checkbox(&mut dark_mode_special, "Dark-mode special"); if dark_mode_special { - *text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq; + *alpha_from_coverage = AlphaFromCoverage::DARK_MODE_DEFAULT; } else { - let mut gamma = match text_alpha_from_coverage { + let mut gamma = match alpha_from_coverage { AlphaFromCoverage::Linear => 1.0, AlphaFromCoverage::Gamma(gamma) => *gamma, AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same @@ -2396,9 +2413,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha ); if gamma == 1.0 { - *text_alpha_from_coverage = AlphaFromCoverage::Linear; + *alpha_from_coverage = AlphaFromCoverage::Linear; } else { - *text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma); + *alpha_from_coverage = AlphaFromCoverage::Gamma(gamma); } } }); @@ -2812,6 +2829,7 @@ impl Widget for &mut FontTweak { scale, y_offset_factor, y_offset, + hinting_override, } = self; ui.label("Scale"); @@ -2827,6 +2845,19 @@ impl Widget for &mut FontTweak { ui.add(DragValue::new(y_offset).speed(-0.02)); ui.end_row(); + ui.label("hinting_override"); + ComboBox::from_id_salt("hinting_override") + .selected_text(match hinting_override { + None => "None", + Some(true) => "Enable", + Some(false) => "Disable", + }) + .show_ui(ui, |ui| { + ui.selectable_value(hinting_override, None, "None"); + ui.selectable_value(hinting_override, Some(true), "Enable"); + ui.selectable_value(hinting_override, Some(false), "Disable"); + }); + if ui.button("Reset").clicked() { *self = Default::default(); } diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index ec50255fd..dd3ef8a4f 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:784cbcdfd8deaf61e7b663f9416d67724e6a6a189a20ba3351908aa5c5f2deff -size 336159 +oid sha256:7051c34854469652d2d953f3110ebcf6fd60f8ee9a2b0c134d9f7255ef180ce5 +size 335353 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index 3fbf0ab56..e1974dc57 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:4cdde1dda0e64f584c769c72f5910a7035e6a4a86a074b590e88365f12570109 -size 94062 +oid sha256:49823cfa4dfba54e54d0122f2bbb246c1daad2ca3ba15071c1ca44eeb3662855 +size 92791 diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index b9d8b2f22..e1a204fd3 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:824d941ea538fd44fc374f5df1893eee2309004c0ee5e69a97f1c84a74b2b423 -size 182128 +oid sha256:1b65b6b1a3afe41337b8fe537525677284e49bd90be29cddb837787162ee452a +size 169596 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index fee7ad891..830fe723d 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:44ea7ac8c8e22eb51fbcb63f00c8510de0e6ae126d19ab44c5d708d979b5362b -size 100345 +oid sha256:9c3af0c37a6997abe549dd28450c41d3d18bbc99d9577997d493566fbb7f9277 +size 96709 diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 1d791cd6d..3b660d5c6 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -161,15 +161,11 @@ pub fn criterion_benchmark(c: &mut Criterion) { { let pixels_per_point = 1.0; - let max_texture_side = 8 * 1024; let wrap_width = 512.0; let font_id = egui::FontId::default(); let text_color = egui::Color32::WHITE; - let mut fonts = egui::epaint::text::Fonts::new( - max_texture_side, - egui::epaint::AlphaFromCoverage::default(), - egui::FontDefinitions::default(), - ); + let mut fonts = + egui::epaint::text::Fonts::new(Default::default(), egui::FontDefinitions::default()); { c.bench_function("text_layout_uncached", |b| { b.iter(|| { @@ -209,7 +205,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut rng = rand::rng(); b.iter(|| { - fonts.begin_pass(max_texture_side, egui::epaint::AlphaFromCoverage::default()); + fonts.begin_pass(egui::epaint::TextOptions::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_demo_lib/tests/snapshots/demos/Bézier Curve.png b/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png index fcab88179..80864779a 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:30929184fab7e7d5975243d86bcab79cd9f7a0c5d57dd9ae827464ff6570be7b -size 31795 +oid sha256:0c6f6847df5b3bfdcb020c1f897a57ffe0971e9de1e6977b19d3909730e1b9a5 +size 30957 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 373adc234..34e564457 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:cb944eca56724f6a2106ea8db2043dc94c0ea40bdd4cdeb0e520790f97cc9598 -size 27049 +oid sha256:43ef176837f05d1795eddda2fee344e935ff6d53edc26548c97eea191d4c6ca2 +size 25839 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png index a62936ff2..331277ebc 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e4a6476a2bb8980a9207868b77a253c65c0ba8433f843bb17e622856695b720 -size 27686 +oid sha256:55b899e115bbb7a17e0e40216479f8fb3a343deddf929e4af6af137a3bf6d4b8 +size 25632 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index 22341d7b3..9e9f9953a 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c1951b99908326b3f05ebb72aa4d02d0f297bdd925f38ded09041fae45400c1 -size 85217 +oid sha256:8ed04e25b2e961f44e2911a037c93729b0cb4de82b2e51cab145abbcb7b4efea +size 74543 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png index 9afd1794a..7c20e243b 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c303fa620a2c7bc491a0ac1f9afdf9601b352e0e5163526c5f8732edf6bd6b3 -size 63404 +oid sha256:1bc9711ea98472bae9267190a91d3240146f4ce9a0caf0cf462d322581ec96aa +size 60508 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png index 05a412548..58e0eafc7 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0df751bac5947c9bf6f82d075cf5670a562742b80d6c512bcd642da5ed449d26 -size 25975 +oid sha256:111caa91ae0658acc17a7dd49582b780ae706ba4ac7812d2a63e09a18a0be967 +size 25585 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png index 9b1b65636..346f06675 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e26e87f2909414b614278a1cf0b485cda425aceb5419906426615dccdcbef2b -size 20877 +oid sha256:7e34217e8a006721bc4525277b96cf99618e3275626aa054b97a8cb4c7c963ab +size 19937 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png index 69d55cd27..886077140 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20e3050bd41c7b9d225feb71f3bea3fdd1b8f749f77c4d140b5e560f53eb32b7 -size 10731 +oid sha256:e62836d9afa18cf4e486fe2819e652bf5df160026dc258201db0b99a75bdf7f1 +size 10125 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png index 2a12a3152..22c3c4574 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1227636b03a7d35db3482b19f6059ec7aaf03ca795edadd5338056be6f6a8f7f -size 126724 +oid sha256:c504102299780498c6b3e463e627588653446caa10bdebc4366900d0e46b649c +size 112349 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index 0385ab120..f7f2aed0e 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:c01e96bf0aab24dbcfc05f2a6dcb0ffcddff69ec2474797de4fbce0a0670a8cd -size 24964 +oid sha256:d44e5abe3f64d5f72bbf7519b6ead816adf1f8ad22711e8ef8f34f9574223d4b +size 24152 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 e14a4ecb5..e27747a96 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:ec357eafd145194f99c36a53a149a8b331fd691c5088df43ee96282b84bc81a4 -size 99439 +oid sha256:38d3d349f3c31b6e5a5a04984d290e2e36441b2ced7ac2060b6c2311715068d9 +size 96806 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index 99cfc8f5d..649470e27 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5c95085e3c78b3fa1cc39ebd032834bd5ab5a80c3a2cad482d8a5bcbad004b9 -size 18064 +oid sha256:1f949b6ce193d360e9624b9e29e21413021828237de944c594beb5c4c3fb60e1 +size 17320 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png index f2d914cc2..d981e7995 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28a69a52c07344576f2b5335497151e4e923b838dfaec9791402949ffb099c12 -size 116116 +oid sha256:a61c10399c3d48d2cbbb4c6cb3beeaf5b448d4a4eb56f6709ad22e2a67b6cff0 +size 111994 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png index c100d9209..4c36acb94 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1378d865af3df02e12a0c4bc087620a4e9ef0029221db3180cdd2fd34f69d7f -size 24832 +oid sha256:17027c0e50ca6f3543897420cab3d94156c514160e7c4db4ecc5db470e801ee0 +size 24014 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 dd2d414a4..4069db0ef 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:bb3f7b5f790830b46d1410c2bbb5e19c6beb403f8fe979eb8d250fba4f89be3e -size 51670 +oid sha256:1525cf27432b6d50a2dac99f400550f5018ddde8f62f77364c536455529e494b +size 49780 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png index 1ef6bf0db..c53dce298 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70b00222e6c63f97bfd8c7a179c15cfaba93f8f2566702d4b03997f4714fe6cb -size 22609 +oid sha256:2777a8d4d64983512d42074129d1c9d3bde2e43ec9f3b3b929a2df5320eb01e6 +size 21650 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png index ed84d7428..512d8cad6 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad26106a86a6236f0db1c51bed754b370530813e9bb6e36c1be2948820fbef25 -size 47827 +oid sha256:88a10a92d0fd0104c7883434b5e49f424f7100ab5013a456216c6bf5bb1e4076 +size 45904 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 9f42483fe..c9d616020 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:afa66ba8daca5c00f9c49b1d9173ad8f5e826247d3a9369d7e7c360cbdfcb72e -size 22928 +oid sha256:48867e0418b2002c5e3f6fae69e6a30d0ef69b3d2dc98a1f7ee175064596450f +size 21879 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 919bdc66d..e2f75c506 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:170cee9d72a4ab59aa2faf1b77aff4a9eee64f3380aa3f1b256340d88b1dabc2 -size 66525 +oid sha256:08b787f4e579746d87338b669d5754f8fecfdd1cb7cf23f6f3dcd1e483054dcb +size 63759 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png index 47718fdd1..f7e790857 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90ab689d8a5034f5cab2ae2b44a8054d6dd815b3d295bee040c5bfcdf4564dee -size 33063 +oid sha256:2ce9633fe06a9bd63d6219f0f7764fa5459a5441a35f385234aa5051495ad48a +size 32357 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index daa9da35a..fa4bd7e25 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf7f0a76424a959ede7afbb0eaf777638038cc6fe208ef710d9d82638d68b4d0 -size 37848 +oid sha256:dc89d49518a6a41c346cf9146657474f5b8898fa0f53e2aedc9c350c62865c41 +size 36721 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index e53f4f7af..3d4b71b39 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d2370972781f15a1d602deca28bca38f1c077152801870edf2112650b8b1349 -size 17708 +oid sha256:b040b83a49b599d0833ca3ce53ba974e0672e6a2eb1e711fc87e3a59718a7d89 +size 17003 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index ca9bacfca..8d5515e39 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:f7a7d0e2618b852b5966073438c95cb62901d5410c1473639920b0b0bf2ec59b -size 256913 +oid sha256:9da1fc5172d2d20ac44048f34b1cead35eb32a9bffebf8d9c031686880c767b7 +size 247070 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png index d00256a7e..c5b559c52 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8b937a8a63de6fedcd0f9748b1d04cd863331a297bec78906885a0107def32a -size 61242 +oid sha256:24088f20928106f51c38197b4c5d61d1c0d32371ca94018dc0f8b4f9e43700d0 +size 53228 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png b/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png index f96fce916..51553321b 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a11b0aeb8b8a7ff3acd54b99869af03cd04cc2edf13afc713ce924c52d39262d -size 24826 +oid sha256:ecfa78eda551ae362b65118b82054ec640ebd80ddcaf1eee5fbec002bba2bcde +size 24722 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index 2344a3868..3efeb1d80 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:2855bd95ab33b5232edada1f65684bbba2748025b6b64eb9ac68a5f2d10ad4bd -size 34491 +oid sha256:dbbd302b6dcd22b89567747ad791f1d05ead01292fb7146ef8ab04e99e2a6c97 +size 31886 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 7e49a8613..0e1bb5d90 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be599ae66323140bba4a7d63546acbf84340b57e2d82d4736bf3fe590040319d -size 23623 +oid sha256:c9d4f953f7cffc647da604e32caa8b122aec6e940b9af38756c4f8746d1a1b31 +size 22691 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 6de002ee4..90f904d47 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:01c3cb5e8972e0cab5325328f93af8f51b35a0d61016e74969eab0f7ddea1e02 -size 176973 +oid sha256:e2fa6340b31dfb2cf9b31b88391555357d84ec4bd062cb1039838d078af07c2f +size 170465 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 417b183b5..05a4d4324 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:f2f6cedc262259d52c1fbf4283d99b4b62ec732e8688b1e2799a2581425e0564 -size 120342 +oid sha256:d1a2c686b37d7d70d09a0236ce83e4c6eed6c72f8e3cc02331a99cc376115234 +size 116410 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index be51e5062..ee237957c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:142f65cee971f82a4917734c4f49ae233aa9a873028dca8c807d2825672bf2b2 -size 26657 +oid sha256:d3d99f7790cfe1eb9ff2e2f010756781bb5911cc8102c2d38ee3e82c04f8f944 +size 25450 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 188c548d8..03fa6274e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72f4c6fe4f5ec243506152027e1150f3069caf98511ceef92b8fea4f6a1563d5 -size 77614 +oid sha256:ab7d1620779aa75f6596d9f84707533f7f6b04bb9c51d8dfea10cfb7abdb7b73 +size 73525 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 cd8524cef..fa7ed7d65 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:cc24f146adf0282cfb51723b56c76eceb92f2988fc67bbeefd16b93950505922 -size 70110 +oid sha256:af2373c1ff32cc520f64e14a29a0c386b14df7dba04f6c281c20b537016b98dc +size 68101 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index 28c9f57ac..b738c6c1d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77aeaa1dcd391a571cb38732686e0b85b2d727975c02507a114d4e932f2c351b -size 65562 +oid sha256:9b6eedb91e29999e0300707f88a44ffa941c72639a89383a507ed7e8c5d80731 +size 59421 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 3ee3adf6e..54f2d8967 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f964939ed1b3904706592915ca4fbbb951855ac88b466c51b835cd1c7467fb0 -size 21501 +oid sha256:c050180f968ac82287212f6f12eb242dd1266fd920f249cc6d48d8c9bfa1abe6 +size 20814 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index 265dfdc1e..2acc75577 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:415b1ce17dd6df7ca7a86fed92750c2ef811ff64720a447ae3ca6be10090666e -size 64624 +oid sha256:c105e9267e81541d11e73a10fcb92c80ae8daeba6b9c586800b07264d5143071 +size 61536 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 fb2bd06aa..3f4d49bba 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:0eaf717bf0083737c4186ac39e7baf98f42fffb36b49434a6658eff1430a0ac6 -size 13187 +oid sha256:185b62db2f890b05a3fb9029dae8e4452a37e3caae5c7b993c9995aefa078eff +size 12813 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 e782c983a..3ca4bfecd 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:611a2d6c793a85eebe807b2ddd4446cc0bc21e4284343dd756e64f0232fb6815 -size 35991 +oid sha256:7425b60e0064dfd9e341fe55e059cbb6a372d526433cb3b1c234a105f16fd247 +size 34520 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png index 7a042bdba..6af84f673 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b85a2af24c3361a0008fd0996e8d7244dc3e289646ec7233e8bad39a586c871c -size 44512 +oid sha256:4731c35a62a88533940c12a06cf8d31479c59a538ef69109b3a655e2d7ef3e43 +size 42109 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png index 3d6c3f55c..aeaa46a34 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67b709c116f56fba7e4e9f182018e84f46f6c6dd33a51f9d0524125dc2056b8c -size 12950 +oid sha256:89074b8dab103a419bc3dac743da4d8c47f435fa55b98d8aab71f6c9fb4d39de +size 12370 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png index e28eaeda9..1359fd607 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b0a70c0d66306edbbc6f77d03ea624aa68b846656811d4cc7d76d28572d177b -size 30723 +oid sha256:968c478d986fc71d8655492b19e833ca07bc0ab85899dc04022bc7cf1dcf782f +size 29319 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png index 391257018..da72002a0 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:137ba69ac73a5e9aacc6bf3bbd589e8640b41c50ccfb49edcda4e2d6efed6c09 -size 13384 +oid sha256:7bd7b54ff60859e4d4793000bef3adbec4c071063bec6bfdbde62516c4fc3478 +size 12959 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png index f5e700ee3..80e561325 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f798c666ad21d3f9bb57826e30f2f6ef044543bc05af8c185e0e63c8297e824 -size 33181 +oid sha256:61e59f8360c567e20bf03b401362de7bb0f87716f13e817cc8da3df742ab68bf +size 31869 diff --git a/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.00.png index 0d70d6973..5018d53c8 100644 --- a/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.00.png +++ b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44fc7d745b478fe937fa7c131871a00b26712a0317aaa027a088782533be6136 -size 7125 +oid sha256:7389e319d9153af313cc113d97b57d462da00feb0d5f99da211552af3ac7e18a +size 6704 diff --git a/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.41.png b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.41.png index 505c764d5..3a1b72bb3 100644 --- a/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.41.png +++ b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x1.41.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abfa00ef9385d380bbd188d6254f92d6839a94f368100e75a2780337438f969f -size 11068 +oid sha256:d4480dd34ed36c6bdbc2084843dd136448b3934c22b3df3e40314ba6324b5b39 +size 10306 diff --git a/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x2.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x2.00.png index 7c040d71a..0f18eb109 100644 --- a/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x2.00.png +++ b/crates/egui_demo_lib/tests/snapshots/italics/image_dark_x2.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e5c9015e2005429ba83a407ed1f7d4dfbf30624f666152e82079c6ed3b3cda5 -size 17238 +oid sha256:624bfa884431c35cc5d852b96653f13da17e60f8545471f9fb1c3bb85b40ffc8 +size 16555 diff --git a/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.00.png index b2291e9f1..6ebc6addc 100644 --- a/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.00.png +++ b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3319a8bca1213fc3a2dd91ead155be1e25045bc614701250bc961848cfc42176 -size 7327 +oid sha256:bf665389ef43524e097a7ae4eec0aa01bb788bdbb306144f20f9133f74a64b2c +size 6941 diff --git a/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.41.png b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.41.png index 995789de1..54fdc064b 100644 --- a/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.41.png +++ b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x1.41.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5172aa12f07b4abf4bb217b8952b4cf5cf61b688455751964a1b54433d8c05b1 -size 11709 +oid sha256:a2480d0f49a929993de70612572b321457b2507c149a25112064cfc27840e6ee +size 11005 diff --git a/crates/egui_demo_lib/tests/snapshots/italics/image_light_x2.00.png b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x2.00.png index 8c5e7ab03..138fd8c83 100644 --- a/crates/egui_demo_lib/tests/snapshots/italics/image_light_x2.00.png +++ b/crates/egui_demo_lib/tests/snapshots/italics/image_light_x2.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e3eedd952d4416af73179451c0c90bcb76635c9c3c94d37f42bdd228ddbdd03 -size 18802 +oid sha256:1e0013b499934f47370a3a20b3d3a19f8a8c6db360752a35a3fb1d676d122263 +size 18068 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index a184b67dc..c2d36be22 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_1.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60a44771c6bc9236593717236f9eca40bcb4723bac7567493cab4326f003eba3 -size 48693 +oid sha256:0054283b203602742d9819ba275c44ea40211ba18bf56fc66dca4fca766184d3 +size 47076 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index 0aa16858a..4aa3fa2f4 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7bc441559ff2d8723cf344113ce5ff8158e41179e4c93abcacbe7b1b13b3723 -size 48998 +oid sha256:6a40e3e7314cb32398797665b2bebb237f1a9cc79e306df9c29f7f04faa3a435 +size 47715 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index eaae2a758..75810326b 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e092be54efaeb700a63d9b679894647159f39a0d3062692ac7056e98242cbee -size 45364 +oid sha256:b1ddff4f50b9a245d270cc13a0ebb9ac71d3590b83d401afb10ab107439cf235 +size 43893 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index 8b54bf99f..4b0eb7a17 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88930779ac199e42fcc9ee25f29bd120478c129807713218370b617905340087 -size 45366 +oid sha256:50dea7c459cf291e6c0b3166354a7e622af6682842ec36f295d532df8c064b38 +size 43984 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 640d84c2b..b5367f0ed 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:f9980486c36a0242f3b043a172c411d4fc9573543d3cd7c1c43bf020c18868a9 -size 619816 +oid sha256:db510af76578693c85ce78ca91224758a56f7bbf33db3221c9a4edca08b06600 +size 590547 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 df05ada25..7158a3545 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:040e2e486ae4773a084da99513a53c620e8e2bba215183ec26c6e489381d6254 -size 823086 +oid sha256:cae2b789e8afff23b7545d42a530e6c972d28736bad2bdacbc69f0e7065f85cc +size 740660 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 c6aa2914f..8b9cb281f 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:439a5f942a5f05b9c09685ef90be94c150a21a68d1d235af22372b9b6a7b7389 -size 1035734 +oid sha256:09d9f567ec371d60881b525ddb462d9135552db97af5921a6eb02aba40e40616 +size 971544 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 0fc009f8a..9f5a69154 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:3a3a9aa8383abfe4580be2cc9987f8123aeabf36bf8ec06029a9af64b9500ec9 -size 1206157 +oid sha256:3c383dd89fda6094704027074a72085591339a276d60502626d78e8e527b2e10 +size 1076719 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 9804e2942..74760261a 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:fedd5546e36a89121c0bb0a780b0bbe081611c2c04013064872801181503fb83 -size 1231599 +oid sha256:0b4559541cf3259496c760a26f8d83e82179cb7e4576333682c5af49ee4a35a7 +size 1125331 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 04d4bb4ea..a85909178 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:69a7040336fc92c6d7b158283aabbc5817980c2fa84a1120b788516cf437b985 -size 1461979 +oid sha256:67c8412a1e8fdbfd88f8573797fbf6fbd89c6ce783a074a8e90f7d8d9e67dd57 +size 1366351 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 50e84c900..0eb5ebd6a 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:1a32d361afa20fc8c20122a89b01fe14b45849da42663991e589579f70fb8577 -size 46790 +oid sha256:a2b7b54a1af0f5cd31bd64f0506e3035dd423314ce3389e61730fa160434fbf3 +size 45074 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 ca5a23f97..bd9942eb7 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:c8d55205cf4225123da33895ed45eb186e5e57184ef5928400a4bae3ab6092be -size 88548 +oid sha256:7b66a0be67ff2d684a54c2321123521b3ad06dfe5ebffd50e89260d77efcfcc4 +size 86833 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 41f9ef2f5..64ddf5c49 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:77ab9e2e18c788f8cdbec171269afe4d0a90c52abeda7063cae3766dcaa5e93b -size 120612 +oid sha256:19320291c99a23429b114a59de4636689e281e1e68766abe2aa1e56562128e50 +size 118919 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 85d844c79..105bbf285 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:36622a2f934503a7b60ded2f44b002e37eedde22d548dcf5a209f54c19548665 -size 53064 +oid sha256:5edf089c00715f1456fe7838e85aadcfc42b6216a3fd95b48d9c21fc8d700cba +size 51371 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 6e6d9f0f2..035eb931d 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:4adeb7a77a0d0fe85097fcd190a99b49858dce11bde601d30335afcb6cc3d5f6 -size 56276 +oid sha256:6cd1a10639dcb323bdc3b2c43e0c35665184fc809731ced90088ee9edb9de845 +size 54577 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 eefbac2bb..26014a12c 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:f45249f7cc90433a64856905c727571c4ef20468e2c7d3ac2029e18a0477932d -size 56743 +oid sha256:87e34024f701dc93f4026213ac7eb468a2cd6d3393eb0dbec382bf58007f8e61 +size 55042 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 08178eaa8..dcbbba2b6 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:d437f68c521f3e627a1b50d46605e8b0b343c5fc3716d9669e8c4436c8992b6f -size 37602 +oid sha256:d7940ff56796efb27bec66b632ff33aa2ad390c4962a711bf520aee341f035a4 +size 35968 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 13f057df9..0a3d062af 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:08ba98437403a08cca825ed8e288c9f088a46d9a1081f0b0a4ed7fbb9b49bb85 -size 37640 +oid sha256:b7bbd16c8aad444f0d11aacf87cf2292d494cc80a1ca46e7e8db86ca3041d35a +size 35931 diff --git a/crates/egui_demo_lib/tests/snapshots/text_selection.png b/crates/egui_demo_lib/tests/snapshots/text_selection.png index 78ebc0dbf..63a4423a3 100644 --- a/crates/egui_demo_lib/tests/snapshots/text_selection.png +++ b/crates/egui_demo_lib/tests/snapshots/text_selection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14f253fedc94985ff1431f1016d901d747e1f9948531cc6350f6615649f29056 -size 4862 +oid sha256:0475c5ac04ab8f79b79d43cfdb985f05b61dbe90e81f898a6dc216c308a28841 +size 4707 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 ab2db5eb5..112605454 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:e1ed0e40d08b2b9ea978a07a4b7bf282c9e2cba8c52896f12210f396241e1b78 -size 66859 +oid sha256:bbdc4199dee2ae853b8a240cd84528482dc6762233bd0d1249f2daa296b49487 +size 64172 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 e84863bf4..1b5b60c8a 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:28f9862dd6f16b99523f5880bf90346fd9455d0d44e7bdd530d523e0b2ef2d2c -size 158892 +oid sha256:f6d38b6b47839d0e4eae530d203c83971fba8a41c9caa3d5b5d89ee7ed582613 +size 150090 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 87eb5ce70..5a2b44feb 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:98c40c99a237f8388d82259fba4388d7b1759fe7b4bf657d326532934135c934 -size 61119 +oid sha256:c0635f1564d6c9707efa68003fb8c9b6eb00408aa8f24c972e33c6c79fed5bdf +size 59354 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 c2c4705e1..81c7452e6 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:90d36311ce5b1dcf81cab22113620a3362f255059d1f52c6794c8249f960549e -size 152215 +oid sha256:4288ee4a0d2229d59c31538179cdda50035a3849f69b400127e1618efe30cdc1 +size 145224 diff --git a/crates/egui_kittest/tests/snapshots/combobox_closed.png b/crates/egui_kittest/tests/snapshots/combobox_closed.png index bb574d356..708985b14 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_closed.png +++ b/crates/egui_kittest/tests/snapshots/combobox_closed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f297609dd69fd479377eaea7cd304b717e0523a650dbf76e19c6d1f1c5af1343 -size 4518 +oid sha256:3ca39801faddae7191ed054029263e8eca488d16e1fcbb40fed482d39fc89e8e +size 4520 diff --git a/crates/egui_kittest/tests/snapshots/combobox_opened.png b/crates/egui_kittest/tests/snapshots/combobox_opened.png index e45a4aed3..53a9c8ed1 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_opened.png +++ b/crates/egui_kittest/tests/snapshots/combobox_opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42911cbb500fa49170aac0da8e4167641c5d7c9724a6accd4d400258fc74e2d7 -size 8061 +oid sha256:bafe5d7129cd2137b8f7bc9662b894d959b7042c436443f835ecd421a0d9c33f +size 8019 diff --git a/crates/egui_kittest/tests/snapshots/image_snapshots.png b/crates/egui_kittest/tests/snapshots/image_snapshots.png index 5036d662c..75e18ba5f 100644 --- a/crates/egui_kittest/tests/snapshots/image_snapshots.png +++ b/crates/egui_kittest/tests/snapshots/image_snapshots.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de4a197f82befde31b6966f902567c35cef96c7d541fd65b4207c693ab12bace -size 2036 +oid sha256:cd2ff48cae729b3f957b1630bef23e94fb2176982c06ec3f123c1a0892fc536d +size 1959 diff --git a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png index c30b3fdd1..ffc93c942 100644 --- a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png +++ b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94ba2e648c981bf4afbd9b9d01eef0708f7067be6e4cefbdfacc13aa219c6289 -size 11253 +oid sha256:27e4950f17ab7f68f7e001ca3a4f3fc18943103f4745c87715dcf6c097e92a57 +size 11131 diff --git a/crates/egui_kittest/tests/snapshots/menu/opened.png b/crates/egui_kittest/tests/snapshots/menu/opened.png index 7a2750454..990bed40e 100644 --- a/crates/egui_kittest/tests/snapshots/menu/opened.png +++ b/crates/egui_kittest/tests/snapshots/menu/opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:436999f511dce318f29172f0b7e2007e1f0fedae58f5e0e85e19f1d8e0bee361 -size 22273 +oid sha256:449cc473469dc80af81fe20e394dd90e67b4ae9c2033ebb7029726274d77d50c +size 21644 diff --git a/crates/egui_kittest/tests/snapshots/menu/submenu.png b/crates/egui_kittest/tests/snapshots/menu/submenu.png index 25453c8d9..7bd7c356d 100644 --- a/crates/egui_kittest/tests/snapshots/menu/submenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/submenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28435faf5c8c6d880cd50d52050c9f4cd6b992d0c621f01ca28fb5502eed16a1 -size 29863 +oid sha256:1cbfc767ed169cbddb3c90c2b455daefd85925501e7e33c7a25a34a72fc13eac +size 28512 diff --git a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png index c22c2b9b6..1e3b905b1 100644 --- a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9a364b4b8c4ad3e78a80b0c6825d9de28c0e0d2e18dcfcd0ff18652ca86c859 -size 34750 +oid sha256:f4397b3d86bb5fe76d661cdb109ef71d43e771093bd9267b74722660d312ec7d +size 33253 diff --git a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png index 6e92f9f03..fb8887d13 100644 --- a/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png +++ b/crates/egui_kittest/tests/snapshots/override_text_color_interactive.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fa5cb5b96232d729f89be8cc92263715fe7197e72343b71e57e53a359afe181 -size 19881 +oid sha256:e8038005841dbf272375388b224dcc9fc1177b5c113d3e6f6dbc2265c88c7e60 +size 19704 diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index f58e6faec..f7a5dcf62 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:87c76a9d07174e4e24ad3d08585c1df7bf3628bdc8f183d11beb6f9e14c4b2ec -size 2325 +oid sha256:fad8b83e553ffa6bfbc4d47b955f2180859048c3789dda99b640e27665d216c7 +size 2244 diff --git a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png index 7e33877fc..9709e159e 100644 --- a/crates/egui_kittest/tests/snapshots/should_wait_for_images.png +++ b/crates/egui_kittest/tests/snapshots/should_wait_for_images.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f038c7fdf31f35107ec6e29edc0895049160ccbe98d1577c16ae082605b58d52 -size 2207 +oid sha256:ad75a0e568e04c20d0e3b823c7e4906c39dcd0a69a086d8e30714a9e4530d031 +size 2128 diff --git a/crates/egui_kittest/tests/snapshots/test_masking.png b/crates/egui_kittest/tests/snapshots/test_masking.png index a5fb50908..a397ceda6 100644 --- a/crates/egui_kittest/tests/snapshots/test_masking.png +++ b/crates/egui_kittest/tests/snapshots/test_masking.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d94d4c3300d406fd1d93ddd90a9a46f99eac81a98a84f4d97a20fe4ef492e5d -size 5674 +oid sha256:4216258893fae554f0ab8b3a76ef0905cacb62c70af47fa811ff6f3d99f9f3ab +size 5619 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png index 586dc00b7..e61dc99a0 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:460cb2e9c91d139334c797829fd82fa77d593e9e58531e57a6b649c7e8fad228 -size 7405 +oid sha256:eb8737af84c3d3b0c054b7e2a8bcb04685243d84cb13b72a1372dc40dbfd14fb +size 7267 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png index 64feb6323..0cc3d6d15 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:996985b155bd579cc4769d8cde5aa7e87c20ed909176da6b52dddeeb78a1cfba -size 8290 +oid sha256:f1651bb1b9bbaa3c65ecd07c39c57527f4beb4c607581a5b2596a49dcf4c5db3 +size 7996 diff --git a/crates/egui_kittest/tests/snapshots/test_shrink.png b/crates/egui_kittest/tests/snapshots/test_shrink.png index c25ae0367..40f2e284d 100644 --- a/crates/egui_kittest/tests/snapshots/test_shrink.png +++ b/crates/egui_kittest/tests/snapshots/test_shrink.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:025942c144891b8862bf931385824e0484e60f4e7766f5d4401511c72ff20756 -size 2975 +oid sha256:21a92c29e27ef0fdec273ea2d94a2b3e74cdf380ec77f4783daeb008bd51db6d +size 2767 diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png b/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png index c25ae0367..40f2e284d 100644 --- a/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png +++ b/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:025942c144891b8862bf931385824e0484e60f4e7766f5d4401511c72ff20756 -size 2975 +oid sha256:21a92c29e27ef0fdec273ea2d94a2b3e74cdf380ec77f4783daeb008bd51db6d +size 2767 diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png index 4d00c924a..86cc5a717 100644 --- a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png +++ b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e269ede9c0784d00c153d51a13566d9c8f0d61ce11565997691fa63be06ec889 -size 5075 +oid sha256:f9ca5f8081d677b8bff47813c4eb94319ca03855e780aed834ecc2f3d905a22c +size 4852 diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 3ac99a97f..77facdb3f 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -61,12 +61,14 @@ _override_unity = [] emath.workspace = true ecolor.workspace = true -ab_glyph.workspace = true ahash.workspace = true log.workspace = true nohash-hasher.workspace = true parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. -profiling = { workspace = true } +profiling.workspace = true +self_cell.workspace = true +skrifa.workspace = true +vello_cpu.workspace = true #! ### Optional dependencies bytemuck = { workspace = true, optional = true, features = ["derive"] } diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 7b97b20a2..8fbfc65ea 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -1,8 +1,8 @@ use criterion::{Criterion, criterion_group, criterion_main}; use epaint::{ - AlphaFromCoverage, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, - TessellationOptions, Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, + ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions, + Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path, }; use std::hint::black_box; @@ -68,7 +68,7 @@ fn tessellate_circles(c: &mut Criterion) { let pixels_per_point = 2.0; let options = TessellationOptions::default(); - let atlas = TextureAtlas::new([4096, 256], AlphaFromCoverage::default()); + let atlas = TextureAtlas::new([4096, 256], Default::default()); let font_tex_size = atlas.size(); let prepared_discs = atlas.prepared_discs(); diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 1bf0285bd..942ca5452 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -62,7 +62,7 @@ pub use self::{ stats::PaintStats, stroke::{PathStroke, Stroke, StrokeKind}, tessellator::{TessellationOptions, Tessellator}, - text::{FontFamily, FontId, Fonts, FontsView, Galley}, + text::{FontFamily, FontId, Fonts, FontsView, Galley, TextOptions}, texture_atlas::TextureAtlas, texture_handle::TextureHandle, textures::TextureManager, diff --git a/crates/epaint/src/shapes/text_shape.rs b/crates/epaint/src/shapes/text_shape.rs index 92a0a0514..3e177db07 100644 --- a/crates/epaint/src/shapes/text_shape.rs +++ b/crates/epaint/src/shapes/text_shape.rs @@ -185,11 +185,7 @@ mod tests { #[test] fn text_bounding_box_under_rotation() { - let mut fonts = Fonts::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = Fonts::new(TextOptions::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 4584c3457..0dbc00ddd 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -1,13 +1,20 @@ +#![allow(clippy::mem_forget)] + use std::collections::BTreeMap; -use ab_glyph::{Font as _, OutlinedGlyph, PxScale}; use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2}; +use self_cell::self_cell; +use skrifa::{ + MetadataProvider as _, + raw::{TableProvider as _, tables::kern::SubtableKind}, +}; +use vello_cpu::{color, kurbo}; use crate::{ - TextureAtlas, + TextOptions, TextureAtlas, text::{ FontTweak, - fonts::{CachedFamily, FontFaceKey}, + fonts::{Blob, CachedFamily, FontFaceKey}, }, }; @@ -43,9 +50,9 @@ pub struct GlyphInfo { /// Doesn't need to be unique. /// /// Is `None` for a special "invisible" glyph. - pub(crate) id: Option, + pub(crate) id: Option, - /// In [`ab_glyph`]s "unscaled" coordinate system. + /// In [`skrifa`]s "unscaled" coordinate system. pub advance_width_unscaled: OrderedFloat, } @@ -123,8 +130,8 @@ pub struct GlyphAllocation { /// Used for pair-kerning. /// /// Doesn't need to be unique. - /// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care. - pub(crate) id: ab_glyph::GlyphId, + /// Use [`skrifa::GlyphId::NOTDEF`] if you just want to have an id, and don't care. + pub(crate) id: skrifa::GlyphId, /// Unit: screen pixels. pub advance_width_px: f32, @@ -139,7 +146,7 @@ struct GlyphCacheKey(u64); impl nohash_hasher::IsEnabled for GlyphCacheKey {} impl GlyphCacheKey { - fn new(glyph_id: ab_glyph::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self { + fn new(glyph_id: skrifa::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self { let ScaledMetrics { pixels_per_point, px_scale_factor, @@ -164,41 +171,229 @@ impl GlyphCacheKey { // ---------------------------------------------------------------------------- +struct DependentFontData<'a> { + skrifa: skrifa::FontRef<'a>, + charmap: skrifa::charmap::Charmap<'a>, + outline_glyphs: skrifa::outline::OutlineGlyphCollection<'a>, + metrics: skrifa::metrics::Metrics, + glyph_metrics: skrifa::metrics::GlyphMetrics<'a>, + hinting_instance: Option, +} + +self_cell! { + struct FontCell { + owner: Blob, + + #[covariant] + dependent: DependentFontData, + } +} + +impl FontCell { + fn px_scale_factor(&self, scale: f32) -> f32 { + let units_per_em = self.borrow_dependent().metrics.units_per_em as f32; + scale / units_per_em + } + + fn allocate_glyph_uncached( + &mut self, + atlas: &mut TextureAtlas, + metrics: &ScaledMetrics, + glyph_info: &GlyphInfo, + bin: SubpixelBin, + ) -> Option { + let glyph_id = glyph_info.id?; + + debug_assert!( + glyph_id != skrifa::GlyphId::NOTDEF, + "Can't allocate glyph for id 0" + ); + + let mut path = kurbo::BezPath::new(); + let mut pen = VelloPen { + path: &mut path, + x_offset: bin.as_float() as f64, + }; + + self.with_dependent_mut(|_, font_data| { + let outline = font_data.outline_glyphs.get(glyph_id)?; + + if let Some(hinting_instance) = &mut font_data.hinting_instance { + let size = skrifa::instance::Size::new(metrics.scale); + if hinting_instance.size() != size { + hinting_instance + .reconfigure( + &font_data.outline_glyphs, + size, + skrifa::instance::LocationRef::default(), + skrifa::outline::Target::Smooth { + mode: skrifa::outline::SmoothMode::Normal, + symmetric_rendering: true, + preserve_linear_metrics: true, + }, + ) + .ok()?; + } + let draw_settings = skrifa::outline::DrawSettings::hinted(hinting_instance, false); + outline.draw(draw_settings, &mut pen).ok()?; + } else { + let draw_settings = skrifa::outline::DrawSettings::unhinted( + skrifa::instance::Size::new(metrics.scale), + skrifa::instance::LocationRef::default(), + ); + outline.draw(draw_settings, &mut pen).ok()?; + } + + Some(()) + })?; + + let bounds = path.control_box().expand(); + let width = bounds.width() as u16; + let height = bounds.height() as u16; + + let mut ctx = vello_cpu::RenderContext::new(width, height); + ctx.set_transform(kurbo::Affine::translate((-bounds.x0, -bounds.y0))); + ctx.set_paint(color::OpaqueColor::::WHITE); + ctx.fill_path(&path); + let mut dest = vello_cpu::Pixmap::new(width, height); + ctx.render_to_pixmap(&mut dest); + let uv_rect = if width == 0 || height == 0 { + UvRect::default() + } else { + let glyph_pos = { + let alpha_from_coverage = atlas.options().alpha_from_coverage; + let (glyph_pos, image) = atlas.allocate((width as usize, height as usize)); + let pixels = dest.data_as_u8_slice(); + for y in 0..height as usize { + for x in 0..width as usize { + image[(x + glyph_pos.0, y + glyph_pos.1)] = alpha_from_coverage + .color_from_coverage( + pixels[((y * width as usize) + x) * 4 + 3] as f32 / 255.0, + ); + } + } + glyph_pos + }; + let offset_in_pixels = vec2(bounds.x0 as f32, bounds.y0 as f32); + let offset = + offset_in_pixels / metrics.pixels_per_point + metrics.y_offset_in_points * Vec2::Y; + UvRect { + offset, + size: vec2(width as f32, height as f32) / metrics.pixels_per_point, + min: [glyph_pos.0 as u16, glyph_pos.1 as u16], + max: [ + (glyph_pos.0 + width as usize) as u16, + (glyph_pos.1 + height as usize) as u16, + ], + } + }; + + Some(GlyphAllocation { + id: glyph_id, + advance_width_px: glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor, + uv_rect, + }) + } +} + +struct VelloPen<'a> { + path: &'a mut kurbo::BezPath, + x_offset: f64, +} + +impl skrifa::outline::OutlinePen for VelloPen<'_> { + fn move_to(&mut self, x: f32, y: f32) { + self.path.move_to((x as f64 + self.x_offset, -y as f64)); + } + + fn line_to(&mut self, x: f32, y: f32) { + self.path.line_to((x as f64 + self.x_offset, -y as f64)); + } + + fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) { + self.path.quad_to( + (cx0 as f64 + self.x_offset, -cy0 as f64), + (x as f64 + self.x_offset, -y as f64), + ); + } + + fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) { + self.path.curve_to( + (cx0 as f64 + self.x_offset, -cy0 as f64), + (cx1 as f64 + self.x_offset, -cy1 as f64), + (x as f64 + self.x_offset, -y as f64), + ); + } + + fn close(&mut self) { + self.path.close_path(); + } +} + /// A specific font face. /// The interface uses points as the unit for everything. -pub struct FontImpl { +pub struct FontFace { name: String, - ab_glyph_font: ab_glyph::FontArc, + font: FontCell, tweak: FontTweak, glyph_info_cache: ahash::HashMap, glyph_alloc_cache: ahash::HashMap, } -trait FontExt { - fn px_scale_factor(&self, scale: f32) -> f32; -} +impl FontFace { + pub fn new( + options: TextOptions, + name: String, + font_data: Blob, + index: u32, + tweak: FontTweak, + ) -> Result> { + let font = FontCell::try_new(font_data, |font_data| { + let skrifa_font = + skrifa::FontRef::from_index(AsRef::<[u8]>::as_ref(font_data.as_ref()), index)?; -impl FontExt for T -where - T: ab_glyph::Font, -{ - fn px_scale_factor(&self, scale: f32) -> f32 { - let units_per_em = self.units_per_em().unwrap_or_else(|| { - panic!("The font unit size exceeds the expected range (16..=16384)") - }); - scale / units_per_em - } -} + let charmap = skrifa_font.charmap(); + let glyphs = skrifa_font.outline_glyphs(); + let metrics = skrifa_font.metrics( + skrifa::instance::Size::unscaled(), + skrifa::instance::LocationRef::default(), + ); + let glyph_metrics = skrifa_font.glyph_metrics( + skrifa::instance::Size::unscaled(), + skrifa::instance::LocationRef::default(), + ); -impl FontImpl { - pub fn new(name: String, ab_glyph_font: ab_glyph::FontArc, tweak: FontTweak) -> Self { - Self { + let hinting_enabled = tweak.hinting_override.unwrap_or(options.font_hinting); + let hinting_instance = hinting_enabled + .then(|| { + // It doesn't really matter what we put here for options. Since the size is `unscaled()`, we will + // always reconfigure this hinting instance with the real options when rendering for the first time. + skrifa::outline::HintingInstance::new( + &glyphs, + skrifa::instance::Size::unscaled(), + skrifa::instance::LocationRef::default(), + skrifa::outline::Target::default(), + ) + .ok() + }) + .flatten(); + + Ok::, Box>(DependentFontData { + skrifa: skrifa_font, + charmap, + outline_glyphs: glyphs, + metrics, + glyph_metrics, + hinting_instance, + }) + })?; + Ok(Self { name, - ab_glyph_font, + font, tweak, glyph_info_cache: Default::default(), glyph_alloc_cache: Default::default(), - } + }) } /// Code points that will always be replaced by the replacement character. @@ -223,10 +418,11 @@ impl FontImpl { /// An un-ordered iterator over all supported characters. fn characters(&self) -> impl Iterator + '_ { - self.ab_glyph_font - .codepoint_ids() - .map(|(_, chr)| chr) - .filter(|&chr| !self.ignore_character(chr)) + self.font + .borrow_dependent() + .charmap + .mappings() + .filter_map(|(chr, _)| char::from_u32(chr).filter(|c| !self.ignore_character(*c))) } /// `\n` will result in `None` @@ -258,7 +454,7 @@ impl FontImpl { // https://en.wikipedia.org/wiki/Thin_space if let Some(space) = self.glyph_info(' ') { - let em = self.ab_glyph_font.units_per_em().unwrap_or(1.0); + let em = self.font.borrow_dependent().metrics.units_per_em as f32; let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); // TODO(emilk): make configurable let glyph_info = GlyphInfo { advance_width_unscaled: advance_width.into(), @@ -275,52 +471,68 @@ impl FontImpl { return Some(glyph_info); } - // Add new character: - let glyph_id = self.ab_glyph_font.glyph_id(c); + let font_data = self.font.borrow_dependent(); - if glyph_id.0 == 0 { - None // unsupported character - } else { - let glyph_info = GlyphInfo { - id: Some(glyph_id), - advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(), - }; - self.glyph_info_cache.insert(c, glyph_info); - Some(glyph_info) - } + // Add new character: + let glyph_id = font_data + .charmap + .map(c) + .filter(|id| *id != skrifa::GlyphId::NOTDEF)?; + + let glyph_info = GlyphInfo { + id: Some(glyph_id), + advance_width_unscaled: font_data + .glyph_metrics + .advance_width(glyph_id) + .unwrap_or_default() + .into(), + }; + self.glyph_info_cache.insert(c, glyph_info); + Some(glyph_info) } #[inline] pub(super) fn pair_kerning_pixels( &self, metrics: &ScaledMetrics, - last_glyph_id: ab_glyph::GlyphId, - glyph_id: ab_glyph::GlyphId, + last_glyph_id: skrifa::GlyphId, + glyph_id: skrifa::GlyphId, ) -> f32 { - self.ab_glyph_font.kern_unscaled(last_glyph_id, glyph_id) * metrics.px_scale_factor + let skrifa_font = &self.font.borrow_dependent().skrifa; + let Ok(kern) = skrifa_font.kern() else { + return 0.0; + }; + kern.subtables() + .find_map(|st| match st.ok()?.kind().ok()? { + SubtableKind::Format0(table_ref) => table_ref.kerning(last_glyph_id, glyph_id), + SubtableKind::Format1(_) => None, + SubtableKind::Format2(subtable2) => subtable2.kerning(last_glyph_id, glyph_id), + SubtableKind::Format3(table_ref) => table_ref.kerning(last_glyph_id, glyph_id), + }) + .unwrap_or_default() as f32 + * metrics.px_scale_factor } #[inline] pub fn pair_kerning( &self, metrics: &ScaledMetrics, - last_glyph_id: ab_glyph::GlyphId, - glyph_id: ab_glyph::GlyphId, + last_glyph_id: skrifa::GlyphId, + glyph_id: skrifa::GlyphId, ) -> f32 { self.pair_kerning_pixels(metrics, last_glyph_id, glyph_id) / metrics.pixels_per_point } #[inline(always)] pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics { - let pt_scale_factor = self - .ab_glyph_font - .px_scale_factor(font_size * self.tweak.scale); - let ascent = (self.ab_glyph_font.ascent_unscaled() * pt_scale_factor).round_ui(); - let descent = (self.ab_glyph_font.descent_unscaled() * pt_scale_factor).round_ui(); - let line_gap = (self.ab_glyph_font.line_gap_unscaled() * pt_scale_factor).round_ui(); + let pt_scale_factor = self.font.px_scale_factor(font_size * self.tweak.scale); + let font_data = self.font.borrow_dependent(); + let ascent = (font_data.metrics.ascent * pt_scale_factor).round_ui(); + let descent = (font_data.metrics.descent * pt_scale_factor).round_ui(); + let line_gap = (font_data.metrics.leading * pt_scale_factor).round_ui(); let scale = font_size * self.tweak.scale * pixels_per_point; - let px_scale_factor = self.ab_glyph_font.px_scale_factor(scale); + let px_scale_factor = self.font.px_scale_factor(scale); let y_offset_in_points = ((font_size * self.tweak.scale * self.tweak.y_offset_factor) + self.tweak.y_offset) @@ -329,6 +541,7 @@ impl FontImpl { ScaledMetrics { pixels_per_point, px_scale_factor, + scale, y_offset_in_points, ascent, row_height: ascent - descent + line_gap, @@ -370,77 +583,20 @@ impl FontImpl { std::collections::hash_map::Entry::Vacant(entry) => entry, }; - debug_assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0"); + let allocation = self + .font + .allocate_glyph_uncached(atlas, metrics, &glyph_info, bin) + .unwrap_or_default(); - let uv_rect = self.ab_glyph_font.outline(glyph_id).map(|outline| { - let glyph = ab_glyph::Glyph { - id: glyph_id, - // We bypass ab-glyph's scaling method because it uses the wrong scale - // (https://github.com/alexheretic/ab-glyph/issues/15), and this field is never accessed when - // rasterizing. We can just put anything here. - scale: PxScale::from(0.0), - position: ab_glyph::Point { - x: bin.as_float(), - y: 0.0, - }, - }; - let outlined = OutlinedGlyph::new( - glyph, - outline, - ab_glyph::PxScaleFactor { - horizontal: metrics.px_scale_factor, - vertical: metrics.px_scale_factor, - }, - ); - let bb = outlined.px_bounds(); - let glyph_width = bb.width() as usize; - let glyph_height = bb.height() as usize; - if glyph_width == 0 || glyph_height == 0 { - UvRect::default() - } else { - let glyph_pos = { - let text_alpha_from_coverage = atlas.text_alpha_from_coverage; - let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height)); - outlined.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)] = text_alpha_from_coverage.color_from_coverage(v); - } - }); - glyph_pos - }; - - let offset_in_pixels = vec2(bb.min.x, bb.min.y); - let offset = offset_in_pixels / metrics.pixels_per_point - + metrics.y_offset_in_points * Vec2::Y; - UvRect { - offset, - size: vec2(glyph_width as f32, glyph_height as f32) / metrics.pixels_per_point, - min: [glyph_pos.0 as u16, glyph_pos.1 as u16], - max: [ - (glyph_pos.0 + glyph_width) as u16, - (glyph_pos.1 + glyph_height) as u16, - ], - } - } - }); - let uv_rect = uv_rect.unwrap_or_default(); - - let allocation = GlyphAllocation { - id: glyph_id, - advance_width_px, - uv_rect, - }; entry.insert(allocation); (allocation, h_pos_round) } } // TODO(emilk): rename? -/// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis) +/// Wrapper over multiple [`FontFace`] (e.g. a primary + fallbacks for emojis) pub struct Font<'a> { - pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap, + pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap, pub(super) cached_family: &'a mut CachedFamily, pub(super) atlas: &'a mut TextureAtlas, } @@ -471,7 +627,7 @@ impl Font<'_> { .fonts .first() .and_then(|key| self.fonts_by_id.get(key)) - .map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size)) + .map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size)) .unwrap_or_default() } @@ -479,7 +635,7 @@ impl Font<'_> { pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 { let (key, glyph_info) = self.glyph_info(c); if let Some(font) = &self.fonts_by_id.get(&key) { - glyph_info.advance_width_unscaled.0 * font.ab_glyph_font.px_scale_factor(font_size) + glyph_info.advance_width_unscaled.0 * font.font.px_scale_factor(font_size) } else { 0.0 } @@ -524,7 +680,10 @@ pub struct ScaledMetrics { /// Translates "unscaled" units to physical (screen) pixels. pub px_scale_factor: f32, - /// Vertical offset, in UI points. + /// Absolute scale in screen pixels, for skrifa. + pub scale: f32, + + /// Vertical offset, in UI points (not screen-space). pub y_offset_in_points: f32, /// This is the distance from the top to the baseline. @@ -540,7 +699,7 @@ pub struct ScaledMetrics { /// Code points that will always be invisible (zero width). /// -/// See also [`FontImpl::ignore_character`]. +/// See also [`FontFace::ignore_character`]. #[inline] fn invisible_char(c: char) -> bool { if c == '\r' { diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 6bc7ccf8f..a2c836d74 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, collections::BTreeMap, sync::{ Arc, @@ -7,10 +8,10 @@ use std::{ }; use crate::{ - AlphaFromCoverage, TextureAtlas, + TextureAtlas, text::{ - Galley, LayoutJob, LayoutSection, - font::{Font, FontImpl, GlyphInfo}, + Galley, LayoutJob, LayoutSection, TextOptions, + font::{Font, FontFace, GlyphInfo}, }, }; use emath::{NumExt as _, OrderedFloat}; @@ -116,7 +117,7 @@ impl std::fmt::Display for FontFamily { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct FontData { /// The content of a `.ttf` or `.otf` file. - pub font: std::borrow::Cow<'static, [u8]>, + pub font: Cow<'static, [u8]>, /// Which font face in the file to use. /// When in doubt, use `0`. @@ -129,7 +130,7 @@ pub struct FontData { impl FontData { pub fn from_static(font: &'static [u8]) -> Self { Self { - font: std::borrow::Cow::Borrowed(font), + font: Cow::Borrowed(font), index: 0, tweak: Default::default(), } @@ -137,7 +138,7 @@ impl FontData { pub fn from_owned(font: Vec) -> Self { Self { - font: std::borrow::Cow::Owned(font), + font: Cow::Owned(font), index: 0, tweak: Default::default(), } @@ -184,6 +185,11 @@ pub struct FontTweak { /// /// Example value: `2.0`. pub y_offset: f32, + + /// Override the global font hinting setting for this specific font. + /// + /// `None` means use the global setting. + pub hinting_override: Option, } impl Default for FontTweak { @@ -192,24 +198,20 @@ impl Default for FontTweak { scale: 1.0, y_offset_factor: 0.0, y_offset: 0.0, + hinting_override: None, } } } // ---------------------------------------------------------------------------- -fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontArc { - match &data.font { - std::borrow::Cow::Borrowed(bytes) => { - ab_glyph::FontRef::try_from_slice_and_index(bytes, data.index) - .map(ab_glyph::FontArc::from) - } - std::borrow::Cow::Owned(bytes) => { - ab_glyph::FontVec::try_from_vec_and_index(bytes.clone(), data.index) - .map(ab_glyph::FontArc::from) - } +pub type Blob = Arc + Send + Sync>; + +fn blob_from_font_data(data: &FontData) -> Blob { + match data.clone().font { + Cow::Borrowed(bytes) => Arc::new(bytes) as Blob, + Cow::Owned(bytes) => Arc::new(bytes) as Blob, } - .unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}")) } /// Describes the font data and the sizes to use. @@ -438,7 +440,7 @@ pub(super) struct CachedFamily { impl CachedFamily { fn new( fonts: Vec, - fonts_by_id: &mut nohash_hasher::IntMap, + fonts_by_id: &mut nohash_hasher::IntMap, ) -> Self { if fonts.is_empty() { return Self { @@ -476,11 +478,11 @@ impl CachedFamily { pub(crate) fn glyph_info_no_cache_or_fallback( &mut self, c: char, - fonts_by_id: &mut nohash_hasher::IntMap, + fonts_by_id: &mut nohash_hasher::IntMap, ) -> Option<(FontFaceKey, GlyphInfo)> { for font_key in &self.fonts { - let font_impl = fonts_by_id.get_mut(font_key).expect("Nonexistent font ID"); - if let Some(glyph_info) = font_impl.glyph_info(c) { + let font_face = fonts_by_id.get_mut(font_key).expect("Nonexistent font ID"); + if let Some(glyph_info) = font_face.glyph_info(c) { self.glyph_info_cache.insert(c, (*font_key, glyph_info)); return Some((*font_key, glyph_info)); } @@ -508,43 +510,29 @@ pub struct Fonts { impl Fonts { /// Create a new [`Fonts`] for text layout. /// This call is expensive, so only create one [`Fonts`] and then reuse it. - /// - /// * `max_texture_side`: largest supported texture size (one side). - pub fn new( - max_texture_side: usize, - text_alpha_from_coverage: AlphaFromCoverage, - definitions: FontDefinitions, - ) -> Self { + pub fn new(options: TextOptions, definitions: FontDefinitions) -> Self { Self { - fonts: FontsImpl::new(max_texture_side, text_alpha_from_coverage, definitions), + fonts: FontsImpl::new(options, definitions), galley_cache: Default::default(), } } - /// Call at the start of each frame with the latest known - /// `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`. + /// Call at the start of each frame with the latest known [`TextOptions`]. /// /// Call after painting the previous frame, but before using [`Fonts`] for the new frame. /// - /// This function will react to changes in `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`, + /// This function will react to changes in [`TextOptions`], /// as well as notice when the font atlas is getting full, and handle that. - pub fn begin_pass( - &mut self, - max_texture_side: usize, - text_alpha_from_coverage: AlphaFromCoverage, - ) { - let max_texture_side_changed = self.fonts.max_texture_side != max_texture_side; - let text_alpha_from_coverage_changed = - self.fonts.atlas.text_alpha_from_coverage != text_alpha_from_coverage; + pub fn begin_pass(&mut self, options: TextOptions) { + let text_options_changed = self.fonts.options() != &options; let font_atlas_almost_full = self.fonts.atlas.fill_ratio() > 0.8; - let needs_recreate = - max_texture_side_changed || text_alpha_from_coverage_changed || font_atlas_almost_full; + let needs_recreate = text_options_changed || font_atlas_almost_full; if needs_recreate { let definitions = self.fonts.definitions.clone(); *self = Self { - fonts: FontsImpl::new(max_texture_side, text_alpha_from_coverage, definitions), + fonts: FontsImpl::new(options, definitions), galley_cache: Default::default(), }; } @@ -558,8 +546,8 @@ impl Fonts { } #[inline] - pub fn max_texture_side(&self) -> usize { - self.fonts.max_texture_side + pub fn options(&self) -> &TextOptions { + self.texture_atlas().options() } #[inline] @@ -628,8 +616,8 @@ pub struct FontsView<'a> { impl FontsView<'_> { #[inline] - pub fn max_texture_side(&self) -> usize { - self.fonts.max_texture_side + pub fn options(&self) -> &TextOptions { + self.fonts.options() } #[inline] @@ -671,6 +659,7 @@ impl FontsView<'_> { /// Height of one row of text in points. /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. + #[inline] pub fn row_height(&mut self, font_id: &FontId) -> f32 { self.fonts .font(&font_id.family) @@ -716,6 +705,7 @@ impl FontsView<'_> { /// Will wrap text at the given width and line break at `\n`. /// /// The implementation uses memoization so repeated calls are cheap. + #[inline] pub fn layout( &mut self, text: String, @@ -730,6 +720,7 @@ impl FontsView<'_> { /// Will line break at `\n`. /// /// The implementation uses memoization so repeated calls are cheap. + #[inline] pub fn layout_no_wrap( &mut self, text: String, @@ -743,6 +734,7 @@ impl FontsView<'_> { /// Like [`Self::layout`], made for when you want to pick a color for the text later. /// /// The implementation uses memoization so repeated calls are cheap. + #[inline] pub fn layout_delayed_color( &mut self, text: String, @@ -759,10 +751,9 @@ impl FontsView<'_> { /// /// Required in order to paint text. pub struct FontsImpl { - max_texture_side: usize, definitions: FontDefinitions, atlas: TextureAtlas, - fonts_by_id: nohash_hasher::IntMap, + fonts_by_id: nohash_hasher::IntMap, fonts_by_name: ahash::HashMap, family_cache: ahash::HashMap, } @@ -770,36 +761,36 @@ pub struct FontsImpl { impl FontsImpl { /// Create a new [`FontsImpl`] for text layout. /// This call is expensive, so only create one [`FontsImpl`] and then reuse it. - pub fn new( - max_texture_side: usize, - text_alpha_from_coverage: AlphaFromCoverage, - definitions: FontDefinitions, - ) -> Self { - let texture_width = max_texture_side.at_most(16 * 1024); + pub fn new(options: TextOptions, definitions: FontDefinitions) -> Self { + let texture_width = options.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], text_alpha_from_coverage); + let atlas = TextureAtlas::new([texture_width, initial_height], options); - let mut fonts_by_id: nohash_hasher::IntMap = Default::default(); - let mut font_impls: ahash::HashMap = Default::default(); + let mut fonts_by_id: nohash_hasher::IntMap = Default::default(); + let mut fonts_by_name: ahash::HashMap = Default::default(); for (name, font_data) in &definitions.font_data { let tweak = font_data.tweak; - let ab_glyph = ab_glyph_font_from_font_data(name, font_data); - let font_impl = FontImpl::new(name.clone(), ab_glyph, tweak); + let blob = blob_from_font_data(font_data); + let font_face = FontFace::new(options, name.clone(), blob, font_data.index, tweak) + .unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}")); let key = FontFaceKey::new(); - fonts_by_id.insert(key, font_impl); - font_impls.insert(name.clone(), key); + fonts_by_id.insert(key, font_face); + fonts_by_name.insert(name.clone(), key); } Self { - max_texture_side, definitions, atlas, fonts_by_id, - fonts_by_name: font_impls, + fonts_by_name, family_cache: Default::default(), } } + pub fn options(&self) -> &TextOptions { + self.atlas.options() + } + /// Get the right font implementation from [`FontFamily`]. pub fn font(&mut self, family: &FontFamily) -> Font<'_> { let cached_family = self.family_cache.entry(family.clone()).or_insert_with(|| { @@ -1192,12 +1183,7 @@ mod tests { #[test] fn test_split_paragraphs() { for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] { - let max_texture_side = 4096; - let mut fonts = FontsImpl::new( - max_texture_side, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); for halign in [Align::Min, Align::Center, Align::Max] { for justify in [false, true] { @@ -1255,11 +1241,7 @@ mod tests { let rounded_output_to_gui = [false, true]; for pixels_per_point in pixels_per_point { - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); for &max_width in &max_widths { for round_output_to_gui in rounded_output_to_gui { @@ -1306,7 +1288,7 @@ mod tests { #[test] fn test_fallback_glyph_width() { - let mut fonts = Fonts::new(1024, AlphaFromCoverage::default(), FontDefinitions::empty()); + let mut fonts = Fonts::new(TextOptions::default(), FontDefinitions::empty()); let mut view = fonts.with_pixels_per_point(1.0); let width = view.glyph_width(&FontId::new(12.0, FontFamily::Proportional), ' '); diff --git a/crates/epaint/src/text/mod.rs b/crates/epaint/src/text/mod.rs index e0f4a3a98..b40ba45b8 100644 --- a/crates/epaint/src/text/mod.rs +++ b/crates/epaint/src/text/mod.rs @@ -20,3 +20,31 @@ pub use { /// Suggested character to use to replace those in password text fields. pub const PASSWORD_REPLACEMENT_CHAR: char = '•'; + +/// Controls how we render text +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct TextOptions { + /// Maximum size of the font texture. + pub max_texture_side: usize, + + /// Controls how to convert glyph coverage to alpha. + pub alpha_from_coverage: crate::AlphaFromCoverage, + + /// Whether to enable font hinting + /// + /// (round some font coordinates to pixels for sharper text). + /// + /// Default is `true`. + pub font_hinting: bool, +} + +impl Default for TextOptions { + fn default() -> Self { + Self { + max_texture_side: 2048, // Small but portable + alpha_from_coverage: crate::AlphaFromCoverage::default(), + font_hinting: true, + } + } +} diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 1db56731d..2b1ab92b9 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -176,7 +176,7 @@ fn layout_section( // Optimization: only recompute `ScaledMetrics` when the concrete `FontImpl` changes. let mut current_font = FontFaceKey::INVALID; - let mut current_font_impl_metrics = ScaledMetrics::default(); + let mut current_font_face_metrics = ScaledMetrics::default(); for chr in job.text[byte_range.clone()].chars() { if job.break_on_newline && chr == '\n' { @@ -185,20 +185,20 @@ fn layout_section( paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? } else { let (font_id, glyph_info) = font.glyph_info(chr); - let mut font_impl = font.fonts_by_id.get_mut(&font_id); + let mut font_face = font.fonts_by_id.get_mut(&font_id); if current_font != font_id { current_font = font_id; - current_font_impl_metrics = font_impl + current_font_face_metrics = font_face .as_ref() - .map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size)) + .map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size)) .unwrap_or_default(); } - if let (Some(font_impl), Some(last_glyph_id), Some(glyph_id)) = - (&font_impl, last_glyph_id, glyph_info.id) + if let (Some(font_face), Some(last_glyph_id), Some(glyph_id)) = + (&font_face, last_glyph_id, glyph_info.id) { - paragraph.cursor_x_px += font_impl.pair_kerning_pixels( - ¤t_font_impl_metrics, + paragraph.cursor_x_px += font_face.pair_kerning_pixels( + ¤t_font_face_metrics, last_glyph_id, glyph_id, ); @@ -207,10 +207,10 @@ fn layout_section( paragraph.cursor_x_px += extra_letter_spacing * pixels_per_point; } - let (glyph_alloc, physical_x) = if let Some(font_impl) = font_impl.as_mut() { - font_impl.allocate_glyph( + let (glyph_alloc, physical_x) = if let Some(font_face) = font_face.as_mut() { + font_face.allocate_glyph( font.atlas, - ¤t_font_impl_metrics, + ¤t_font_face_metrics, glyph_info, chr, paragraph.cursor_x_px, @@ -224,8 +224,8 @@ fn layout_section( pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN), advance_width: glyph_alloc.advance_width_px / pixels_per_point, line_height, - font_impl_height: current_font_impl_metrics.row_height, - font_impl_ascent: current_font_impl_metrics.ascent, + font_face_height: current_font_face_metrics.row_height, + font_face_ascent: current_font_face_metrics.ascent, font_height: font_metrics.row_height, font_ascent: font_metrics.ascent, uv_rect: glyph_alloc.uv_rect, @@ -463,22 +463,22 @@ fn replace_last_glyph_with_overflow_character( let font_size = section.format.font_id.size; let (font_id, glyph_info) = font.glyph_info(overflow_character); - let mut font_impl = font.fonts_by_id.get_mut(&font_id); - let font_impl_metrics = font_impl + let mut font_face = font.fonts_by_id.get_mut(&font_id); + let font_face_metrics = font_face .as_mut() .map(|f| f.scaled_metrics(pixels_per_point, font_size)) .unwrap_or_default(); let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() { // Kern the overflow character properly - let pair_kerning = font_impl + let pair_kerning = font_face .as_mut() - .map(|font_impl| { + .map(|font_face| { if let (Some(prev_glyph_id), Some(overflow_glyph_id)) = ( - font_impl.glyph_info(prev_glyph.chr).and_then(|g| g.id), - font_impl.glyph_info(overflow_character).and_then(|g| g.id), + font_face.glyph_info(prev_glyph.chr).and_then(|g| g.id), + font_face.glyph_info(overflow_character).and_then(|g| g.id), ) { - font_impl.pair_kerning(&font_impl_metrics, prev_glyph_id, overflow_glyph_id) + font_face.pair_kerning(&font_face_metrics, prev_glyph_id, overflow_glyph_id) } else { 0.0 } @@ -490,10 +490,10 @@ fn replace_last_glyph_with_overflow_character( 0.0 // TODO(emilk): heed paragraph leading_space 😬 }; - let replacement_glyph_width = font_impl + let replacement_glyph_width = font_face .as_mut() .and_then(|f| f.glyph_info(overflow_character)) - .map(|i| i.advance_width_unscaled.0 * font_impl_metrics.px_scale_factor) + .map(|i| i.advance_width_unscaled.0 * font_face_metrics.px_scale_factor) .unwrap_or_default(); // Check if we're within width budget: @@ -502,12 +502,12 @@ fn replace_last_glyph_with_overflow_character( { // we are done - let (replacement_glyph_alloc, physical_x) = font_impl + let (replacement_glyph_alloc, physical_x) = font_face .as_mut() .map(|f| { f.allocate_glyph( font.atlas, - &font_impl_metrics, + &font_face_metrics, glyph_info, overflow_character, overflow_glyph_x * pixels_per_point, @@ -526,8 +526,8 @@ fn replace_last_glyph_with_overflow_character( pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN), advance_width: replacement_glyph_alloc.advance_width_px / pixels_per_point, line_height, - font_impl_height: font_impl_metrics.row_height, - font_impl_ascent: font_impl_metrics.ascent, + font_face_height: font_face_metrics.row_height, + font_face_ascent: font_face_metrics.ascent, font_height: font_metrics.row_height, font_ascent: font_metrics.ascent, uv_rect: replacement_glyph_alloc.uv_rect, @@ -668,14 +668,14 @@ fn galley_from_rows( for glyph in &mut row.glyphs { let format = &job.sections[glyph.section_index as usize].format; - glyph.pos.y = glyph.font_impl_ascent + glyph.pos.y = glyph.font_face_ascent // Apply valign to the different in height of the entire row, and the height of this `Font`: + format.valign.to_factor() * (max_row_height - glyph.line_height) // When mixing different `FontImpl` (e.g. latin and emojis), // we always center the difference: - + 0.5 * (glyph.font_height - glyph.font_impl_height); + + 0.5 * (glyph.font_height - glyph.font_face_height); glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y); } @@ -1050,18 +1050,13 @@ impl RowBreakCandidates { #[cfg(test)] mod tests { - use crate::AlphaFromCoverage; use super::{super::*, *}; #[test] fn test_zero_max_width() { let pixels_per_point = 1.0; - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::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, pixels_per_point, layout_job.into()); @@ -1074,11 +1069,7 @@ mod tests { let pixels_per_point = 1.0; - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); let text_format = TextFormat { font_id: FontId::monospace(12.0), ..Default::default() @@ -1124,11 +1115,7 @@ mod tests { #[test] fn test_cjk() { let pixels_per_point = 1.0; - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); let mut layout_job = LayoutJob::single_section( "日本語とEnglishの混在した文章".into(), TextFormat::default(), @@ -1144,11 +1131,7 @@ mod tests { #[test] fn test_pre_cjk() { let pixels_per_point = 1.0; - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); let mut layout_job = LayoutJob::single_section( "日本語とEnglishの混在した文章".into(), TextFormat::default(), @@ -1164,11 +1147,7 @@ mod tests { #[test] fn test_truncate_width() { let pixels_per_point = 1.0; - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); let mut layout_job = LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default()); layout_job.wrap.max_width = f32::INFINITY; @@ -1188,11 +1167,7 @@ mod tests { #[test] fn test_empty_row() { let pixels_per_point = 1.0; - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); let font_id = FontId::default(); let font_height = fonts @@ -1225,11 +1200,7 @@ mod tests { #[test] fn test_end_with_newline() { let pixels_per_point = 1.0; - let mut fonts = FontsImpl::new( - 1024, - AlphaFromCoverage::default(), - FontDefinitions::default(), - ); + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); let font_id = FontId::default(); let font_height = fonts diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index f3963394a..83a98ab05 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -686,11 +686,11 @@ pub struct Glyph { /// The row/line height of this font. pub font_height: f32, - /// The ascent of the sub-font within the font (`FontImpl`). - pub font_impl_ascent: f32, + /// The ascent of the sub-font within the font (`FontFace`). + pub font_face_ascent: f32, - /// The row/line height of the sub-font within the font (`FontImpl`). - pub font_impl_height: f32, + /// The row/line height of the sub-font within the font (`FontFace`). + pub font_face_height: f32, /// Position and size of the glyph in the font texture, in texels. pub uv_rect: UvRect, diff --git a/crates/epaint/src/texture_atlas.rs b/crates/epaint/src/texture_atlas.rs index 6488d9079..9a77c142a 100644 --- a/crates/epaint/src/texture_atlas.rs +++ b/crates/epaint/src/texture_atlas.rs @@ -1,7 +1,7 @@ use ecolor::Color32; use emath::{Rect, remap_clamp}; -use crate::{AlphaFromCoverage, ColorImage, ImageDelta}; +use crate::{ColorImage, ImageDelta, TextOptions}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct Rectu { @@ -75,11 +75,11 @@ pub struct TextureAtlas { discs: Vec, /// Controls how to convert glyph coverage to alpha. - pub(crate) text_alpha_from_coverage: AlphaFromCoverage, + options: TextOptions, } impl TextureAtlas { - pub fn new(size: [usize; 2], text_alpha_from_coverage: AlphaFromCoverage) -> Self { + pub fn new(size: [usize; 2], options: TextOptions) -> Self { assert!(size[0] >= 1024, "Tiny texture atlas"); let mut atlas = Self { image: ColorImage::filled(size, Color32::TRANSPARENT), @@ -88,7 +88,7 @@ impl TextureAtlas { row_height: 0, overflowed: false, discs: vec![], // will be filled in below - text_alpha_from_coverage, + options, }; // Make the top left pixel fully white for `WHITE_UV`, i.e. painting something with solid color: @@ -121,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)] = - text_alpha_from_coverage.color_from_coverage(coverage); + options.alpha_from_coverage.color_from_coverage(coverage); } } atlas.discs.push(PrerasterizedDisc { @@ -138,6 +138,10 @@ impl TextureAtlas { atlas } + pub fn options(&self) -> &TextOptions { + &self.options + } + pub fn size(&self) -> [usize; 2] { self.image.size } diff --git a/deny.toml b/deny.toml index c7206ff26..1b3bd8b12 100644 --- a/deny.toml +++ b/deny.toml @@ -51,6 +51,7 @@ skip = [ { name = "core-foundation" }, # version conflict between winit and wgpu ecosystems { name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems { name = "getrandom" }, # ring / rustls (and thus ehttp) still depend on getrandom 0.2 + { name = "kurbo" }, # Old version because of resvg { name = "quick-xml" }, # old version via wayland-scanner { name = "redox_syscall" }, # old version via winit { name = "rustc-hash" }, # Small enough diff --git a/tests/egui_tests/tests/snapshots/button_shortcut.png b/tests/egui_tests/tests/snapshots/button_shortcut.png index 7f39196b8..de7d64b4d 100644 --- a/tests/egui_tests/tests/snapshots/button_shortcut.png +++ b/tests/egui_tests/tests/snapshots/button_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5befd84158b582c79a968f36e43c7017187b364824eb4470b048d133e62f9360 -size 1600 +oid sha256:cbf68b6934dae0868bc9cf0891baf5acf110284d297cfa348e756237fca64a28 +size 1564 diff --git a/tests/egui_tests/tests/snapshots/grow_all.png b/tests/egui_tests/tests/snapshots/grow_all.png index 373889987..3e5208fe0 100644 --- a/tests/egui_tests/tests/snapshots/grow_all.png +++ b/tests/egui_tests/tests/snapshots/grow_all.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:469a1b1faa71da472c07bcc7103933db9964440a4c49dc596220f070e9b483f5 -size 14375 +oid sha256:2b91ae9e626d885b049d80dc9421275e147f4a3501c21ff4740b0f59d9c2998b +size 13930 diff --git a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png index 038ce78db..672418f84 100644 --- a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png +++ b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c83e094b1f0dede0195cc77f5caa3b7d13249364612b03c02f0ef5f2af5e28ad -size 12512 +oid sha256:3a5669c2c354c6ea42d8eaeb2eb39b65130a87807cbba8382dcc24d59790e794 +size 12181 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_image.png b/tests/egui_tests/tests/snapshots/layout/atoms_image.png index bf98d3d2d..200ea6476 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_image.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:070638cd5c174200161498123a612e4a58d58517b539e91e289d1e3dd38670bb -size 388531 +oid sha256:2e236f71e26e1a96acf9cd135b5db3a9cb0df374b87c3e283023dd14df193411 +size 369870 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png index ae17dff6a..3c982b37e 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac8885d6e5325b5f1f0ccb11f185df5c4937b66c80159aa8cf53930f5c8045e2 -size 395599 +oid sha256:096ec8246969f85cfa0cb8d58731be9aaf82b7dac70dc064ec999b1eed25e1ef +size 368552 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png index 97c181c70..664e23a9b 100644 --- a/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png +++ b/tests/egui_tests/tests/snapshots/layout/atoms_multi_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05b891dd999050150f4cd4226a1c68673b352f04d08d405751d66c698c7711c5 -size 309736 +oid sha256:0813583ca9658b5f27f3585e59f829b71c86061619d7f61a16cc2ccf0906a322 +size 291213 diff --git a/tests/egui_tests/tests/snapshots/layout/button.png b/tests/egui_tests/tests/snapshots/layout/button.png index e53754f51..21449927d 100644 --- a/tests/egui_tests/tests/snapshots/layout/button.png +++ b/tests/egui_tests/tests/snapshots/layout/button.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e8a7835e3fd22aafc22b6c536051367e2c27c8607fbe5d8b6b6cf6d0ba3d54f -size 332771 +oid sha256:e822c2324268d6e6168f9510aa1caec94df38dd0c163afcdecad11f2b1740936 +size 314449 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png index ece6568e9..4ee6cffa2 100644 --- a/tests/egui_tests/tests/snapshots/layout/button_image.png +++ b/tests/egui_tests/tests/snapshots/layout/button_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59cbb416865f8bede12b449d8e65baddb6949df017face2782a3492de7a058e3 -size 355276 +oid sha256:682dd89e15ee289a87a592c93ac2b9ec3172cd4fedcc02072c0516a9ae9ecd64 +size 335687 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 13f860b83..5b74267e1 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:cad07a16b6f0eb8c7647c7d0500ea99c68dddf2eef892b4df48aa20f581e8a85 -size 438837 +oid sha256:e2d22c9e7fd701be1dc1581635cdfa2829e02db9c6f66bf54eac106ebd7344a3 +size 421041 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox.png b/tests/egui_tests/tests/snapshots/layout/checkbox.png index 18ebbdb7c..c1e993885 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4747efdf758e7e8e2d7f3954d9595dfd45d3b4b86923b8ff39c8a96002bb4825 -size 408726 +oid sha256:ee91ad31d625930c55ae4ac41011f2018ef11ba20cefe5686b7338671fd6c32e +size 389522 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png index 127aa7f30..4b972d966 100644 --- a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a3ca4b3a47ff516f9b05799cdf5f92845ae1e058728d635986cc61b7317f110 -size 437102 +oid sha256:bcb5e0ec12a4bb7aba8ca8b53622fb2c204411ec66d7745bdb06e01bd1ffc731 +size 417596 diff --git a/tests/egui_tests/tests/snapshots/layout/drag_value.png b/tests/egui_tests/tests/snapshots/layout/drag_value.png index 471d3b867..44bf0bfcb 100644 --- a/tests/egui_tests/tests/snapshots/layout/drag_value.png +++ b/tests/egui_tests/tests/snapshots/layout/drag_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c168f197bec3bc780553db0a47f464f8d76afc606e28e2545ecd91f174abe551 -size 249781 +oid sha256:b2cd4d27748e193d4f46ad7a5be6ff411ad3152b4fd546c0dc98dd3bb5333d93 +size 236090 diff --git a/tests/egui_tests/tests/snapshots/layout/radio.png b/tests/egui_tests/tests/snapshots/layout/radio.png index 07e20176d..d3930768e 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio.png +++ b/tests/egui_tests/tests/snapshots/layout/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39f1985a3a975b1b9a179d3b1bd5832e0b4c30d10232babf2e4736f55b43989f -size 350051 +oid sha256:c15ece11f5c45d4bb89096a4d7146032e109fd9a099f2f37641e2676f7c3e184 +size 327971 diff --git a/tests/egui_tests/tests/snapshots/layout/radio_checked.png b/tests/egui_tests/tests/snapshots/layout/radio_checked.png index 2163011e6..c2d12eb98 100644 --- a/tests/egui_tests/tests/snapshots/layout/radio_checked.png +++ b/tests/egui_tests/tests/snapshots/layout/radio_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18a38fe66d5ac8f2cf5c109f1cd9c29951e5bf8428b6bf0d4587dfc8f8c5c890 -size 370205 +oid sha256:5942409a24177f84e067bcb488d8f976a0a6ad432f9f8603be2fdd4269d79efa +size 347946 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value.png b/tests/egui_tests/tests/snapshots/layout/selectable_value.png index 2ee7f7d0e..e2ea0c1f4 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfc900ea84b408564652df487e705311b164d9bd3ff5631c3cebb83b06497a7b -size 410131 +oid sha256:2c082417d4f65be1efc6c040d2acaf02d899ceaa547ba86f530e1d2e94f4e385 +size 389160 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png index 7e3dd6319..2a2553a30 100644 --- a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9d08ce85c9210a7d9046480ab208040e5ba399c40acaecf5cb43f807534bce9 -size 423523 +oid sha256:7edb1db196e1a6c740503d976f5f8e4dd9d3d4dd07e8391ce77f01f411cae315 +size 402030 diff --git a/tests/egui_tests/tests/snapshots/layout/slider.png b/tests/egui_tests/tests/snapshots/layout/slider.png index b8cc394c4..b7d9edcd5 100644 --- a/tests/egui_tests/tests/snapshots/layout/slider.png +++ b/tests/egui_tests/tests/snapshots/layout/slider.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b402195e54bdbd09985e4b30a025083298f29ab747b809fbb864c8dfef0975eb -size 289714 +oid sha256:e8bd1515d5c4045f4cd1b5d0c4f48469bd7e3ce738a95f741e9254e02ea28185 +size 276004 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit.png b/tests/egui_tests/tests/snapshots/layout/text_edit.png index 8b53899dd..379b33806 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ed6af3a92790e07b71e71637b5d6bb45d55a7d26738d438714aca64d7f4534c -size 245394 +oid sha256:61dde59ee92a1c22aba7fd8decf62d88d1ed81c10cd969ce65c451185f7ca58b +size 221618 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_clip.png b/tests/egui_tests/tests/snapshots/layout/text_edit_clip.png index 0c4327b58..ccc29355f 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit_clip.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f107d95fee9a5fb5fbfd2422452e1820738a84c81774587dbfa8153e91e4c73 -size 414552 +oid sha256:c2a7ad1a4568f0ed7f203453697982603fad8b7e9852b4193216ebff1624671d +size 384210 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png b/tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png index ecc6efa8b..9ac2cefee 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_no_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c1aebada9349f8cb4046469b0a6f9796a21f88b6724bd85cd832a40b8007409 -size 540527 +oid sha256:54a2f4004a71af18ffc42bba723a69855af4913ddedd8185688a59f9967e5a13 +size 509495 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png b/tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png index 780fec82f..e74e0f928 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_placeholder_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:685de2e33ff26aafa87426bcda18bb9963c2deb2a811cd0aae4450af0e245a06 -size 390735 +oid sha256:2ab3a86f34c5cce033903cd67c1070dcc509e385e62e05358e1329968bfb1e95 +size 363693 diff --git a/tests/egui_tests/tests/snapshots/max_width.png b/tests/egui_tests/tests/snapshots/max_width.png index a10284911..6534961a8 100644 --- a/tests/egui_tests/tests/snapshots/max_width.png +++ b/tests/egui_tests/tests/snapshots/max_width.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e2dc33b2d4caddac86dd8649d09ff3d57187a0152240f824a6aa170a35dd719 -size 8570 +oid sha256:ea5546e2e72aa5181edfe260cf5b506a30fea8c3db049c080bafc303223ba95f +size 8367 diff --git a/tests/egui_tests/tests/snapshots/max_width_and_grow.png b/tests/egui_tests/tests/snapshots/max_width_and_grow.png index b0d5c134f..54dddf7e8 100644 --- a/tests/egui_tests/tests/snapshots/max_width_and_grow.png +++ b/tests/egui_tests/tests/snapshots/max_width_and_grow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36ab2c05eade94dcfe524651a0954c122aea754976af94a73a7efd950055c9eb -size 8574 +oid sha256:7d65a6c7e855a5476369422577d02f5e2a96814b100d7385f172fa9506189849 +size 8369 diff --git a/tests/egui_tests/tests/snapshots/shrink_first_text.png b/tests/egui_tests/tests/snapshots/shrink_first_text.png index 7bae217bb..81680a36a 100644 --- a/tests/egui_tests/tests/snapshots/shrink_first_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_first_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2ba53264abcaa2ee79858ddbd475ed35aa28d146b846cbc080c26911d373ea6 -size 11881 +oid sha256:77ff29a1441d11f3b13ddaf5f6dd5f2c5781bc418887e1c2eabe00679958cba6 +size 11448 diff --git a/tests/egui_tests/tests/snapshots/shrink_last_text.png b/tests/egui_tests/tests/snapshots/shrink_last_text.png index 821490e52..6f7b28c16 100644 --- a/tests/egui_tests/tests/snapshots/shrink_last_text.png +++ b/tests/egui_tests/tests/snapshots/shrink_last_text.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:082d80ae144338dce45169c02038b6dc5b75d7b6d93c2a8213ddbb2a8784cc92 -size 12435 +oid sha256:23923d37e4dd848b043c7118e651ddade82c0df180652d8f0dcb829b1b6245d6 +size 12009 diff --git a/tests/egui_tests/tests/snapshots/sides/default_long.png b/tests/egui_tests/tests/snapshots/sides/default_long.png index 452ed723a..ae862c32d 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_long.png +++ b/tests/egui_tests/tests/snapshots/sides/default_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e455b08f4674a9326682771f12456a71cc22dfd733ee965fdbbb5582cba0380 -size 8176 +oid sha256:9c970aab8c09558b806c81f57fc1d695992cb9f6e735a3fb2be75997c106a141 +size 8214 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 index ce5996d2f..842f41171 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png +++ b/tests/egui_tests/tests/snapshots/sides/default_long_fit_contents.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc -size 8963 +oid sha256:3afbf9e4d598907f088d3f09b1cf2b70c682062f1f4b98aa98b997121f763040 +size 8802 diff --git a/tests/egui_tests/tests/snapshots/sides/default_short.png b/tests/egui_tests/tests/snapshots/sides/default_short.png index 2d7ccae52..f19a5afbf 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_short.png +++ b/tests/egui_tests/tests/snapshots/sides/default_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 -size 1700 +oid sha256:da2b06feee78b808eab7ec4286b5050244b18b056f08dc49c417da8ff08bed0c +size 1637 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 index 9c5635e19..099d55cb5 100644 --- a/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png +++ b/tests/egui_tests/tests/snapshots/sides/default_short_fit_contents.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 -size 1305 +oid sha256:a0a38c58ae7a30256e9491bfeb1155f2df6bba2a656ed9611fa945cbe2ebdc43 +size 1242 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png index cc17a1c48..ebf7424c3 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d36fb3c42b7f74e0b6dd8e73b9f8455e7fac035f5979cb93769e6a3f5453fdbc -size 7239 +oid sha256:46bca727290bb0fc5a9a28137385e7ee4821390d1594704ce5e0ea089f28dacf +size 7079 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 index ce5996d2f..842f41171 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc -size 8963 +oid sha256:3afbf9e4d598907f088d3f09b1cf2b70c682062f1f4b98aa98b997121f763040 +size 8802 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png index 2d7ccae52..f19a5afbf 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_left_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 -size 1700 +oid sha256:da2b06feee78b808eab7ec4286b5050244b18b056f08dc49c417da8ff08bed0c +size 1637 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 index 9c5635e19..099d55cb5 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 -size 1305 +oid sha256:a0a38c58ae7a30256e9491bfeb1155f2df6bba2a656ed9611fa945cbe2ebdc43 +size 1242 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png index 03ca0a66e..d1cfeb533 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89d9445d7aaff34ace5322af6efc308f261cf5becfdd11aa7fec016236ecfa84 -size 7064 +oid sha256:841f69878a4b9331f8ab4730d212384a82a9de14b9fba0d6964cd3010900132a +size 6939 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 index ce5996d2f..842f41171 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc -size 8963 +oid sha256:3afbf9e4d598907f088d3f09b1cf2b70c682062f1f4b98aa98b997121f763040 +size 8802 diff --git a/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png index 2d7ccae52..f19a5afbf 100644 --- a/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png +++ b/tests/egui_tests/tests/snapshots/sides/shrink_right_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 -size 1700 +oid sha256:da2b06feee78b808eab7ec4286b5050244b18b056f08dc49c417da8ff08bed0c +size 1637 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 index 9c5635e19..099d55cb5 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 -size 1305 +oid sha256:a0a38c58ae7a30256e9491bfeb1155f2df6bba2a656ed9611fa945cbe2ebdc43 +size 1242 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png index 48332d65b..be67eaf7a 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7487a1b77fd2db2493bca7d42128aad7a0049962599c8b5add2b7b25376a37d -size 9381 +oid sha256:602bc370e3929995c9b17415b513b412e0e12433f2c2b9120c58ea63c747ed79 +size 9184 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 index ce5996d2f..842f41171 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc -size 8963 +oid sha256:3afbf9e4d598907f088d3f09b1cf2b70c682062f1f4b98aa98b997121f763040 +size 8802 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png index 2d7ccae52..f19a5afbf 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_left_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 -size 1700 +oid sha256:da2b06feee78b808eab7ec4286b5050244b18b056f08dc49c417da8ff08bed0c +size 1637 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 index 9c5635e19..099d55cb5 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 -size 1305 +oid sha256:a0a38c58ae7a30256e9491bfeb1155f2df6bba2a656ed9611fa945cbe2ebdc43 +size 1242 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png index fa65ff9db..cb31d61e1 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_long.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f614fb9b2aba8cc5a6997a519ca92083fb4378a36f335570d9e870159267f40 -size 9495 +oid sha256:b9165daef8acd038a1527192ded0b7cd5d03f235be737308ade467df33b6c8a0 +size 9192 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 index ce5996d2f..842f41171 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36600579bd2b5a9f255c9d843ccb76e74c622bc7d206962f52e3519e21dc2cfc -size 8963 +oid sha256:3afbf9e4d598907f088d3f09b1cf2b70c682062f1f4b98aa98b997121f763040 +size 8802 diff --git a/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png index 2d7ccae52..f19a5afbf 100644 --- a/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png +++ b/tests/egui_tests/tests/snapshots/sides/wrap_right_short.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ad4ffb19388aeafd11891f487953525c335b0d10bceb455274df532582733c8 -size 1700 +oid sha256:da2b06feee78b808eab7ec4286b5050244b18b056f08dc49c417da8ff08bed0c +size 1637 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 index 9c5635e19..099d55cb5 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2985caf40b9bacf9f18c84240181f84bea04cc413a009d5ca68c8d544ffa35 -size 1305 +oid sha256:a0a38c58ae7a30256e9491bfeb1155f2df6bba2a656ed9611fa945cbe2ebdc43 +size 1242 diff --git a/tests/egui_tests/tests/snapshots/size_max_size.png b/tests/egui_tests/tests/snapshots/size_max_size.png index 9c5ac8be3..12b526287 100644 --- a/tests/egui_tests/tests/snapshots/size_max_size.png +++ b/tests/egui_tests/tests/snapshots/size_max_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5230bd00a43990b30e523528b609e04916dab3cf87a59d56e39dcd5926f47aa5 -size 8838 +oid sha256:f2d9b0884adb89f598dd0c7eb421c0c8e8bcdaa1cbca02f4646c777711a005c2 +size 8655 diff --git a/tests/egui_tests/tests/snapshots/text_edit_rtl_0.png b/tests/egui_tests/tests/snapshots/text_edit_rtl_0.png index d93540222..af1b2dfb8 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_rtl_0.png +++ b/tests/egui_tests/tests/snapshots/text_edit_rtl_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:336581facb1ec989a43291ed76bd8ddb552c46137a75601f466e6dc4dae77278 -size 2395 +oid sha256:1d1102bc84e5ea0b021c6674ca243e51f011cd9212991a245addf0459e045293 +size 2347 diff --git a/tests/egui_tests/tests/snapshots/text_edit_rtl_1.png b/tests/egui_tests/tests/snapshots/text_edit_rtl_1.png index 2ae957da9..ad94f8834 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_rtl_1.png +++ b/tests/egui_tests/tests/snapshots/text_edit_rtl_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b0684c53a20eaa90a9dccef8ac3eaa2a6eede7c770e7bbbba6d995f43584d99 -size 2353 +oid sha256:28b76c813a9eb7d4b49ffdc25fa63e208396489bd5547602b9df1eeb125b3b4a +size 2305 diff --git a/tests/egui_tests/tests/snapshots/text_edit_rtl_2.png b/tests/egui_tests/tests/snapshots/text_edit_rtl_2.png index b9235740d..a11a1d1b6 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_rtl_2.png +++ b/tests/egui_tests/tests/snapshots/text_edit_rtl_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38f325f2e741f18f897502c176f9a7efe276e9adab41a144511121dd8b8a3073 -size 3079 +oid sha256:4fdb8db17e3ec526c698812b9912555d1fa3837ba601fd9b39b6c7e9d451a070 +size 3007 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png index 4204dd1d3..0e2ff4cb0 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:6d0c3773bc3698fbd1bd1eb1aa1ed45938d5cb94696bfcec56e4e7e865871baf -size 11143 +oid sha256:863c60a3246d123b958f0ef8245999da23a9e4fadc942282a4231212b26246dd +size 10879 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png index 5d1e74292..ee24faad9 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:9764ab5549e0775380b1db3c9a9a1d47c6520bcd5b8781f922e97e3524c362aa -size 12133 +oid sha256:a67a3272a3816120f0dc8857086f7c96352366823f6cebc85059abeced8c0cc2 +size 11972 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 b2f5646d3..e125622bc 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:4d0c7d4b161f7a1f9cadb3e285edcd08588b9e47e10c5579183c824ae4e7be1b -size 15170 +oid sha256:1aa2bfe30e56a7f86145007989fafbc13271cc8268c876c51826d92683125b1a +size 14763 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 3f20c4379..b577739cd 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:65359fcb0f01627876e697684b185c60812dd1591b0f42174673712939e2f193 -size 14852 +oid sha256:f3379de9b9c49f6d930203b1bfb1b64951ec78bd712d12dd31ca8a01f5e6b69b +size 14370 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png index 2145ceee7..5c170014f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68347d7eb452a6f30fa93778f9ebd17f20c1425426472d3ebe4c8b55fc0ba8ea -size 13774 +oid sha256:9f1a6442ac27ddb872d9236f6e9041e1552d73926d20ef43a6ad9f2be911cf19 +size 13500 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index ec012113b..d60d26c74 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:2c323b3b530be2c4ff195e369e86df49ef28de0696fb33a74361d9dbd95e37ae -size 14889 +oid sha256:6a475763dd3c18e2d027016906e02e5127515bcf89085eb0621022b980278424 +size 14599 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png index 05f63136b..de70c841c 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:8a48d2014ed6295d61f3200389315662b89e7efba27a93fded255cce7bd21e05 -size 8675 +oid sha256:5ca946ae1875730db15a7e525d2edfab4b55d9a07ad72998c565ce0c7c9bea90 +size 8400 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png index 298841d6a..d36c5759f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdaeee74db8c9527e6656b4a3026ed18cb58c4761f1155768a456d6d58dc79e2 -size 12549 +oid sha256:8556c997810eeafc522710365914bca9aefec1362860086e69b2182820444b20 +size 12192 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index 02590ce3d..ba382e20f 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:3dfbfd35264e4d35a594c72ef0fb9575b090301e112a98228d3070fa85aa4e42 -size 13240 +oid sha256:5a44028c96d97b68cb2ab44d9d52ec7a8c353d8d11022967c345b2536dc4e5c7 +size 12890 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png index 85cb2a451..2a27d8941 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbaa88e2769bd9dbffa9b3ced36585c00b4ad6ca91ae61a6becc63a495a812fc -size 14116 +oid sha256:7d4aa6f3a30fa438667b7d63d3b25bb0d478bc1284f87f002f6727d8dfd5a33e +size 13923 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 8f0cfb4a9..31d5bc95f 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:b1bac7bec0c22e9530ef2428c4233be7a1c3554c653b6344a2d7b981c5455920 -size 14142 +oid sha256:0e5a20f72effd38e26dfe8ffd9e1d484bf1000340191eb7ef6188a082f4ee6f1 +size 13929 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index fd9b15b73..61418cede 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:3667467ff1cf2ce210ec1e1555b40bba827008c5ee40d25ccaf082d2718c6d77 -size 10144 +oid sha256:cc69a14376d18201885502575595bfe61c06a6917074892d72aa9189e57d327c +size 9965 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png index 649a05fc4..177043578 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:d06b03948190e2d6408c339b97ec3f3e2104ffc7da61f5935b7df8bb89c9d7aa -size 8813 +oid sha256:3b322265006cd8e5ef6dfddafb38c8a47714d084402bd2fbc9bc80b4ff10e8e7 +size 8214 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png index 70c4bfe8f..049341e46 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2be8ebcc7d8cc7b3824ae27c57969c0d1bc2d5affb8f3f9df687fb3d1860280 -size 11567 +oid sha256:65cf9db0c073eae5a484058bd26f41d89da949bdca3144ec73aa21878e248923 +size 11011 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png index a5bda4b8f..8864dca70 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:934263e4413e48ea3abf8b53e213f3a61459b697b30cf05436e2d2e6a3d48e3c -size 22356 +oid sha256:188be46ed22526b36620539c802bf7b4a116b574eb790d1c3ff18058fe37b807 +size 21534 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png index e49bb4414..7190703ba 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb3230e609246415501d89984bb59ee1dad1241b8054009e7a5108efe3965904 -size 10880 +oid sha256:afeb3a0de414de4e5ab00ecb9eb7e3e69ed0dc85b5e419a796c3e7159d6da3de +size 10360 From 6277a310b93f2f07834e920baabe43409334c973 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 7 Dec 2025 08:33:51 +0000 Subject: [PATCH 329/388] Disable the Skrifa traversal feature (#7758) - Followup to https://github.com/emilk/egui/pull/7694 - Disables the `traversal` feature of `skrifa` which is not needed except internally by the fontations project - Should save a little compile time, and possibly some binary size. Signed-off-by: Nico Burns --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b1822d58a..5650152cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,7 @@ ron = "0.11.0" self_cell = "1.2.1" serde = { version = "1.0.228", features = ["derive"] } similar-asserts = "1.7.0" -skrifa = "0.37.0" +skrifa = { version = "0.37.0", default-features = false, features = ["std", "autohint_shaping"] } smallvec = "1.15.1" smithay-clipboard = "0.7.2" static_assertions = "1.1.0" From 2115ca941be9ca52e70468ec7638d62fd1da6adf Mon Sep 17 00:00:00 2001 From: switch Date: Sun, 7 Dec 2025 23:34:26 +0100 Subject: [PATCH 330/388] egui-wgpu: attach stencil buffer (#7702) --- crates/eframe/src/web/web_painter_wgpu.rs | 27 +++++++++++++++------ crates/egui-wgpu/src/winit.rs | 29 +++++++++++++++++------ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index efecd12ee..387366e5a 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -243,13 +243,26 @@ impl WebPainter for WebPainterWgpu { depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { wgpu::RenderPassDepthStencilAttachment { view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - // It is very unlikely that the depth buffer is needed after egui finished rendering - // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) - store: wgpu::StoreOp::Discard, - }), - stencil_ops: None, + depth_ops: self + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_depth_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, + }), + stencil_ops: self + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_stencil_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0), + store: wgpu::StoreOp::Discard, + }), } }), label: Some("egui_render"), diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 3a286cc9e..a35466493 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -526,13 +526,28 @@ impl Painter { depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| { wgpu::RenderPassDepthStencilAttachment { view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - // It is very unlikely that the depth buffer is needed after egui finished rendering - // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) - store: wgpu::StoreOp::Discard, - }), - stencil_ops: None, + depth_ops: self + .options + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_depth_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, + }), + stencil_ops: self + .options + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_stencil_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0), + store: wgpu::StoreOp::Discard, + }), } }), timestamp_writes: None, From a0bb4cfef82dd9b50f990f607b7c7c4f28eb8589 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 11 Dec 2025 15:34:47 +0100 Subject: [PATCH 331/388] Release 0.33.3: update cargo version and changelog --- CHANGELOG.md | 5 ++++ Cargo.lock | 32 ++++++++++++------------ Cargo.toml | 26 +++++++++---------- crates/ecolor/CHANGELOG.md | 4 +++ crates/eframe/CHANGELOG.md | 4 +++ crates/egui-wgpu/CHANGELOG.md | 4 +++ crates/egui-winit/CHANGELOG.md | 4 +++ crates/egui_extras/CHANGELOG.md | 4 +++ crates/egui_glow/CHANGELOG.md | 4 +++ crates/egui_kittest/CHANGELOG.md | 5 ++++ crates/emath/CHANGELOG.md | 4 +++ crates/epaint/CHANGELOG.md | 4 +++ crates/epaint_default_fonts/CHANGELOG.md | 4 +++ 13 files changed, 75 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 856fe09da..12010e287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ 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.33.3 - 2025-12-11 +* Treat `.` as a word-splitter in text navigation [#7741](https://github.com/emilk/egui/pull/7741) by [@emilk](https://github.com/emilk) +* Change text color of selected text [#7691](https://github.com/emilk/egui/pull/7691) by [@emilk](https://github.com/emilk) + + ## 0.33.2 - 2025-11-13 ### ⭐ Added * Add `Plugin::on_widget_under_pointer` to support widget inspector [#7652](https://github.com/emilk/egui/pull/7652) by [@juancampa](https://github.com/juancampa) diff --git a/Cargo.lock b/Cargo.lock index f75e31381..a99c0d281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1257,7 +1257,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.33.2" +version = "0.33.3" dependencies = [ "bytemuck", "cint", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.33.2" +version = "0.33.3" dependencies = [ "ahash", "bytemuck", @@ -1308,7 +1308,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.33.2" +version = "0.33.3" dependencies = [ "accesskit", "ahash", @@ -1328,7 +1328,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.33.2" +version = "0.33.3" dependencies = [ "ahash", "bytemuck", @@ -1346,7 +1346,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.33.2" +version = "0.33.3" dependencies = [ "accesskit_winit", "arboard", @@ -1369,7 +1369,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.33.2" +version = "0.33.3" dependencies = [ "accesskit", "accesskit_consumer", @@ -1399,7 +1399,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.33.2" +version = "0.33.3" dependencies = [ "chrono", "criterion", @@ -1416,7 +1416,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.33.2" +version = "0.33.3" dependencies = [ "ahash", "chrono", @@ -1435,7 +1435,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.33.2" +version = "0.33.3" dependencies = [ "bytemuck", "document-features", @@ -1454,7 +1454,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.33.2" +version = "0.33.3" dependencies = [ "dify", "document-features", @@ -1474,7 +1474,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.33.2" +version = "0.33.3" dependencies = [ "egui", "egui_extras", @@ -1504,7 +1504,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.33.2" +version = "0.33.3" dependencies = [ "bytemuck", "document-features", @@ -1602,7 +1602,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.33.2" +version = "0.33.3" dependencies = [ "ahash", "bytemuck", @@ -1626,7 +1626,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.33.2" +version = "0.33.3" [[package]] name = "equivalent" @@ -3495,7 +3495,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.33.2" +version = "0.33.3" dependencies = [ "eframe", "env_logger", @@ -5894,7 +5894,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.33.2" +version = "0.33.3" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index 5650152cc..b291cb36c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.88" -version = "0.33.2" +version = "0.33.3" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.33.2", path = "crates/emath", default-features = false } -ecolor = { version = "0.33.2", path = "crates/ecolor", default-features = false } -epaint = { version = "0.33.2", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.33.2", path = "crates/epaint_default_fonts" } -egui = { version = "0.33.2", path = "crates/egui", default-features = false } -egui-winit = { version = "0.33.2", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.33.2", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.33.2", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.33.2", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.33.2", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.33.2", path = "crates/eframe", default-features = false } +emath = { version = "0.33.3", path = "crates/emath", default-features = false } +ecolor = { version = "0.33.3", path = "crates/ecolor", default-features = false } +epaint = { version = "0.33.3", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.33.3", path = "crates/epaint_default_fonts" } +egui = { version = "0.33.3", path = "crates/egui", default-features = false } +egui-winit = { version = "0.33.3", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.33.3", path = "crates/eframe", default-features = false } accesskit = "0.21.1" accesskit_consumer = "0.30.1" diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 4dee81296..a4fe1f3de 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 11f1c8fc9..74a705251 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 * Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman) * Make sure `native_pixels_per_point` is set during app creation [#7683](https://github.com/emilk/egui/pull/7683) by [@emilk](https://github.com/emilk) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index be00dd049..cef640184 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 * Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman) diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 8e555a7bc..f88a8c84a 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 * Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh) diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 9dbeb5879..15eaf8704 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/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.33.3 - 2025-12-11 +* Bump `ehttp` to 0.6.0 [#7757](https://github.com/emilk/egui/pull/7757) by [@jprochazk](https://github.com/jprochazk) + + ## 0.33.2 - 2025-11-13 Nothing new diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 4dd90a359..2fccf1166 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.33.3 - 2025-12-11 +* Enforce consistent snapshot updates [#7744](https://github.com/emilk/egui/pull/7744) by [@lucasmerlin](https://github.com/lucasmerlin) +* `kittest`: add drag-and-drop helpers [#7690](https://github.com/emilk/egui/pull/7690) by [@emilk](https://github.com/emilk) + + ## 0.33.2 - 2025-11-13 Nothing new diff --git a/crates/emath/CHANGELOG.md b/crates/emath/CHANGELOG.md index 334af345f..f559265c4 100644 --- a/crates/emath/CHANGELOG.md +++ b/crates/emath/CHANGELOG.md @@ -6,6 +6,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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 * Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index b23c454b4..dd6b4e2a5 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 Nothing new diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index 3f131cb55..58fd310f4 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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 Nothing new From 06e632535b4fe6a6d710239f268d6384cf310696 Mon Sep 17 00:00:00 2001 From: Johnchoi913 Date: Sun, 14 Dec 2025 09:23:41 -0500 Subject: [PATCH 332/388] Enable feature for example custom_3d_glow (#7730) * Small fix, no issue opened * [x] I have followed the instructions in the PR template --- examples/custom_3d_glow/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 8636b0f43..1aaca8a43 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] eframe = { workspace = true, features = [ "default", + "glow", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } env_logger = { workspace = true, features = ["auto-color", "humantime"] } From 8ef7f10367ecdca0e0938394ff217d3bf9f2ef0e Mon Sep 17 00:00:00 2001 From: Justin Symonds Date: Sun, 14 Dec 2025 06:24:08 -0800 Subject: [PATCH 333/388] Fixes for doc comments (#7668) * [x] I have followed the instructions in the PR template - Some typos/grammos - Attempt to finish incomplete comment - Broken link - I understand the colon is a convention for pluralizing symbol names, but it seems redundant in the presence of other punctuation --------- Co-authored-by: Lucas Meurer --- crates/egui/src/context.rs | 8 ++++---- crates/egui/src/lib.rs | 2 +- crates/egui/src/memory/mod.rs | 2 +- crates/egui/src/ui.rs | 20 ++++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 988226e2c..ad329e7fb 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2880,7 +2880,7 @@ impl Context { self.input(|i| i.pointer.hover_pos()) } - /// If you detect a click or drag and wants to know where it happened, use this. + /// If you detect a click or drag and want to know where it happened, use this. /// /// Latest position of the mouse, but ignoring any [`crate::Event::PointerGone`] /// if there were interactions this pass. @@ -2953,7 +2953,7 @@ impl Context { /// Moves the given area to the top in its [`Order`]. /// - /// [`crate::Area`]:s and [`crate::Window`]:s also do this automatically when being clicked on or interacted with. + /// [`crate::Area`]s and [`crate::Window`]s also do this automatically when being clicked on or interacted with. pub fn move_to_top(&self, layer_id: LayerId) { self.memory_mut(|mem| mem.areas_mut().move_to_top(layer_id)); } @@ -3072,7 +3072,7 @@ impl Context { /// for a responsive start and a slow end. /// /// The easing function flips when `target_value` is `false`, - /// so that when going back towards 0.0, we get + /// so that when going back towards 0.0, we get the reverse behavior. #[track_caller] // To track repaint cause pub fn animate_bool_with_time_and_easing( &self, @@ -3953,7 +3953,7 @@ impl Context { /// Show an immediate viewport, creating a new native window, if possible. /// /// This is the easier type of viewport to use, but it is less performant - /// at it requires both parent and child to repaint if any one of them needs repainting, + /// as it requires both parent and child to repaint if any one of them needs repainting, /// which effectively produce double work for two viewports, and triple work for three viewports, etc. /// To avoid this, use [`Self::show_viewport_deferred`] instead. /// diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index d756caf75..c39f254a2 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -195,7 +195,7 @@ //! * lays out the letters `click me` in order to figure out the size of the button //! * decides where on screen to place the button //! * check if the mouse is hovering or clicking that location -//! * chose button colors based on if it is being hovered or clicked +//! * choose button colors based on if it is being hovered or clicked //! * add a [`Shape::Rect`] and [`Shape::Text`] to the list of shapes to be painted later this frame //! * return a [`Response`] with the [`clicked`](`Response::clicked`) member so the user can check for interactions //! diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index d215a3bec..77a18dc04 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -21,7 +21,7 @@ pub use theme::{Theme, ThemePreference}; /// how far the user has scrolled in a [`ScrollArea`](crate::ScrollArea) etc. /// /// If you want this to persist when closing your app, you should serialize [`Memory`] and store it. -/// For this you need to enable the `persistence`. +/// For this you need to enable the `persistence` feature. /// /// If you want to store data for your widgets, you should look at [`Memory::data`] #[derive(Clone, Debug)] diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d230ed736..228f2beaf 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -74,14 +74,14 @@ pub struct Ui { /// This value is based on where in the hierarchy of widgets this Ui is in, /// and the value is increment with each added child widget. /// This works as an Id source only as long as new widgets aren't added or removed. - /// They are therefore only good for Id:s that has no state. + /// They are therefore only good for Id:s that have no state. next_auto_id_salt: u64, /// Specifies paint layer, clip rectangle and a reference to [`Context`]. painter: Painter, /// The [`Style`] (visuals, spacing, etc) of this ui. - /// Commonly many [`Ui`]:s share the same [`Style`]. + /// Commonly many [`Ui`]s share the same [`Style`]. /// The [`Ui`] implements copy-on-write for this. style: Arc