From 6b79845431ba1f4e2643cd9bba6b9b52c7b3a65e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 10 Nov 2025 21:49:31 +0100 Subject: [PATCH 01/43] 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 02/43] 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 03/43] 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 04/43] 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 05/43] 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 06/43] 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 07/43] 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 08/43] 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 09/43] [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 10/43] 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 11/43] 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 12/43] 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 13/43] 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 14/43] 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 15/43] 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 16/43] 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 17/43] 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 18/43] 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 19/43] 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 20/43] 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 21/43] 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 22/43] 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 23/43] 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 24/43] 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 25/43] 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 26/43] 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 27/43] 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 28/43] 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 29/43] 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 30/43] 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 31/43] 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 32/43] 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