From eb35f7d12f2a941707eee3406aab28e1df0262c9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 29 Mar 2026 10:00:36 +0200 Subject: [PATCH 01/84] Improve test of text rendering --- crates/egui_demo_lib/tests/misc.rs | 5 +++-- .../tests/snapshots/image_kerning/image_dark_x1.png | 4 ++-- .../tests/snapshots/image_kerning/image_dark_x2.png | 4 ++-- .../tests/snapshots/image_kerning/image_light_x1.png | 4 ++-- .../tests/snapshots/image_kerning/image_light_x2.png | 4 ++-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/egui_demo_lib/tests/misc.rs b/crates/egui_demo_lib/tests/misc.rs index d5f6a3a3c..6d66abfb1 100644 --- a/crates/egui_demo_lib/tests/misc.rs +++ b/crates/egui_demo_lib/tests/misc.rs @@ -13,8 +13,9 @@ fn test_kerning() { ui.label("Hello world!"); ui.label("Repeated characters: iiiiiiiiiiiii lllllllll mmmmmmmmmmmmmmmm"); ui.label("Thin spaces: −123 456 789"); - ui.label("Ligature: fi :)"); - ui.label("\ttabbed"); + ui.label("Ligature: fi fl ffi ffl"); + ui.label("Kerning: AVATAR"); + ui.label("\ttabbed\ttext"); }); harness.run(); harness.fit_contents(); 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 5cc884a55..e48dfae92 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:c8ea98c65376d9f6ac66d0a9471c4bf3add0904294e7ca1a105458b90654a2e2 -size 12476 +oid sha256:9c2990a81dfa8832f0cb1c4c0ce2f86e468a7a6f693e09efffa131ed3259e2e8 +size 15428 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 1359fd607..3f9da5333 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:968c478d986fc71d8655492b19e833ca07bc0ab85899dc04022bc7cf1dcf782f -size 29319 +oid sha256:07d987ff87c9f41ec71ceea0caff25795bf4dff525ef4ef241d0ba786acee3e1 +size 35960 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 b223bbb3d..84b808a83 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:3793a5e83ef9bdffef99bcd8905a094acb69cde356e3a7125a544045296c3926 -size 13070 +oid sha256:f5c5ce0d46231d90ccb04e158947d793d99cd5cce911c72b960b6d04feba2134 +size 16122 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 80e561325..ff848168a 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:61e59f8360c567e20bf03b401362de7bb0f87716f13e817cc8da3df742ab68bf -size 31869 +oid sha256:60dcd590b1d00361278b135ce9ef084c7382875c71c72b19fb6e23dba68f7902 +size 39279 From c2b482ff7ef7c9093d3a59398e94791a395ad9bf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 4 Apr 2026 12:03:41 +0200 Subject: [PATCH 02/84] Flip if-else:s with a negation (#8063) --- crates/eframe/src/web/events.rs | 10 +++++----- crates/egui/src/response.rs | 6 +++--- .../src/text_selection/text_cursor_state.rs | 8 ++++---- crates/egui/src/widgets/slider.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 12 +++++------ crates/egui_demo_app/src/wrap_app.rs | 12 +++++------ crates/epaint/src/stats.rs | 6 +++--- examples/file_dialog/src/main.rs | 12 +++++------ tests/test_viewports/src/main.rs | 20 +++++++++---------- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index d77444563..e24e99fee 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -1114,16 +1114,16 @@ fn get_display_size(resize_observer_entries: &js_sys::Array) -> Result<(u32, u32 } else if JsValue::from_str("contentBoxSize").js_in(entry.as_ref()) { let content_box_size = entry.content_box_size(); let idx0 = content_box_size.at(0); - if !idx0.is_undefined() { - let size: web_sys::ResizeObserverSize = idx0.dyn_into()?; - width = size.inline_size(); - height = size.block_size(); - } else { + if idx0.is_undefined() { // legacy let size = JsValue::clone(content_box_size.as_ref()); let size: web_sys::ResizeObserverSize = size.dyn_into()?; width = size.inline_size(); height = size.block_size(); + } else { + let size: web_sys::ResizeObserverSize = idx0.dyn_into()?; + width = size.inline_size(); + height = size.block_size(); } if DEBUG_RESIZE { log::info!("contentBoxSize {width}x{height}"); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index a0dd6bd91..54d53f2b4 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -272,10 +272,10 @@ impl Response { false } else if let Some(pos) = pointer_interact_pos { let layer_under_pointer = self.ctx.layer_id_at(pos); - if layer_under_pointer != Some(self.layer_id) { - true - } else { + if layer_under_pointer == Some(self.layer_id) { !self.interact_rect.contains(pos) + } else { + true } } else { false // clicked without a pointer, weird diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index 9c9b0a263..12e0656d8 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -156,13 +156,13 @@ fn select_line_at(text: &str, ccursor: CCursor) -> CCursorRange { let min = ccursor_previous_line(text, ccursor); let max = ccursor_next_line(text, min); CCursorRange::two(min, max) - } else if !is_linebreak(char_after_cursor) { - let max = ccursor_next_line(text, ccursor); - CCursorRange::two(ccursor, max) - } else { + } else if is_linebreak(char_after_cursor) { let min = ccursor_previous_line(text, ccursor); let max = ccursor_next_line(text, ccursor); CCursorRange::two(min, max) + } else { + let max = ccursor_next_line(text, ccursor); + CCursorRange::two(ccursor, max) } } else { let min = ccursor_previous_line(text, ccursor); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 69d08c44e..1a9edf02d 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -314,7 +314,7 @@ impl<'a> Slider<'a> { /// Default: `0.0` (disabled). #[inline] pub fn step_by(mut self, step: f64) -> Self { - self.step = if step != 0.0 { Some(step) } else { None }; + self.step = if step == 0.0 { None } else { Some(step) }; self } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index ef668a02e..6f4d9a044 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -680,7 +680,9 @@ impl TextEdit<'_> { .wrap_mode(wrap_mode) .allocate(ui); - allocated.frame = if !custom_frame { + allocated.frame = if custom_frame { + allocated.frame + } else { let visuals = ui.style().interact(&allocated.response); let background_color = background_color.unwrap_or_else(|| ui.visuals().text_edit_bg_color()); @@ -713,8 +715,6 @@ impl TextEdit<'_> { ) .outer_margin(Margin::same(-(visuals.expansion as i8))) .stroke(stroke) - } else { - allocated.frame }; allocated.paint(ui) @@ -1019,7 +1019,9 @@ fn events( } } Event::Paste(text_to_insert) => { - if !text_to_insert.is_empty() { + if text_to_insert.is_empty() { + None + } else { let mut ccursor = text.delete_selected(&cursor_range); if multiline { text.insert_text_at(&mut ccursor, text_to_insert, char_limit); @@ -1029,8 +1031,6 @@ fn events( } Some(CCursorRange::one(ccursor)) - } else { - None } } Event::Text(text_to_insert) => { diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 313a1a685..9f43f8624 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -466,10 +466,10 @@ impl WrapApp { for file in &i.raw.hovered_files { if let Some(path) = &file.path { write!(text, "\n{}", path.display()).ok(); - } else if !file.mime.is_empty() { - write!(text, "\n{}", file.mime).ok(); - } else { + } else if file.mime.is_empty() { text += "\n???"; + } else { + write!(text, "\n{}", file.mime).ok(); } } text @@ -505,10 +505,10 @@ impl WrapApp { for file in &self.dropped_files { let mut info = if let Some(path) = &file.path { path.display().to_string() - } else if !file.name.is_empty() { - file.name.clone() - } else { + } else if file.name.is_empty() { "???".to_owned() + } else { + file.name.clone() }; let mut additional_info = vec![]; diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 1eef4f444..de8f275cf 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -135,10 +135,10 @@ impl AllocInfo { what, self.megabytes() ) - } else if self.element_size != ElementSize::Heterogenous { + } else if self.element_size == ElementSize::Heterogenous { format!( "{:6} {:16} {} {:3} allocations", - self.num_elements(), + "", what, self.megabytes(), self.num_allocs() @@ -146,7 +146,7 @@ impl AllocInfo { } else { format!( "{:6} {:16} {} {:3} allocations", - "", + self.num_elements(), what, self.megabytes(), self.num_allocs() diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index 63e7bb0a8..33bda7a96 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -50,10 +50,10 @@ impl eframe::App for MyApp { for file in &self.dropped_files { let mut info = if let Some(path) = &file.path { path.display().to_string() - } else if !file.name.is_empty() { - file.name.clone() - } else { + } else if file.name.is_empty() { "???".to_owned() + } else { + file.name.clone() }; let mut additional_info = vec![]; @@ -95,10 +95,10 @@ fn preview_files_being_dropped(ctx: &egui::Context) { for file in &i.raw.hovered_files { if let Some(path) = &file.path { write!(text, "\n{}", path.display()).ok(); - } else if !file.mime.is_empty() { - write!(text, "\n{}", file.mime).ok(); - } else { + } else if file.mime.is_empty() { text += "\n???"; + } else { + write!(text, "\n{}", file.mime).ok(); } } text diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index 3330c1313..25787c912 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -416,16 +416,7 @@ fn drag_source( ) -> InnerResponse { let is_being_dragged = ui.ctx().is_being_dragged(id); - if !is_being_dragged { - let res = ui.scope(body); - - // Check for drags: - let response = ui.interact(res.response.rect, id, egui::Sense::drag()); - if response.hovered() { - ui.set_cursor_icon(egui::CursorIcon::Grab); - } - res - } else { + if is_being_dragged { ui.set_cursor_icon(egui::CursorIcon::Grabbing); // Paint the body to a new layer: @@ -440,6 +431,15 @@ fn drag_source( ); } + res + } else { + let res = ui.scope(body); + + // Check for drags: + let response = ui.interact(res.response.rect, id, egui::Sense::drag()); + if response.hovered() { + ui.set_cursor_icon(egui::CursorIcon::Grab); + } res } } From 0435d2a9a1417b9f3ff726d377656ce81b02d538 Mon Sep 17 00:00:00 2001 From: Michael Grupp Date: Sat, 4 Apr 2026 12:09:56 +0200 Subject: [PATCH 03/84] Add `HarnessBuilder::with_render_options()` (closes #7630) (#8060) Allows to override the default `PREDICTABLE` render options, e.g. if it's desired to create snapshots with the exact texture options used by the app. See #7630 for details / examples. --- crates/egui_kittest/src/builder.rs | 19 ++++++++++++++++++- crates/egui_kittest/src/lib.rs | 5 +++++ crates/egui_kittest/src/wgpu.rs | 21 +++++++++++++++++---- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index b33e37e39..a81e9dca0 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -18,6 +18,9 @@ pub struct HarnessBuilder { #[cfg(feature = "snapshot")] pub(crate) default_snapshot_options: crate::SnapshotOptions, + + #[cfg(feature = "wgpu")] + pub(crate) render_options: egui_wgpu::RendererOptions, } impl Default for HarnessBuilder { @@ -35,6 +38,9 @@ impl Default for HarnessBuilder { #[cfg(feature = "snapshot")] default_snapshot_options: crate::SnapshotOptions::default(), + + #[cfg(feature = "wgpu")] + render_options: egui_wgpu::RendererOptions::PREDICTABLE, } } } @@ -119,6 +125,16 @@ impl HarnessBuilder { self } + /// Configures the [`egui_wgpu::RendererOptions`] used by this harness. + /// + /// The default is [`egui_wgpu::RendererOptions::PREDICTABLE`]. + #[cfg(feature = "wgpu")] + #[inline] + pub fn with_render_options(mut self, options: egui_wgpu::RendererOptions) -> Self { + self.render_options = options; + self + } + /// Set the [`TestRenderer`] to use for rendering. /// /// By default, a [`LazyRenderer`] is used. @@ -133,7 +149,8 @@ impl HarnessBuilder { /// This sets up a [`crate::wgpu::WgpuTestRenderer`] with the default setup. #[cfg(feature = "wgpu")] pub fn wgpu(self) -> Self { - self.renderer(crate::wgpu::WgpuTestRenderer::default()) + let test_renderer = crate::wgpu::WgpuTestRenderer::with_render_options(self.render_options); + self.renderer(test_renderer) } /// Enable wgpu rendering with the given setup. diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index b9f7dc7c2..9b2319caf 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -116,6 +116,11 @@ impl<'a, State> Harness<'a, State> { #[cfg(feature = "snapshot")] default_snapshot_options, + + // rustfmt adds this weird indentation below. + // See: https://github.com/rust-lang/rustfmt/issues/5920 + #[cfg(feature = "wgpu")] + render_options: _, } = builder; let ctx = ctx.unwrap_or_default(); ctx.set_theme(theme); diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index a9f0de9ad..45152e81e 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -58,7 +58,10 @@ pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup { egui_wgpu::WgpuSetup::CreateNew(setup) } -pub fn create_render_state(setup: WgpuSetup) -> egui_wgpu::RenderState { +pub fn create_render_state( + setup: WgpuSetup, + options: egui_wgpu::RendererOptions, +) -> egui_wgpu::RenderState { // No display handle needed for headless testing — we don't present to a window. let instance = pollster::block_on(setup.new_instance()); @@ -69,7 +72,7 @@ pub fn create_render_state(setup: WgpuSetup) -> egui_wgpu::RenderState { }, &instance, None, - egui_wgpu::RendererOptions::PREDICTABLE, + options, )) .expect("Failed to create render state") } @@ -89,14 +92,17 @@ impl WgpuTestRenderer { /// Create a new [`WgpuTestRenderer`] with the default setup. pub fn new() -> Self { Self { - render_state: create_render_state(default_wgpu_setup()), + render_state: create_render_state( + default_wgpu_setup(), + egui_wgpu::RendererOptions::PREDICTABLE, + ), } } /// Create a new [`WgpuTestRenderer`] with the given setup. pub fn from_setup(setup: WgpuSetup) -> Self { Self { - render_state: create_render_state(setup), + render_state: create_render_state(setup, egui_wgpu::RendererOptions::PREDICTABLE), } } @@ -115,6 +121,13 @@ impl WgpuTestRenderer { ); Self { render_state } } + + /// Create a new [`WgpuTestRenderer`] with custom render options. + pub fn with_render_options(options: egui_wgpu::RendererOptions) -> Self { + Self { + render_state: create_render_state(default_wgpu_setup(), options), + } + } } impl crate::TestRenderer for WgpuTestRenderer { From 8ada641ee2b7df6cf11f2346537a98293664dcb8 Mon Sep 17 00:00:00 2001 From: psyche <180074435+EtherealPsyche@users.noreply.github.com> Date: Sat, 4 Apr 2026 18:28:06 +0800 Subject: [PATCH 04/84] perf: remove redundant clone in about.rs demo (#8057) As shown in the commit, this is exactly what it contains. There was a redundant and seemingly meaningless `.clone()` in `about.rs`, which I removed. Was it because the function signature of `Image::new()` was different in older versions of egui? Co-authored-by: EtherealPsyche --- crates/egui_demo_lib/src/demo/about.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/about.rs b/crates/egui_demo_lib/src/demo/about.rs index 853ebf490..227aff429 100644 --- a/crates/egui_demo_lib/src/demo/about.rs +++ b/crates/egui_demo_lib/src/demo/about.rs @@ -29,9 +29,8 @@ impl crate::View for About { ui.vertical_centered(|ui| { ui.add_space(4.0); - let egui_icon = egui::include_image!("../../data/egui-logo.svg"); ui.add( - egui::Image::new(egui_icon.clone()) + egui::Image::new(egui::include_image!("../../data/egui-logo.svg")) .max_height(30.0) .tint(ui.visuals().strong_text_color()), ); From 4a09782fce11c03b8afa30b8a4a5a2d0942b3303 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 4 Apr 2026 16:20:29 +0200 Subject: [PATCH 05/84] Enable a few more clippy lints (#8064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable these new clippy lints and fix all warnings: * `format_push_string` — use `write!` instead of `s += &format!(…)` to avoid extra allocations * `ignored_unit_patterns` — use `()` instead of `_` when matching unit * `missing_fields_in_debug` — ensure manual `Debug` impls account for all fields * `needless_raw_string_hashes` — remove unnecessary `r#` on string literals * `ref_option` — prefer `Option<&T>` over `&Option` in function signatures --- Cargo.toml | 5 +++ crates/eframe/src/native/file_storage.rs | 2 +- crates/egui-wgpu/src/lib.rs | 33 ++++++++++++++----- crates/egui-wgpu/src/setup.rs | 17 +++++++--- crates/egui/src/callstack.rs | 14 +++++--- crates/egui/src/containers/window.rs | 6 ++-- crates/egui/src/context.rs | 15 ++++++--- crates/egui/src/data/input.rs | 2 ++ .../text_selection/label_text_selection.rs | 11 +++++-- crates/egui/src/widgets/drag_value.rs | 8 ++--- crates/egui_demo_app/src/main.rs | 3 +- crates/egui_demo_app/src/wrap_app.rs | 3 +- crates/egui_extras/src/table.rs | 12 +++---- crates/egui_kittest/src/snapshot.rs | 8 ++--- examples/custom_3d_glow/src/main.rs | 8 ++--- examples/file_dialog/src/main.rs | 3 +- tests/test_background_logic/src/main.rs | 3 +- 17 files changed, 100 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c6cb0c03..d589ef954 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -218,10 +218,12 @@ flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" +format_push_string = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" if_let_mutex = "warn" ignore_without_reason = "warn" +ignored_unit_patterns = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" @@ -270,6 +272,7 @@ mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_enforced_import_renames = "warn" missing_errors_doc = "warn" +missing_fields_in_debug = "warn" missing_safety_doc = "warn" mixed_attributes_style = "warn" mut_mut = "warn" @@ -279,6 +282,7 @@ needless_continue = "warn" needless_for_each = "warn" needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" +needless_raw_string_hashes = "warn" negative_feature_names = "warn" non_std_lazy_statics = "warn" non_zero_suggestions = "warn" @@ -299,6 +303,7 @@ rc_mutex = "warn" readonly_write_lock = "warn" redundant_type_annotations = "warn" ref_as_ptr = "warn" +ref_option = "warn" ref_option_ref = "warn" ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index f26a6df74..13e22fa75 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -207,7 +207,7 @@ fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { profiling::scope!("ron::serialize"); if let Err(err) = ron::Options::default() .to_io_writer_pretty(&mut writer, &kv, config) - .and_then(|_| writer.flush().map_err(|err| err.into())) + .and_then(|()| writer.flush().map_err(|err| err.into())) { log::warn!("Failed to serialize app state: {err}"); } else { diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index eb04173b5..180b305be 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -427,37 +427,52 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String { // > name: "Apple M1 Pro", device_type: IntegratedGpu, backend: Metal, driver: "", driver_info: "" // > name: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)", device_type: IntegratedGpu, backend: Gl, driver: "", driver_info: "" + use std::fmt::Write as _; + let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}"); if !name.is_empty() { - summary += &format!(", name: {name:?}"); + write!(summary, ", name: {name:?}").ok(); } if !driver.is_empty() { - summary += &format!(", driver: {driver:?}"); + write!(summary, ", driver: {driver:?}").ok(); } if !driver_info.is_empty() { - summary += &format!(", driver_info: {driver_info:?}"); + write!(summary, ", driver_info: {driver_info:?}").ok(); } if *vendor != 0 { #[cfg(not(target_arch = "wasm32"))] { - summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor)); + write!( + summary, + ", vendor: {} (0x{vendor:04X})", + parse_vendor_id(*vendor) + ) + .ok(); } #[cfg(target_arch = "wasm32")] { - summary += &format!(", vendor: 0x{vendor:04X}"); + write!(summary, ", vendor: 0x{vendor:04X}").ok(); } } if *device != 0 { - summary += &format!(", device: 0x{device:02X}"); + write!(summary, ", device: 0x{device:02X}").ok(); } if !device_pci_bus_id.is_empty() { - summary += &format!(", pci_bus_id: {device_pci_bus_id:?}"); + write!(summary, ", pci_bus_id: {device_pci_bus_id:?}").ok(); } if *subgroup_min_size != 0 || *subgroup_max_size != 0 { - summary += &format!(", subgroup_size: {subgroup_min_size}..={subgroup_max_size}"); + write!( + summary, + ", subgroup_size: {subgroup_min_size}..={subgroup_max_size}" + ) + .ok(); } - summary += &format!(", transient_saves_memory: {transient_saves_memory}"); + write!( + summary, + ", transient_saves_memory: {transient_saves_memory}" + ) + .ok(); summary } diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index c5b3f0421..cf8b652c2 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -296,15 +296,22 @@ impl Clone for WgpuSetupCreateNew { impl std::fmt::Debug for WgpuSetupCreateNew { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + instance_descriptor, + display_handle, + power_preference, + native_adapter_selector, + device_descriptor: _, + } = self; f.debug_struct("WgpuSetupCreateNew") - .field("instance_descriptor", &self.instance_descriptor) - .field("display_handle", &self.display_handle) - .field("power_preference", &self.power_preference) + .field("instance_descriptor", instance_descriptor) + .field("display_handle", display_handle) + .field("power_preference", power_preference) .field( "native_adapter_selector", - &self.native_adapter_selector.is_some(), + &native_adapter_selector.is_some(), ) - .finish() + .finish_non_exhaustive() } } diff --git a/crates/egui/src/callstack.rs b/crates/egui/src/callstack.rs index fef9b2166..b1239db01 100644 --- a/crates/egui/src/callstack.rs +++ b/crates/egui/src/callstack.rs @@ -1,3 +1,5 @@ +use std::fmt::Write as _; + #[derive(Clone)] struct Frame { /// `_main` is usually as the deepest depth. @@ -23,7 +25,7 @@ pub fn capture() -> String { if let Some(file_and_line) = &mut file_and_line && let Some(line_nr) = symbol.lineno() { - file_and_line.push_str(&format!(":{line_nr}")); + write!(file_and_line, ":{line_nr}").ok(); } let file_and_line = file_and_line.unwrap_or_default(); @@ -130,12 +132,14 @@ pub fn capture() -> String { if frame.depth + 1 < last_depth || last_depth + 1 < frame.depth { // Show that some frames were elided - formatted.push_str(&format!("{:widest_depth$} …\n", "")); + writeln!(formatted, "{:widest_depth$} …", "").ok(); } - formatted.push_str(&format!( - "{depth:widest_depth$}: {file_and_line:widest_file_line$} {name}\n" - )); + writeln!( + formatted, + "{depth:widest_depth$}: {file_and_line:widest_file_line$} {name}" + ) + .ok(); last_depth = frame.depth; } diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index ae5fbfc8f..5ade37014 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -673,7 +673,7 @@ impl Window<'_> { title_bar.ui( &mut area_content_ui, - &content_response, + content_response.as_ref(), open.as_deref_mut(), &mut collapsing, collapsible, @@ -1256,7 +1256,7 @@ impl TitleBar { fn ui( self, ui: &mut Ui, - content_response: &Option, + content_response: Option<&Response>, open: Option<&mut bool>, collapsing: &mut CollapsingState, collapsible: bool, @@ -1300,7 +1300,7 @@ impl TitleBar { ui.visuals().text_color(), ); - if let Some(content_response) = &content_response { + if let Some(content_response) = content_response { // Paint separator between title and content: let content_rect = content_response.rect; if false { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index a8751bffc..824a5318c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2428,6 +2428,7 @@ impl Context { #[cfg(debug_assertions)] fn debug_painting(&self) { #![expect(clippy::iter_over_hash_type)] // ok to be sloppy in debug painting + use std::fmt::Write as _; let paint_widget = |widget: &WidgetRect, text: &str, color: Color32| { let rect = widget.interact_rect; @@ -2500,13 +2501,17 @@ impl Context { for id in contains_pointer { let mut widget_text = format!("{id:?}"); if let Some(rect) = widget_rects.get(id) { - widget_text += - &format!(" {:?} {:?} {:?}", rect.layer_id, rect.rect, rect.sense); + write!( + widget_text, + " {:?} {:?} {:?}", + rect.layer_id, rect.rect, rect.sense + ) + .ok(); } if let Some(info) = widget_rects.info(id) { - widget_text += &format!(" {info:?}"); + write!(widget_text, " {info:?}").ok(); } - debug_text += &format!("{widget_text}\n"); + writeln!(debug_text, "{widget_text}").ok(); } self.debug_text(debug_text); } @@ -2571,7 +2576,7 @@ impl Context { ); self.viewport(|vp| { for reason in &vp.output.request_discard_reasons { - warning += &format!("\n {reason}"); + write!(warning, "\n {reason}").ok(); } }); diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 5e1680334..00cf59cba 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -376,12 +376,14 @@ impl ViewportInfo { ui.label(opt_as_str(&visible)); ui.end_row(); + #[expect(clippy::ref_option)] fn opt_rect_as_string(v: &Option) -> String { v.as_ref().map_or(String::new(), |r| { format!("Pos: {:?}, size: {:?}", r.min, r.size()) }) } + #[expect(clippy::ref_option)] fn opt_as_str(v: &Option) -> String { v.as_ref().map_or(String::new(), |v| format!("{v:?}")) } diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index 1dc825ad3..3df17d17c 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -49,10 +49,15 @@ fn pos_in_galley(galley: &Galley, ccursor: CCursor) -> Pos2 { impl std::fmt::Debug for WidgetTextCursor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + widget_id, + ccursor, + pos: _, + } = self; f.debug_struct("WidgetTextCursor") - .field("widget_id", &self.widget_id.short_debug_format()) - .field("ccursor", &self.ccursor.index) - .finish() + .field("widget_id", &widget_id.short_debug_format()) + .field("ccursor", &ccursor.index) + .finish_non_exhaustive() } } diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 1297b614b..cca43ea2f 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -551,7 +551,7 @@ impl Widget for DragValue<'_> { if let Some(value_text) = value_text { // We were editing the value as text last frame, but lost focus. // Make sure we applied the last text value: - let parsed_value = parse(&custom_parser, &value_text); + let parsed_value = parse(custom_parser.as_ref(), &value_text); if let Some(mut parsed_value) = parsed_value { // User edits always clamps: parsed_value = clamp_value_to_range(parsed_value, range.clone()); @@ -591,7 +591,7 @@ impl Widget for DragValue<'_> { response.lost_focus() && !ui.input(|i| i.key_pressed(Key::Escape)) }; if update { - let parsed_value = parse(&custom_parser, &value_text); + let parsed_value = parse(custom_parser.as_ref(), &value_text); if let Some(mut parsed_value) = parsed_value { // User edits always clamps: parsed_value = clamp_value_to_range(parsed_value, range.clone()); @@ -733,8 +733,8 @@ impl Widget for DragValue<'_> { } } -fn parse(custom_parser: &Option>, value_text: &str) -> Option { - match &custom_parser { +fn parse(custom_parser: Option<&NumParser<'_>>, value_text: &str) -> Option { + match custom_parser { Some(parser) => parser(value_text), None => default_parser(value_text), } diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index a4ffdb5f9..38da5751d 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -38,7 +38,8 @@ fn main() { }); for loud_crate in ["naga", "wgpu_core", "wgpu_hal"] { if !rust_log.contains(&format!("{loud_crate}=")) { - rust_log += &format!(",{loud_crate}=warn"); + use std::fmt::Write as _; + write!(rust_log, ",{loud_crate}=warn").ok(); } } diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 9f43f8624..64ecbce56 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -519,7 +519,8 @@ impl WrapApp { additional_info.push(format!("{} bytes", bytes.len())); } if !additional_info.is_empty() { - info += &format!(" ({})", additional_info.join(", ")); + use std::fmt::Write as _; + write!(info, " ({})", additional_info.join(", ")).ok(); } ui.label(info); diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 25257421a..f62b71122 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -996,7 +996,7 @@ impl<'a> TableBody<'a> { overline: false, response: &mut response, }); - self.capture_hover_state(&response, self.row_index); + self.capture_hover_state(response.as_ref(), self.row_index); let bottom_y = self.layout.cursor.y; if Some(self.row_index) == self.scroll_to_row { @@ -1078,7 +1078,7 @@ impl<'a> TableBody<'a> { overline: false, response: &mut response, }); - self.capture_hover_state(&response, row_index); + self.capture_hover_state(response.as_ref(), row_index); } if total_rows - max_row > 0 { @@ -1160,7 +1160,7 @@ impl<'a> TableBody<'a> { overline: false, response: &mut response, }); - self.capture_hover_state(&response, row_index); + self.capture_hover_state(response.as_ref(), row_index); break; } } @@ -1183,7 +1183,7 @@ impl<'a> TableBody<'a> { selected: false, response: &mut response, }); - self.capture_hover_state(&response, row_index); + self.capture_hover_state(response.as_ref(), row_index); cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { @@ -1234,8 +1234,8 @@ impl<'a> TableBody<'a> { // Capture the hover information for the just created row. This is used in the next render // to ensure that the entire row is highlighted. - fn capture_hover_state(&self, response: &Option, row_index: usize) { - let is_row_hovered = response.as_ref().is_some_and(|r| r.hovered()); + fn capture_hover_state(&self, response: Option<&Response>, row_index: usize) { + let is_row_hovered = response.is_some_and(|r| r.hovered()); if is_row_hovered { self.layout .ui diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 28b5ac986..adbe32399 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -563,7 +563,7 @@ pub fn image_snapshot_options( options: &SnapshotOptions, ) { match try_image_snapshot_options(current, name, options) { - Ok(_) => {} + Ok(()) => {} Err(err) => { panic!("{err}"); } @@ -582,7 +582,7 @@ pub fn image_snapshot_options( #[track_caller] pub fn image_snapshot(current: &image::RgbaImage, name: impl Into) { match try_image_snapshot(current, name) { - Ok(_) => {} + Ok(()) => {} Err(err) => { panic!("{err}"); } @@ -884,14 +884,14 @@ impl Drop for SnapshotResults { #[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 ); } diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index 6b804ed1c..e0a3e3dd2 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -106,7 +106,7 @@ impl RotatingTriangle { let program = gl.create_program().expect("Cannot create program"); let (vertex_shader_source, fragment_shader_source) = ( - r#" + " const vec2 verts[3] = vec2[3]( vec2(0.0, 1.0), vec2(-1.0, -1.0), @@ -124,15 +124,15 @@ impl RotatingTriangle { gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); gl_Position.x *= cos(u_angle); } - "#, - r#" + ", + " precision mediump float; in vec4 v_color; out vec4 out_color; void main() { out_color = v_color; } - "#, + ", ); let shader_sources = [ diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index 33bda7a96..a2cef84e7 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -64,7 +64,8 @@ impl eframe::App for MyApp { additional_info.push(format!("{} bytes", bytes.len())); } if !additional_info.is_empty() { - info += &format!(" ({})", additional_info.join(", ")); + use std::fmt::Write as _; + write!(info, " ({})", additional_info.join(", ")).ok(); } ui.label(info); diff --git a/tests/test_background_logic/src/main.rs b/tests/test_background_logic/src/main.rs index ea80cfa9e..b1c559916 100644 --- a/tests/test_background_logic/src/main.rs +++ b/tests/test_background_logic/src/main.rs @@ -56,7 +56,8 @@ fn viewport_info(ctx: &egui::Context) -> String { ]; for (name, value) in flags { if let Some(value) = value { - s += &format!(" {name}={value}"); + use std::fmt::Write as _; + write!(s, " {name}={value}").ok(); } } s From 64341d9242403c482f8a791acc2c99e1077e6391 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Mon, 6 Apr 2026 10:17:01 +0200 Subject: [PATCH 06/84] Update of selected dependencies (#8042) Update some dependencies to get rid of old ones (especially nice to get rid of windows-sys 0.45...). * Closes * [x] I have followed the instructions in the PR template --- Cargo.lock | 402 ++++++++++++++++++++++++----------------------------- deny.toml | 2 +- 2 files changed, 182 insertions(+), 222 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec0b421f6..5814d9a85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.1", + "getrandom 0.3.4", "once_cell", "serde", "version_check", @@ -159,23 +159,22 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", "bitflags 2.9.4", "cc", - "cesu8", "jni", - "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", "num_enum", - "thiserror 1.0.66", + "simd_cesu8", + "thiserror 2.0.18", ] [[package]] @@ -369,7 +368,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.38", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -401,7 +400,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 0.38.38", + "rustix 0.38.44", "tracing", ] @@ -428,7 +427,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.38", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -512,7 +511,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -625,9 +624,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -670,7 +669,7 @@ dependencies = [ "bitflags 2.9.4", "log", "polling", - "rustix 0.38.38", + "rustix 0.38.44", "slab", "thiserror 1.0.66", ] @@ -682,7 +681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.38", + "rustix 0.38.44", "wayland-backend", "wayland-client", ] @@ -705,12 +704,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -1273,7 +1266,7 @@ dependencies = [ "epaint", "log", "profiling", - "thiserror 2.0.17", + "thiserror 2.0.18", "type-map", "web-time", "wgpu", @@ -1589,9 +1582,9 @@ checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "euclid" -version = "0.22.11" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" dependencies = [ "num-traits", ] @@ -1723,9 +1716,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "font-types" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23" +checksum = "73829a7b5c91198af28a99159b7ae4afbb252fb906159ff7f189f3a2ceaa3df2" dependencies = [ "bytemuck", "serde", @@ -1850,7 +1843,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.4", "windows-targets 0.52.6", ] @@ -1871,19 +1864,19 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasip2", ] [[package]] @@ -2001,7 +1994,7 @@ dependencies = [ "hashbrown 0.16.1", "log", "presser", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows", ] @@ -2376,25 +2369,61 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-macros", + "jni-sys 0.4.1", "log", - "thiserror 1.0.66", + "simd_cesu8", + "thiserror 2.0.18", "walkdir", - "windows-sys 0.45.0", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -2523,7 +2552,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.4", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.18", ] [[package]] @@ -2540,15 +2569,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -2657,7 +2686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -2691,7 +2720,7 @@ dependencies = [ "once_cell", "rustc-hash 1.1.0", "spirv", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-ident", ] @@ -2702,7 +2731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.9.4", - "jni-sys", + "jni-sys 0.3.1", "log", "ndk-sys", "num_enum", @@ -2722,7 +2751,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.1", ] [[package]] @@ -3196,9 +3225,9 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3418,7 +3447,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 0.38.38", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -3484,9 +3513,9 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -3587,6 +3616,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -3643,7 +3678,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.4", ] [[package]] @@ -3711,9 +3746,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] @@ -3726,7 +3761,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3867,29 +3902,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustix" -version = "0.38.38" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "semver", ] [[package]] name = "rustix" -version = "1.0.8" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", ] [[package]] @@ -4000,6 +4044,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -4093,6 +4143,22 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -4175,7 +4241,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix 0.38.38", + "rustix 0.38.44", "thiserror 1.0.66", "wayland-backend", "wayland-client", @@ -4301,7 +4367,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", "yaml-rust", ] @@ -4313,9 +4379,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -4388,11 +4454,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4408,9 +4474,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -4535,26 +4601,17 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.2+spec-1.1.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1dfefef6a142e93f346b64c160934eb13b5594b84ab378133ac6815cb2bd57f" +checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "serde_core", "serde_spanned", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -4566,12 +4623,12 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] @@ -4642,13 +4699,13 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "uds_windows" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -4890,12 +4947,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -4965,7 +5022,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 1.0.8", + "rustix 1.1.4", "scoped-tls", "smallvec", "wayland-sys", @@ -4978,7 +5035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.4", - "rustix 1.0.8", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] @@ -5000,7 +5057,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.4", "wayland-client", "xcursor", ] @@ -5088,9 +5145,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" +checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" dependencies = [ "core-foundation 0.10.1", "jni", @@ -5171,7 +5228,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", "wgpu-core-deps-wasm", @@ -5260,7 +5317,7 @@ dependencies = [ "raw-window-metal", "renderdoc-sys", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen", "wayland-sys", "web-sys", @@ -5294,22 +5351,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -5319,12 +5360,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows" version = "0.62.2" @@ -5354,7 +5389,7 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", + "windows-link", "windows-result", "windows-strings", ] @@ -5366,7 +5401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core", - "windows-link 0.2.1", + "windows-link", "windows-threading", ] @@ -5392,12 +5427,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -5411,7 +5440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5420,7 +5449,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5429,16 +5458,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-link", ] [[package]] @@ -5465,7 +5485,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] @@ -5474,22 +5494,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-link", ] [[package]] @@ -5510,11 +5515,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -5531,15 +5536,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5552,12 +5551,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5570,12 +5563,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5600,12 +5587,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5618,12 +5599,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5636,12 +5611,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5654,12 +5623,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5704,7 +5667,7 @@ dependencies = [ "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix 0.38.38", + "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -5734,13 +5697,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.33.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.9.4", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -5770,7 +5730,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 1.0.8", + "rustix 1.1.4", "x11rb-protocol", ] diff --git a/deny.toml b/deny.toml index 2d3231535..fb6d88e66 100644 --- a/deny.toml +++ b/deny.toml @@ -52,12 +52,12 @@ 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 = "jni-sys" }, # 0.3.1 depends on 0.4 { name = "kurbo" }, # Old version because of resvg { name = "redox_syscall" }, # old version via winit { name = "rustc-hash" }, # Small enough { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x { name = "thiserror-impl" }, # same as above - { name = "toml_datetime" }, # required while eco-system updates to toml 1.0 ] skip-tree = [ { name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15 From 33e89e33bec3e0009096323f0832e73265c29e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uma=C4=B5o?= <107099960+umajho@users.noreply.github.com> Date: Mon, 6 Apr 2026 16:24:50 +0800 Subject: [PATCH 07/84] Improve IME handling, add public method `owns_ime_events` on `Memory` (#7983) * Depends on #7967 * Closes #7485 * Should fix #7906 (This issue doesn't seem to have been resolved, but the author closed it; I personally don't have the environment to verify whether it is fixed.) * Replaces #4137, #4896, and partially #7810 * [x] I have followed the instructions in the PR template This PR started as a fix for #7485, but has since evolved into a broader rewrite of IME-related logic. ## Overview This PR primarily introduces a new public method, `owns_ime_events`, on [`Memory`], and refactors parts of [`TextEdit`] to integrate with it. Previously, each [`TextEdit`] widget independently determined whether to handle IME events and stored its own IME-related state. This approach made ownership-handling fragmented and was therefore error-prone. With this PR: - IME event ownership is centralized, ensuring that at most a single widget owns IME events per frame. - [`PlatformOutput`]'s `ime` field can be set to `None` for at least one frame when IME composition is interrupted, allowing the IME to be properly dismissed. ## Details Two new public methods are introduced on [`Memory`]: - `fn owns_ime_events(&self, id: Id) -> bool`: check IME event ownership for the current frame for the widget with the given `id`. - `fn interrupt_ime(&mut self)`: interrupt the current IME composition, if any. Since the newly added methods on [`Memory`] are public, other widgets can also participate in IME handling without risking ownership conflicts of IME events. I also added an internal (`pub(crate)`) field on [`TextEditState`], called `cursor_purpose`, to distinguish the role of the [`TextEdit`] cursor. Additionally, `egui::ImeEvent::Enabled` and `egui::ImeEvent::Disabled` have been removed, as they are no longer used anywhere. ## Demonstrations ### Windows: The Korean IME text duplication bug fixed in #4137 does not reappear.
With this PR Without this PR
Behavior Correct (no regression) Correct
Screencast ![win-kor-after](https://github.com/user-attachments/assets/1b080c8f-2031-406f-8781-aacafd5c879a) ![win-kor-before](https://github.com/user-attachments/assets/20258841-72fe-4652-b9a9-9b40e338ccf2)
### Windows: Chinese and Japanese IMEs now behave more consistently with the Korean IME in similar scenarios. This change does not matter much, as composition is rarely interrupted mid-process with these IMEs in typical usage.
With this PR Without this PR
Behavior Composition can be interrupted by clicking (like Korean IMEs) Composition can not interrupted by clicking
Screencast (Builtin Chinese IME) ![win-cmn-after](https://github.com/user-attachments/assets/2c76b0a9-da6d-48e1-84e0-47d9631f1196) ![win-cmn-before](https://github.com/user-attachments/assets/ea125fb8-c325-48d5-abaf-17d495b8f075)
Screencast (Builtin Japanese IME) ![win-jpn-after](https://github.com/user-attachments/assets/c69e5f48-65b1-4c0f-af4a-522d2f47b75d) ![win-jpn-before](https://github.com/user-attachments/assets/a0f1fdad-4f6c-40c2-af57-029f42acf6d5)
### macOS: was buggy, still buggy Likely due to this upstream bug in `winit`: https://github.com/rust-windowing/winit/issues/4432 Once `winit` is updated to a version that includes the fix, the behavior should become correct with this PR.
With this PR Without this PR
Behavior Buggy as before Buggy: Characters are duplicated
Screencast ![mac-kor-after](https://github.com/user-attachments/assets/c2bd90e8-e473-49c8-9537-c970c92889bf) ![mac-kor-before](https://github.com/user-attachments/assets/63b6cd8a-8903-4743-98bf-ee15296354ba)
### Wayland + iBus: Korean IME duplication bug fixed
With this PR Without this PR
Behavior Correct Buggy: Characters are duplicated
Screencast ![wayland-kor-after-2](https://github.com/user-attachments/assets/b154add5-a1ce-4e3a-b243-e72480820c1b) ![wayland-kor-before-2](https://github.com/user-attachments/assets/43b28374-f273-4b6f-9845-3efd96ec9a37)
### Wayland + iBus: #7485 is fixed
With this PR Without this PR
Behavior Correct Buggy: Only a single ASCII character can be typed after TextEdit is focused
Screencast ![wayland-7485-after](https://github.com/user-attachments/assets/ec33a54d-1d4e-40f9-8c82-202104bd2d85) ![wayland-7485-before](https://github.com/user-attachments/assets/20d2d395-03fd-4966-a376-87249a41aab3)
### Wayland + iBus: selection is also not broken This PR does not reintroduce the selection bug fixed in #7973.
With this PR
Behavior Correct
Screencast ![wayland-focus-after](https://github.com/user-attachments/assets/daa29197-f7f7-4a7b-b454-c28ee9afa9c1)
### X11 + Fcitx5: IME composition can be interrupted But due to #7975, the experience is still subpar. (Uncommitted text is lost after interruption.)
With this PR Without this PR
Screencast ![x11-after](https://github.com/user-attachments/assets/e626d9ed-89a2-4825-9cde-3a67723bcb82) ![x11-before](https://github.com/user-attachments/assets/da93b351-9488-4da9-aa56-b64190e84ec3)
[`Memory`]: https://docs.rs/egui/latest/egui/struct.Memory.html [`TextEdit`]: https://docs.rs/egui/latest/egui/widgets/text_edit/struct.TextEdit.html [`PlatformOutput`]: https://docs.rs/egui/latest/egui/struct.PlatformOutput.html [`TextEditState`]: https://docs.rs/egui/latest/egui/widgets/text_edit/struct.TextEditState.html --- crates/eframe/src/web/text_agent.rs | 4 - crates/egui-winit/src/lib.rs | 64 +--------- crates/egui/src/data/input.rs | 7 ++ crates/egui/src/data/output.rs | 3 + crates/egui/src/memory/mod.rs | 60 ++++++++++ crates/egui/src/widgets/text_edit/builder.rs | 120 ++++++++++--------- crates/egui/src/widgets/text_edit/state.rs | 22 ++-- 7 files changed, 154 insertions(+), 126 deletions(-) diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index ac917329f..e3d4f8860 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -75,11 +75,7 @@ impl TextAgent { }; let on_composition_start = { - let input = input.clone(); move |_: web_sys::CompositionEvent, runner: &mut AppRunner| { - input.set_value(""); - let event = egui::Event::Ime(egui::ImeEvent::Enabled); - runner.input.raw.events.push(event); // Repaint moves the text agent into place, // see `move_to` in `AppRunner::handle_platform_output`. runner.needs_repaint.repaint_asap(); diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 90f0311d5..99a9894f3 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -101,9 +101,6 @@ pub struct State { /// Only one touch will be interpreted as pointer at any time. pointer_touch_id: Option, - /// track ime state - has_sent_ime_enabled: bool, - #[cfg(feature = "accesskit")] pub accesskit: Option, @@ -150,8 +147,6 @@ impl State { simulate_touch_screen: false, pointer_touch_id: None, - has_sent_ime_enabled: false, - #[cfg(feature = "accesskit")] accesskit: None, @@ -689,17 +684,11 @@ impl State { // } match ime { - winit::event::Ime::Enabled => { - if cfg!(target_os = "linux") { - // This event means different things in X11 and Wayland, but we can just - // ignore it and enable IME on the preedit event. - // See - } else { - self.ime_event_enable(); - } - } - winit::event::Ime::Preedit(text, Some(_cursor)) => { - self.ime_event_enable(); + // [`winit::event::Ime::Enabled`] means different things in X11 and + // Wayland, but it doesn't matter to us. + // See + winit::event::Ime::Enabled | winit::event::Ime::Disabled => {} + winit::event::Ime::Preedit(text, _) => { self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone()))); @@ -708,53 +697,10 @@ impl State { self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone()))); - self.ime_event_disable(); - } - winit::event::Ime::Disabled => { - self.ime_event_disable(); - } - winit::event::Ime::Preedit(_, None) => { - if cfg!(target_os = "macos") { - // On macOS, when the user presses backspace to delete the - // last character in an IME composition, `winit` only emits - // `winit::event::Ime::Preedit("", None)` without a - // preceding `winit::event::Ime::Preedit("", Some(0, 0))`. - // - // The current implementation of `egui::TextEdit` relies on - // receiving an `egui::ImeEvent::Preedit("")` to remove the - // last character in the composition in this case, so we - // emit it here. - // - // This is guarded to macOS-only, as applying it on other - // platforms is unnecessary and can cause undesired - // behavior. - // See: https://github.com/emilk/egui/pull/7973 - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Preedit(String::new()))); - } - - self.ime_event_disable(); } } } - pub fn ime_event_enable(&mut self) { - if !self.has_sent_ime_enabled { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Enabled)); - self.has_sent_ime_enabled = true; - } - } - - pub fn ime_event_disable(&mut self) { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Disabled)); - self.has_sent_ime_enabled = false; - } - /// Returns `true` if the event was sent to egui. pub fn on_mouse_motion(&mut self, delta: (f64, f64)) -> bool { if !self.is_pointer_in_window() && !self.any_pointer_button_down { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 00cf59cba..7a104a95e 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -605,15 +605,22 @@ pub enum Event { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ImeEvent { /// Notifies when the IME was enabled. + #[deprecated = "No longer used by egui"] Enabled, /// A new IME candidate is being suggested. + /// + /// An empty preedit string indicates that the IME has been dismissed, while + /// a non-empty preedit string indicates that the IME is active. Preedit(String), /// IME composition ended with this final result. + /// + /// The IME is considered dismissed after this event. Commit(String), /// Notifies when the IME was disabled. + #[deprecated = "No longer used by egui"] Disabled, } diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 2d2c74430..ea3ff4eec 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -123,6 +123,9 @@ pub struct PlatformOutput { /// This is set if, and only if, the user is currently editing text. /// /// Useful for IME. + /// + /// This field should only be set by the widget that currently owns IME + /// events (see [`crate::Memory::owns_ime_events`]). pub ime: Option, /// The difference in the widget tree since last frame. diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 08b08a462..34e0fe319 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -116,6 +116,22 @@ pub struct Memory { /// (e.g. relative to some other widget). #[cfg_attr(feature = "persistence", serde(skip))] popups: ViewportIdMap, + + /// When the last IME interruption was made. + #[cfg_attr(feature = "persistence", serde(skip))] + ime_interruption_time: ImeInterruptionTime, +} + +#[derive(Clone, Copy, Debug, Default)] +enum ImeInterruptionTime { + #[default] + None, + + /// The IME was interrupted in the current frame. + ThisFrame, + + /// The IME was interrupted in the previous frame. + LastFrame, } impl Default for Memory { @@ -133,6 +149,7 @@ impl Default for Memory { popups: Default::default(), everything_is_visible: Default::default(), add_fonts: Default::default(), + ime_interruption_time: Default::default(), }; slf.interactions.entry(slf.viewport_id).or_default(); slf.areas.entry(slf.viewport_id).or_default(); @@ -761,6 +778,16 @@ impl Memory { self.areas.entry(self.viewport_id).or_default(); + match self.ime_interruption_time { + ImeInterruptionTime::ThisFrame => { + self.ime_interruption_time = ImeInterruptionTime::LastFrame; + } + ImeInterruptionTime::LastFrame => { + self.ime_interruption_time = ImeInterruptionTime::None; + } + ImeInterruptionTime::None => {} + } + // self.interactions is handled elsewhere self.options.begin_pass(new_raw_input); @@ -875,9 +902,12 @@ impl Memory { /// Give keyboard focus to a specific widget. /// See also [`crate::Response::request_focus`]. + /// + /// Calling this will interrupt IME composition. #[inline(always)] pub fn request_focus(&mut self, id: Id) { self.focus_mut().focused_widget = Some(FocusWidget::new(id)); + self.interrupt_ime(); } /// Surrender keyboard focus for a specific widget. @@ -993,6 +1023,36 @@ impl Memory { pub(crate) fn focus_mut(&mut self) -> &mut Focus { self.focus.entry(self.viewport_id).or_default() } + + /// Check if the widget owns IME events. + /// + /// A widget should only consume IME events if this returns `true`. At most + /// one widget can own IME events for each frame. + pub fn owns_ime_events(&self, id: Id) -> bool { + let Some(focus) = self.focus() else { + return false; + }; + // We check across two frames because the widget that called + // `interrupt_ime` may run after other widgets that call this method + // within the same frame. + if matches!( + self.ime_interruption_time, + ImeInterruptionTime::ThisFrame | ImeInterruptionTime::LastFrame + ) { + return false; + } + focus.focused() == Some(id) + } + + /// Interrupt the current IME composition, if any. + /// + /// This causes [`Self::owns_ime_events`] to return `false` for all widgets + /// for the remainder of this frame and the next frame, giving time + /// for the IME to be dismissed (by making `platform_output.ime` be `None` + /// for at least one frame). + pub fn interrupt_ime(&mut self) { + self.ime_interruption_time = ImeInterruptionTime::ThisFrame; + } } /// State of an open popup. diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 6f4d9a044..9905a2a55 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -10,8 +10,11 @@ use crate::{ TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint, os::OperatingSystem, output::OutputEvent, - response, text_selection, - text_selection::{CCursorRange, text_cursor_state::cursor_rect, visuals::paint_text_selection}, + response, + text_edit::state::TextEditCursorPurpose, + text_selection::{ + self, CCursorRange, text_cursor_state::cursor_rect, visuals::paint_text_selection, + }, vec2, }; @@ -858,33 +861,23 @@ impl TextEdit<'_> { now - state.last_interaction_time, ); } - - // Set IME output (in screen coords) when text is editable and visible - let to_global = ui - .ctx() - .layer_transform_to_global(ui.layer_id()) - .unwrap_or_default(); - - ui.output_mut(|o| { - o.ime = Some(crate::output::IMEOutput { - rect: to_global * inner_rect, - cursor_rect: to_global * primary_cursor_rect, + if ui.memory(|mem| mem.owns_ime_events(id)) { + // Set IME output (in screen coords) when text is editable and visible + let to_global = ui + .ctx() + .layer_transform_to_global(ui.layer_id()) + .unwrap_or_default(); + ui.output_mut(|o| { + o.ime = Some(crate::output::IMEOutput { + rect: to_global * inner_rect, + cursor_rect: to_global * primary_cursor_rect, + }); }); - }); + } } } } - // Ensures correct IME behavior when the text input area gains or loses focus. - if state.ime_enabled && (response.gained_focus() || response.lost_focus()) { - state.ime_enabled = false; - if let Some(mut ccursor_range) = state.cursor.char_range() { - ccursor_range.secondary.index = ccursor_range.primary.index; - state.cursor.set_char_range(Some(ccursor_range)); - } - ui.input_mut(|i| i.events.retain(|e| !matches!(e, Event::Ime(_)))); - } - state.clone().store(ui.ctx(), id); if response.changed() { @@ -999,6 +992,11 @@ fn events( let events = ui.input(|i| i.filtered_events(&event_filter)); + let owns_ime_events = ui.memory(|mem| mem.owns_ime_events(id)); + if !owns_ime_events { + state.cursor_purpose = TextEditCursorPurpose::Selection; + } + for event in &events { let did_mutate_text = match event { // First handle events that only changes the selection cursor, not the text: @@ -1126,7 +1124,7 @@ fn events( .. } => check_for_mutating_key_press(os, &cursor_range, text, galley, modifiers, *key), - Event::Ime(ime_event) => { + Event::Ime(ime_event) if owns_ime_events => { /// Both `ImeEvent::Preedit("")` and `ImeEvent::Commit("")` /// might be emitted from different integrations to signify that /// the current IME composition should be cleared. @@ -1160,46 +1158,58 @@ fn events( } match ime_event { - ImeEvent::Enabled => { - state.ime_enabled = true; - state.ime_cursor_range = cursor_range; + #[expect(deprecated)] + ImeEvent::Enabled | ImeEvent::Disabled => None, + // Ignore `Preedit`/`Commit` events with empty text when + // there is no active IME composition. + // + // Some integrations may emit these events when there is no + // active IME composition (e.g. when `set_ime_allowed` or + // `set_ime_cursor_area` is called on `winit`'s `Window` on + // Wayland). Without this guard, they would clear any + // selected text. + // + // TODO(umajho): Ideally this would be handled by the + // integration, but since this guard is harmless for well- + // behaved integrations and also fixes the issue described + // above, it is good enough for now. + ImeEvent::Preedit(composition_text) | ImeEvent::Commit(composition_text) + if composition_text.is_empty() + && !matches!( + state.cursor_purpose, + TextEditCursorPurpose::ImeComposition + ) => + { + None + } + ImeEvent::Preedit(composition_text) | ImeEvent::Commit(composition_text) + if composition_text == "\n" || composition_text == "\r" => + { None } ImeEvent::Preedit(preedit_text) => { - if preedit_text == "\n" || preedit_text == "\r" { - None + state.cursor_purpose = if preedit_text.is_empty() { + TextEditCursorPurpose::Selection } else { - let mut ccursor = clear_preedit_text(text, &cursor_range); + TextEditCursorPurpose::ImeComposition + }; + let mut ccursor = clear_preedit_text(text, &cursor_range); - let start_cursor = ccursor; - if !preedit_text.is_empty() { - text.insert_text_at(&mut ccursor, preedit_text, char_limit); - } - state.ime_cursor_range = cursor_range; - Some(CCursorRange::two(start_cursor, ccursor)) + let start_cursor = ccursor; + if !preedit_text.is_empty() { + text.insert_text_at(&mut ccursor, preedit_text, char_limit); } + Some(CCursorRange::two(start_cursor, ccursor)) } ImeEvent::Commit(commit_text) => { - if commit_text == "\n" || commit_text == "\r" { - None - } else { - state.ime_enabled = false; + state.cursor_purpose = TextEditCursorPurpose::Selection; + let mut ccursor = clear_preedit_text(text, &cursor_range); - let mut ccursor = clear_preedit_text(text, &cursor_range); - - if !commit_text.is_empty() - && cursor_range.secondary.index - == state.ime_cursor_range.secondary.index - { - text.insert_text_at(&mut ccursor, commit_text, char_limit); - } - - Some(CCursorRange::one(ccursor)) + if !commit_text.is_empty() { + text.insert_text_at(&mut ccursor, commit_text, char_limit); } - } - ImeEvent::Disabled => { - state.ime_enabled = false; - None + + Some(CCursorRange::one(ccursor)) } } } diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index 5827aac4b..48935c4f7 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -37,18 +37,14 @@ pub struct TextEditState { /// Controls the text selection. pub cursor: TextCursorState, + /// The purpose of the cursor. + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) cursor_purpose: TextEditCursorPurpose, + /// Wrapped in Arc for cheaper clones. #[cfg_attr(feature = "serde", serde(skip))] pub(crate) undoer: Arc>, - // If IME candidate window is shown on this text edit. - #[cfg_attr(feature = "serde", serde(skip))] - pub(crate) ime_enabled: bool, - - // cursor range for IME candidate. - #[cfg_attr(feature = "serde", serde(skip))] - pub(crate) ime_cursor_range: CCursorRange, - // Text offset within the widget area. // Used for sensing and singleline text clipping. #[cfg_attr(feature = "serde", serde(skip))] @@ -82,3 +78,13 @@ impl TextEditState { self.set_undoer(TextEditUndoer::default()); } } + +#[derive(Clone, Default)] +pub(crate) enum TextEditCursorPurpose { + /// The cursor is used for text selection. + #[default] + Selection, + + /// The cursor is used for IME composition. + ImeComposition, +} From 16cad760a51497bb14202c4f61ca49e418f4f895 Mon Sep 17 00:00:00 2001 From: Gautier Cailly <109429289+gcailly@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:25:04 +0200 Subject: [PATCH 08/84] Integrate harfrust for text shaping (#8031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Related to #56 (Improve text — tracking issue) ## Summary This PR integrates [harfrust](https://crates.io/crates/harfrust) (a pure-Rust port of HarfBuzz) into epaint's text layout pipeline, replacing the character-by-character glyph positioning with proper OpenType text shaping. ### What this enables - **GPOS kerning**: most modern fonts only ship kerning in GPOS tables (not the legacy `kern` table). Pairs like "AV", "VA", "AT" are now properly tightened. - **GSUB substitutions**: ligatures (fi, fl), contextual alternates, and other OpenType features. - **Combining marks**: diacritics (e.g. ɔ̃) are positioned via anchor tables instead of being rendered as standalone replacement glyphs. ### Before/After #### Kerning, etc. before_main after_harfrust #### Ligatures before_closeup after_closeup ### Architecture The shaping integrates into the existing pipeline without changing the public API: 1. **`Font::segment_into_runs`** — segments text into contiguous runs by font face (grapheme-cluster aware, never splits combining sequences) 2. **`FontFace::shape_text`** — calls harfrust to shape each run, returning glyph IDs + positioned advances/offsets 3. **`layout_shaped_run`** — emits `Glyph` structs from the shaping output, with NOTDEF fallback to other font faces for missing glyphs 4. **Buffer recycling** — `FontsImpl` pools a `harfrust::UnicodeBuffer` to avoid per-layout allocations ### Disclaimer I'm far from being a good Rust programmer. Claude Code did most of the heavy lifting here. I did my best and used my limited knowledge to avoid making too many mistakes. If this PR isn't up to quality standards, please don't hesitate to close it. ## Test plan - [x] `cargo test -p epaint` — all 18 text tests pass, including 6 new ones - [x] `cargo clippy -p epaint --all-features` — clean - [x] `cargo fmt` — clean - [ ] Snapshot tests need regeneration (expected: shaping changes glyph positions) - New tests added: - `test_gpos_kerning` — verifies GPOS kerning tightens "AV", "VA", "AT" pairs - `test_combining_diacritics` — combining tilde doesn't add extra width - `test_shaping_basic_latin` — sanity check for Latin text - `test_shaping_empty_string` — empty input doesn't panic - `test_shaping_multiple_newlines` — newline splitting works correctly - `test_shaping_mixed_font_fallback` — Latin + emoji in same string --------- Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 62 +- Cargo.toml | 4 +- .../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/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 +- .../tests/snapshots/tessellation_test.png | 3 - .../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_0.png | 4 +- .../tests/snapshots/text_selection_1.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/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 +- .../snapshots/should_wait_for_images.png | 4 +- .../tests/snapshots/test_masking.png | 4 +- .../tests/snapshots/test_scroll_initial.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 | 3 + crates/epaint/src/text/font.rs | 145 ++-- crates/epaint/src/text/fonts.rs | 14 + crates/epaint/src/text/text_layout.rs | 665 +++++++++++++++--- .../tests/snapshots/atom_letter_spacing.png | 4 +- .../tests/snapshots/button_shortcut.png | 4 +- tests/egui_tests/tests/snapshots/grow_all.png | 4 +- ...orizontal_wrapped_multiline_row_height.png | 4 +- ...wrapped_multiline_row_height_reference.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 +- .../layout/text_edit_prefix_suffix.png | 4 +- .../egui_tests/tests/snapshots/max_width.png | 4 +- .../tests/snapshots/max_width_and_grow.png | 4 +- .../tests/snapshots/rotated_ellipse.png | 4 +- .../tests/snapshots/rotated_rect.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 +- .../snapshots/sides/shrink_left_long.png | 4 +- .../sides/shrink_left_long_fit_contents.png | 4 +- .../snapshots/sides/shrink_right_long.png | 4 +- .../sides/shrink_right_long_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_left_long.png | 4 +- .../sides/wrap_left_long_fit_contents.png | 4 +- .../tests/snapshots/sides/wrap_right_long.png | 4 +- .../sides/wrap_right_long_fit_contents.png | 4 +- .../tests/snapshots/size_max_size.png | 4 +- .../snapshots/text_edit_delay_0_empty.png | 4 +- .../text_edit_delay_1_h_invisible.png | 4 +- .../tests/snapshots/text_edit_halign.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 +- .../visuals/text_edit_prefix_suffix.png | 4 +- 158 files changed, 998 insertions(+), 502 deletions(-) delete mode 100644 crates/egui_demo_lib/tests/snapshots/tessellation_test.png diff --git a/Cargo.lock b/Cargo.lock index 5814d9a85..58817670a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,6 +1540,7 @@ dependencies = [ "emath", "epaint_default_fonts", "font-types", + "harfrust", "log", "mimalloc", "nohash-hasher", @@ -1551,6 +1552,8 @@ dependencies = [ "similar-asserts", "skrifa", "smallvec", + "unicode-general-category", + "unicode-segmentation", "vello_cpu", ] @@ -1658,12 +1661,9 @@ dependencies = [ [[package]] name = "fearless_simd" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903" -dependencies = [ - "bytemuck", -] +checksum = "76258897e51fd156ee03b6246ea53f3e0eb395d0b327e9961c4fc4c8b2fa151a" [[package]] name = "file_dialog" @@ -2018,6 +2018,15 @@ dependencies = [ "bitflags 2.9.4", ] +[[package]] +name = "guillotiere" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b17e70c989c36bad147b27a58d148c0741c51448aa5653436547323e524d0ab" +dependencies = [ + "euclid", +] + [[package]] name = "half" version = "2.6.0" @@ -2029,6 +2038,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "core_maths", + "read-fonts", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -3732,6 +3754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" dependencies = [ "bytemuck", + "core_maths", "font-types", ] @@ -4732,6 +4755,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -4899,28 +4928,41 @@ dependencies = [ ] [[package]] -name = "vello_common" -version = "0.0.6" +name = "vello_api" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd1a4c633ce09e7d713df1a6e036644a125e15e0c169cfb5180ddf5836ca04b" +checksum = "a5088cd0113bc5332c753f24503825e3bc93e26c7883c9dc3ad9637bb62c4634" +dependencies = [ + "bytemuck", + "peniko", +] + +[[package]] +name = "vello_common" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986dc49a501a683477614bf07b8e7b6c79ae4828efce3bf22e51850f4a0a8a4c" dependencies = [ "bytemuck", "fearless_simd", + "guillotiere", "hashbrown 0.16.1", "log", "peniko", "skrifa", "smallvec", + "thiserror 2.0.18", ] [[package]] name = "vello_cpu" -version = "0.0.6" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162bfe48aabf6a9fdcd401b628c7d9f260c2cbabb343c70a65feba6f7849edc" +checksum = "a678f91c7524a3a9ac9a19df9f83552866ec70b2ca26441b916a6b219b6aa2de" dependencies = [ "bytemuck", "hashbrown 0.16.1", + "vello_api", "vello_common", ] diff --git a/Cargo.toml b/Cargo.toml index d589ef954..2f5d5cf50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ font-types = { version = "0.11.0", default-features = false, features = ["std"] glow = "0.17.0" glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } +harfrust = "0.5.2" home = "0.5.9" image = { version = "0.25.6", default-features = false } jiff = { version = "0.2.23", default-features = false } @@ -136,8 +137,9 @@ tokio = "1.49" toml = {version = "1.0.0", default-features = false } type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } +unicode-general-category = "1.1.0" unicode-segmentation = "1.12.0" -vello_cpu = { version = "0.0.6", default-features = false, features = ["std", "u8_pipeline", "f32_pipeline"] } +vello_cpu = { version = "0.0.7", default-features = false, features = ["std", "u8_pipeline", "f32_pipeline"] } wasm-bindgen = "0.2.108" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml. Don't update this spuriously, because of https://github.com/rerun-io/rerun/issues/8766 wasm-bindgen-futures = "0.4.58" wayland-cursor = { version = "0.31.11", default-features = false } diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 0c5228a26..347bdd568 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:63021012cccfca02d09aa424333453140ae4da3ae58fa32b422f6152ba25741c -size 335394 +oid sha256:288e11a1fa684575155826a760d5aecc5855e1f4b68bc8954441bf3ac015ee84 +size 335175 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index e14066758..6778d8b92 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:4470063fe210d2e5170d6609c2603fff1984b8ee76fb65a1f60a1c4cfdf46ce8 -size 92796 +oid sha256:d674918c635bfc865043f2123c0f5d4a671dd21ba7b878c056e817b19f2e8f00 +size 92770 diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index 5118065c4..f8237d940 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:84f0e72ce337d56f3767ebed1ab6a47f3d27c9fbcce4d8a19aeab358e12920f5 -size 169664 +oid sha256:fa67354cfe4d9cb8774c8de614be5975853a4a817ceaf96c3ec7a968de638d8d +size 169740 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index ae238b029..c47f260e9 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:6030f2f3da3dbbdf8bf3eaf429f222acffb624c7696b654d8b6e64273d49be58 -size 99008 +oid sha256:b9f5204a9b8f15e0f144e66f0df8685e4e3ed90cd265474f2600fdd4cb5df390 +size 98934 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 71bed3294..96d7706ee 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:bff16d453b960bb9abcdd9f72dab73f8f25da6339a0e1c310ed352f57080db93 -size 32426 +oid sha256:10d64e017d1d0eba736a4471d28b1602a0cb69d8e2ab53f4ee604b01c9343116 +size 32475 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 f987b948d..c9a05a629 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:24f4a9745c60c0353ece5f8fc48200671dcb185f4f0b964bbe66bf4a2fe71d7a -size 27067 +oid sha256:ab0d730ce0cf5f1d79947601def4f60c0a015e23a5dcd780df65c7ddc7ae7156 +size 27194 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 f4b7690fc..6134dfad9 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:75a9cd9a3315b236c23a53e890de1a821d39c3327813d06df85ba86d2ed50cc7 -size 26887 +oid sha256:6fccbda741a056ae30a7bfd7497f7b0adfb337bdb885d247fbdfef43cc24b54c +size 26948 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 8c6077a1c..d43e856aa 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:b849adf0ff9a06e1f7bfa22ef32b13224c7e62429cf675286110729156434d12 -size 76531 +oid sha256:26d247655398bae33c724ad3c3bdcab330d194093b07442708d5069e256f636b +size 76542 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 d4c9508f9..60d3993ef 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:d4e33c7f817100d8414bba245ee7886354b86109f383d59e87a197e39501f0a0 -size 62604 +oid sha256:e3389491f9f5c54cfc2e295abe76ad53f223c31e9489e1d07a8323cf14fcf37d +size 62628 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 40153611c..c59717683 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:93fcc271831167cb077f3de0a9f0e27037f9e5a2ce94e056bd6f1ede9890cb7e -size 27818 +oid sha256:6e42e801758bb9e6130a4e94bd0857d6f254e68a7dedebec5af5cb7f7d896068 +size 27822 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 049d585ec..f3c65337f 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:d194db1705b36c1ee6189878a323a591555493c9319b7c2ddfc0cb0541055aca -size 20948 +oid sha256:41e68fd3a679e2a6e3dd81129de5aa1ded3a8f24cc7b1f2ba0b876240d309d9b +size 21019 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 1440d2be2..6a050e0e8 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:62af1b6d248b731b0930c51e481118ae01a5d8c08f27697ff121a47fefea83bf -size 10795 +oid sha256:3db502ec416b322e0f98e9737faa52d5d2fd308d47649710fffa0b0bc5996f52 +size 10783 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 ac85845f9..b187b4cfd 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:deff441cd1d9142352f8759dff4b759f4572f0ddf93752349314da77abe4b254 -size 115028 +oid sha256:c39ae3420fe01d696a032d8d052c405c5623a9208fc673f5cf09188b1e6a539b +size 115447 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index b8791ed2e..658cb0922 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:20ea4f93ee50c7a3585aef74c66d7700083ac1c16519b0704b70387849d9d2bc -size 25057 +oid sha256:19322248e8335301b2ce31fc1f1352993a374dda945d227784fecea2ee831761 +size 25088 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 b8f0440b6..f6f9a3cda 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:1b72a4c0e6d441190a7a156b8bba709e81b6c1fe7b0eacedc1ee7a3bfcf881f6 -size 99297 +oid sha256:98f8865d866a6f28ae3e3a16c815770ed691a031b1d06f7d3662a7e94f564606 +size 99318 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index a965901d5..ab7de6c2d 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:08c40934d4bd2a239bdcc1928d1e5eba56bac03fdded2c85cf47b020d669f07e -size 18281 +oid sha256:39208c3c2c95f68fb37880b011f866bedd8dbccee33d163a725cb2a5cc6bb1b3 +size 18290 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 abd7c485b..b899d7356 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:82878e4150e38fdc4b2e78203c8c661c2d9e716ab32595c298392faf6ba96105 -size 113803 +oid sha256:d251ffd6c34e4ffa7b5de7fe5ae57a2d8f48ba7c2e03711da2c3f1a3fd84648c +size 113998 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 46a949571..5b3a63aeb 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:72d49d9a35e16e7413158ba14ce9cab762925f5f5e52fbbe16292de499a177f1 -size 25791 +oid sha256:4d959e17ee5c8a32534ab98b6f08db884b2fbf25476d8dc2dc90edc79bd87ea4 +size 25821 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 8c3634222..85c205be0 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:bb7e3887cf12bd00ea09b297ac361562a961f64c15b28360bc87f72f270a4065 -size 51649 +oid sha256:c923f523cc77d678929f294b360f60b9f546ddec66d5317ad0eb44bd61a5f927 +size 51733 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 d01e4bb7b..b7d365f3a 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:58cd3aba4392332a45f57c7dd90a9b5da386cb396c0c6319e7a7dae71e03ff30 -size 22563 +oid sha256:c9c98d7bfa08e22e217dd9e7031cc49e4b4486f1a9fdd223bd122be07af72365 +size 22550 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 0f50709d3..a4a82635b 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:26ffcf6b71108b82ce15d4cf3f9dd0ce9fe0b9563f02725fef1b74f40e749439 -size 47281 +oid sha256:68afa93605427e12fe527f3ca9613095664b4983f1f585a60f14bc2370c0a1f2 +size 47224 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 1ba4655fd..b0cb0c27b 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:faedf9631149e231d510165215c24fccec50502d58000d5f893aa047a637a68f -size 23148 +oid sha256:33959851f1bd2386fcca8fb5700133d14d90db5a8f783fbfaa9f3aebb7b0d5b2 +size 23119 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 f34ac0cd4..28e6d1845 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:b6b4c2e55c02fa4caf5f9f8bd2d8c0311cc4cbcf1fc2f568fe112e8e6125c675 -size 65308 +oid sha256:7b918565d66594fe53ed62bb14e357d3489335f3d037ccb088f198d944eb367d +size 65307 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png index 28fe7b683..7f0357df1 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:3a65927cd8bd8d24e3ffbea8eb421eb22849b27dc77d36f8acd82bf5d5e63959 -size 33469 +oid sha256:f8c3c2912a3a11892e65f94792c77c79400a81d8c913109d39e8ce12f5b095c6 +size 33503 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 4e2c0a458..59609d76a 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:115398e2b4b459afc2f1c49c4e60ef3f63e562650f05581372002c93030da632 -size 38374 +oid sha256:0c2c7b0d4913b59ef932f6d6349ddab5cc8619b2e8e9a8b5eb3e055a62e6ed60 +size 38382 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index da06d85bf..4d4b5c355 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:9c595ee9b7ada33780178a6a35e26a98055a707f2ff99f6bb36e8db4ed819791 -size 18242 +oid sha256:9567f56f2dd030608798347e1ab755d95730ba5c5dd1721f1c61147be7216e87 +size 18304 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 70e7ce90e..553f8aa0b 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:a46457b23b7b32694564d03b42bccac2f017a756225bc54b508bb6fe2ad8ee7b -size 249548 +oid sha256:d19d694e6a70ab6acb45668b391751e82f7beab19f9918e23821c667e8cc9cdb +size 249733 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png index 0aab4baf8..6a18dc9e5 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:c218115d305dfa6c9ab883ac6f3a21584b4840b3ba273ea765c8a8381d78935f -size 57181 +oid sha256:9e1c73e28020371429b3e03d540f72dbf886fd40f0ba08bb194e868bbb3c95ff +size 57230 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 4b560e20a..92d19c470 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:fadea24444c402695db6cbc9e03aef8a0ed3c5db487a324fb255d38c14f73dce -size 19804 +oid sha256:c2facd3881e6b107a0dcce9d6e00008a3c9b0f31ad270c35357a87e487180f56 +size 19814 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index 2d57b2074..de52524ac 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:f6105c95470d1342f9003ab03e71243b5e18a6f225261aee94b15f8f0501572c -size 33542 +oid sha256:0ad81eb762150360368a97858ef30bb0d5aff72e71743fa40c3fd4d70ec84cdc +size 33400 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 5517e03ef..14603f2f2 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:f2ce9062c5d1f0b0861d5df49ae64e56ba0e6501e8bd3f8a92c53aea748be78b -size 23629 +oid sha256:c22130891755ac095d73e0494f11ee8e89d0fd0c31a321d3afb969648ece11ef +size 23675 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 0924d3aa9..525f23435 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:927a497e8b6f9ce3b71dcb67086f477e19d327c163b2b8ad868af10009c2faf2 -size 172981 +oid sha256:465f33e53ebe15b776e2d6d0710f13f2453f00bab1f6ef4319b3491f1d1d3a26 +size 173487 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 28864a446..34ec70a65 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:6ffba8bb50b42e47f855f62682f6d5ec10bf67b01d3aa2e843f6bf787f150d0d -size 118562 +oid sha256:001f7a1310ddf37be4d9a7f56a95a3079f713b741824a348544561bb16c291fa +size 118614 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index c28cac211..b9dea21f0 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:9d8d87e4cb944def0dc28e3515243a8c1b07b9b0f88e802924d4381c1cda74db -size 26763 +oid sha256:8105f7c2b519716cc0a45dffd5f08980f53f35c6c6b788592e1d82506cdacccc +size 26665 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 727c01434..909b766b9 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:db4c0cf1c4cdae3d416afce5c58efd1cc382be86431e547fa66bcc95a0a17ddb -size 76364 +oid sha256:a9e83d5e83e3003830f7f719b02dd93273733a9b72d388aa42083387d02c1a20 +size 76310 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 c1b6f506a..b1ed6bcde 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:57bf5220ae8f47485a07e9117abaaad36924d8c6c0f9e278cb05c455f342bff6 -size 70250 +oid sha256:3d46c87417c49c8462fac6c488e46b1482bbc75f70fe8f7af8391f0d5d28dac3 +size 70271 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 e9b302746..61065db1d 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:7c964d07a39ad286a562b53cdfe514d568d91955e6c1ca06a0cb5e45dbe3977e -size 60947 +oid sha256:4c47faa75a002577220a92efceab5c0ccee1ddaa17c2d2f3d6d1429dbf8fe718 +size 60887 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 6b1a9946b..81b49688b 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:f5e9d645e1decf7624bc8f031b5e28213e64fc5f568dd3eeb1768a57fcb988c3 -size 21814 +oid sha256:9c09529c3a1c26c8f28c00fc15cc5f495842862276870c24b5ee0713954f97fc +size 21916 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index e7ed07b04..bea2050ab 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:57018beba5e4fb4f1e6de9c58bf898560b3a7669159d5bad91a4e2382ef57ce0 -size 64004 +oid sha256:9d69b9a12e777ee559a481aec012935a5bfb2ca8b0d48725ecd33e7f0880b2b8 +size 64273 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 92992cd83..728dc59b6 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:99fa5a5cb10c7d277eafb258af6019eda24a3c96075a50db321f52a521dede92 -size 13700 +oid sha256:f6c020860fe9cb7503cea548afbf298ebb9dc620133870b528fe7508d04150c8 +size 13691 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 cdc1a43dd..951a6fd41 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:1cc61413bcce62cc8e0a55460a974bb56ac40936cd2e5512c4a0e0c521eaaae4 -size 35874 +oid sha256:d033935ca18ebee7e3c35629233cc3e3a73766ac8c0627fcdd8a12660eed703c +size 35873 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 c769f61bd..933343f25 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:999f9cc006302b8951d97b510a02f1209969c376ecc7909ed5d7b46da27c0637 -size 483753 +oid sha256:77c9058b036770a644f1bfcf9ed1ba7a29a7c98107b1823474c55ccc2880e9e7 +size 485880 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 e48dfae92..218a4e258 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:9c2990a81dfa8832f0cb1c4c0ce2f86e468a7a6f693e09efffa131ed3259e2e8 -size 15428 +oid sha256:2ef07080ca2aa10e128c646479b8b322a092d63f15b35c52ed59015e7c2a0f60 +size 15434 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 3f9da5333..8da2d19a9 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:07d987ff87c9f41ec71ceea0caff25795bf4dff525ef4ef241d0ba786acee3e1 -size 35960 +oid sha256:4edb61c6619d5e44892fa29da0aa0a624306ae637dbbaa057e3fa47c14dc06bd +size 35988 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 84b808a83..1085f9969 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:f5c5ce0d46231d90ccb04e158947d793d99cd5cce911c72b960b6d04feba2134 -size 16122 +oid sha256:f8da0c2e37497968864a91fa6bef7c545c37791f4f9b788ea9a2f43dd4ac16b1 +size 16116 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 ff848168a..d89daa1c0 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:60dcd590b1d00361278b135ce9ef084c7382875c71c72b19fb6e23dba68f7902 -size 39279 +oid sha256:2a603fcb2eb97943d6be3239b91cacee093adcb55a6dd14af93a72fc8b3a61fa +size 39270 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 5018d53c8..081f36a0e 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:7389e319d9153af313cc113d97b57d462da00feb0d5f99da211552af3ac7e18a -size 6704 +oid sha256:19c9ae55906e0ad18a9507898884ec31ef785c498fb8d10267ee848bd3f17186 +size 6705 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 3a1b72bb3..0bd058217 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:d4480dd34ed36c6bdbc2084843dd136448b3934c22b3df3e40314ba6324b5b39 -size 10306 +oid sha256:6f13116cb76e52620221b41c8dbd37b74a0da513402cbb2cb7e70c89027f5e31 +size 10313 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 0f18eb109..330ecd636 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:624bfa884431c35cc5d852b96653f13da17e60f8545471f9fb1c3bb85b40ffc8 -size 16555 +oid sha256:6869b180f2d1dcfd9fcbb215ed3febfbe548f30035560272a4e11e465f20c592 +size 16567 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 6ebc6addc..86374449e 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:bf665389ef43524e097a7ae4eec0aa01bb788bdbb306144f20f9133f74a64b2c -size 6941 +oid sha256:132f060b2e20b4cc0c55062367381f47856a5cc10310b903dbddc3790f475581 +size 6942 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 54fdc064b..30b722245 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:a2480d0f49a929993de70612572b321457b2507c149a25112064cfc27840e6ee -size 11005 +oid sha256:e5ec63c5e2f5bb5c0048ee67df5b821a30f89ba35dbedf72e7bc91e04a5f8359 +size 11007 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 138fd8c83..ca90da21d 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:1e0013b499934f47370a3a20b3d3a19f8a8c6db360752a35a3fb1d676d122263 -size 18068 +oid sha256:491bb98b22cf56e50d3ed10a10649db15aaffb93d7e045dfecfa26e98f537c35 +size 18070 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index 96d31e11d..d1abde4fa 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:941582e2e20a9459db1f2cb7f07fa1930acfdb12cbbe7f96f9aafbeabf8b37f6 -size 47076 +oid sha256:8bd53b56322123940496700cbd2a73e336fd80eaf49dcb19a958888d66570ddb +size 47159 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index 3c0a88ee0..8f6976ea5 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:2735a021f171f5c95888cda76e8668e1e023588c8c6c7cd382c03d8e31988fe3 -size 48209 +oid sha256:d15954e6183558141e05e1b566ec4a794d372503baf436da2a8c8c67299056f1 +size 48238 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index 28221255f..ab4a99a12 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:867bef6b55b73d127306a461e115b6f0047d582904999de80aeabae00e60c967 -size 44295 +oid sha256:1738ecf660979888c70b1046dd759fe0b082e4958814fb19077c4fed2fb4bdef +size 44338 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 bb1935741..cbac0d342 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:936ec8b223ae7f0f32c640c127e1b6b14033bb7d168a4d1f0e6b3bd08a761e36 -size 44055 +oid sha256:dae7239dc065068147387eb313afdbaa3f0df8b81d060dbbc29f5a6a31ad76c8 +size 44309 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 b48827b6a..ea32cd32f 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:fba7387f5deba5e144e2106154b15ab956a50a418857bd34e16b306d7f1a29e4 -size 588252 +oid sha256:ce6e44aeb60b6cc2179e14c467edc2d570ea1adbce08511650fc733e39f7757c +size 590679 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 d1286d6a1..1bff62c8d 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:4656f3255d7859c07b269ff655eafe21bdddb949a07aa91477b826f6e2af8c28 -size 740616 +oid sha256:2edd6679d434df3407048472aaa95e410158f28e9a13c9bd88e3f8be95b08d9d +size 745771 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 f1892bf26..39c2a6b36 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:b18ff644ba5bd0c7f094bf8eac079d8a72bc6918638b1b110002f2f0a7a362cc -size 967860 +oid sha256:20cb796b59113853345c98e5238021a9c5c977c25689ee9201fdec1e4d98123d +size 973781 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 c32762306..fb0da4a80 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:134caff5b8a4969055c32e8f51ca9c6eae1528b84d348691d860913e839de0d9 -size 1076746 +oid sha256:9658cc6b4dd2dd33356facc6b48cf69f9a86f976ac2dd4f16e362a2fd3f73827 +size 1081864 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 d021a1e71..736c3dd67 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:d731b4ce039315e096113f3c83168165020949e57564e641e778728e35901169 -size 1125286 +oid sha256:b5d5941c8fa0456f8a92fce4888e7a2888945f5df7cfed867ef073c439c7ad9f +size 1131030 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 03d4fb69a..b4bf73cb7 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:cfac3518220555984d47c9fdfea2202a37102250aefcc2509794f337b3a7baae -size 1361407 +oid sha256:78ef6dba3dcf5e26a9289ce226165224c712c22f9b5dfe699b599c7d2531133b +size 1363589 diff --git a/crates/egui_demo_lib/tests/snapshots/tessellation_test.png b/crates/egui_demo_lib/tests/snapshots/tessellation_test.png deleted file mode 100644 index 7c65aef2c..000000000 --- a/crates/egui_demo_lib/tests/snapshots/tessellation_test.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa7d25b097911f6b18308bab56d302e3dae9f8f9916f563d5703632a26eda260 -size 72501 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 1d3f1785e..09763f516 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:cf21fe763e9762bca1b0f486e29a6024efcbc106a7f1ac195104acd0621cf8db -size 45107 +oid sha256:9c0bf76e2a4d60fd4ba302b2217beaa4b0627614ff7d23294c7f5e6b81a028c7 +size 45061 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 646eaa1e0..7254be665 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:2f09338e652b965cc9ae7bbb261845cd9c15d79f3d15f3c5b5326ef6d163b606 -size 86885 +oid sha256:de7db1296bdd127dfd827c6d1cc1a5a840391c3abb558be07faeb7e6612ed6e8 +size 86830 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 e667fc387..85a4fa0da 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:e298244953653e46875053b12b4fe06ee692cb58fc131233ac4172677f0f8b44 -size 118961 +oid sha256:645519e1e4c196b985b6b422ddf1ba51e7e92d1b0c80972318dcf44dab3022d3 +size 118907 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 843c3ba3b..a3c23b831 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:6b9b36acf821cca71f97a3c8468fb925561f3bc2030742aef1e3c1d9e69ccc6f -size 51419 +oid sha256:465a3aa235ea69bce6650cf6e100ffc719bba67baa0cb13d7015a0fc54d53d9a +size 51376 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 e738e22eb..ec1182d15 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:f5ad7a37546d48fc5426c32534a1c452fd0bf8280346dbe6e67ac26f17f3ba8a -size 54626 +oid sha256:aac429243686d35096760eb1b7c6a2bcad1c1d00a3341a858a6c71aaff2cf128 +size 54582 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 d46e593ba..fa5ea442d 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:c0b61e9d1c2bcbf891a7acd4f3c1d2bd7524133d8165e7e7984998670de5a085 -size 55090 +oid sha256:8afb610d9e86b324db22849457eb23419c4f3c240e5ee951a13ce8901003bca8 +size 55053 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 31a1dd365..ec2750465 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:a2e4975e9328a6d72f2c932daddfbb00cebdb2249aceb53f667d4060a1c0ea8a -size 36006 +oid sha256:8e1a958b753fee4bec405a74dd7727e5c477c946484ed086c5c4ffee058dd5e8 +size 35960 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 db20010e0..b725ec08f 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:ac6f9adeef92be9f69cb288ccafda8d522b8c3cde64352cd5369ae63668240c0 -size 35973 +oid sha256:1887d632d0994efec3dd7cecc41e3ad460c109baa1012d69e005cdc916e3cd24 +size 35925 diff --git a/crates/egui_demo_lib/tests/snapshots/text_selection_0.png b/crates/egui_demo_lib/tests/snapshots/text_selection_0.png index 7930dff48..a6d590fdf 100644 --- a/crates/egui_demo_lib/tests/snapshots/text_selection_0.png +++ b/crates/egui_demo_lib/tests/snapshots/text_selection_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:344d90928510855dc718a2e36e31a97f084f1163ab750d0217fb8620469b621a -size 5276 +oid sha256:16e993bb27d32162fcf573cc4d977af775e6e0b94373fc5e8e1a9890453e508c +size 5278 diff --git a/crates/egui_demo_lib/tests/snapshots/text_selection_1.png b/crates/egui_demo_lib/tests/snapshots/text_selection_1.png index 8691211cb..3506845cb 100644 --- a/crates/egui_demo_lib/tests/snapshots/text_selection_1.png +++ b/crates/egui_demo_lib/tests/snapshots/text_selection_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60449af267336663304e44e254d0984e037bebfa2d1efdf32234cab4374e8c79 -size 5301 +oid sha256:79676693e9f1804c7e0a32ffa9bec3bde281446ffc184b55a1a3fcf34074ac34 +size 5304 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 4495bf173..5ea006f1f 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:ef245aae271ccae628bb4171f7e601194c77fd18888ef2ea829bea75bd38b0e5 -size 64965 +oid sha256:8c0054dd1717833ae6f19726b58bffbf70bd4dc784b5f2774b194800a3adffed +size 64973 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 7c47f522d..c37f49163 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:e621561567539ff24b4d22b53b65fac6cddae71d92fccd7800a90972a6de3e0e -size 151100 +oid sha256:9be7321d8184c3d06cfeb54410c1f839892c4f836767e9b1950e70306cde6c54 +size 151090 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 520895ff5..54f2d37fd 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:e6c2d538be7971169bbc4473945e6815eac8c5dd6372bc1f1897a032b6bca12b -size 59962 +oid sha256:f07c25c053e9c4d5f7416cc00e101e13b7007b65c46ba13d0853d95ca43fddc4 +size 59950 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 90311fddc..318fb89e4 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:d705af99624cd2824cd1f520fa05481ac67b8913feebae836db7b99ac60cb466 -size 145841 +oid sha256:b6a9bc30b4d6ed100555ee553554848d20d93f25643e72eeef1826483ed307d9 +size 145940 diff --git a/crates/egui_kittest/tests/snapshots/combobox_closed.png b/crates/egui_kittest/tests/snapshots/combobox_closed.png index 073ae79a3..7df09f4bd 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:00fb02e0cc2c1454d3a3dc0635be24086234c2bc5e2c9fd73741b179622e16d6 -size 4514 +oid sha256:698d1e9ecf490e04fb58ebf81cf9c633be29712abe172a6e6adfdc7fe442732d +size 4517 diff --git a/crates/egui_kittest/tests/snapshots/combobox_opened.png b/crates/egui_kittest/tests/snapshots/combobox_opened.png index 78e1baaca..20dd3e232 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:d8757e2db9a3892d9347495ad59f14d2bd9164a9ba258375a53c9faf8176b597 -size 8016 +oid sha256:84e857521c20ea2458d9f9d724578e0495c06e75571c25104bf64a0be0be617d +size 8028 diff --git a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png index 2a4621b0e..c498163ee 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:38ee4acc23d9c66f127d377ac8a0dd3b683a1465ca319fba092f6d3cdff8c266 -size 11166 +oid sha256:aac8c973f26d03c25155f0454185dc3ab27fffae1c63443e36f07c49fb0828bf +size 11183 diff --git a/crates/egui_kittest/tests/snapshots/menu/opened.png b/crates/egui_kittest/tests/snapshots/menu/opened.png index c698cdb4b..4ef240cc9 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:ac1941f5eab71bfad020132eae47e1995efa17410b7861aa9f260032e5b0472c -size 21785 +oid sha256:8dd0d91d0866848e7965e88223ff3ddae2231728197fbcd3fb5b1a6b034e179d +size 21804 diff --git a/crates/egui_kittest/tests/snapshots/menu/submenu.png b/crates/egui_kittest/tests/snapshots/menu/submenu.png index f277511c1..20c6bed89 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:b1f1a4dd9de1d8405c527c7f8f04b42ed9d403d0ec507bb3ff650a6896f28df0 -size 28628 +oid sha256:11d0081c272986e6fbd00cde05c0931321bc9670d6a0fa3a650323cd7b56d795 +size 28667 diff --git a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png index dfc2b707c..01a316007 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:af05a9b66340e0c128d823d3935a23bcf17cfeac02a822e7277234a9c8eb26e0 -size 33393 +oid sha256:6fc08e05c0ab0e167503324fb7c82777afd5866384ae8bbacb9b933f717feb56 +size 33412 diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index 050a4a43e..55a13d3e4 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:1dd1f5013587463f002b1becac1560876c462295dbe5dfbb1a9dbce58991e53d -size 2209 +oid sha256:57211d5ac8021223ed288b9fd6f651ed901ec91599ed67d54ac540f3a2037937 +size 2205 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 6ceffde99..461a2e695 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:cfc03625c268f0ae067d2f4521a8668b47e4bc8525350d77a480840a09cd5083 -size 2046 +oid sha256:4ae52d6761a4d0b95b38ca4ca34c10751eef1ab2a54a62786c55bee3a3522ba1 +size 2050 diff --git a/crates/egui_kittest/tests/snapshots/test_masking.png b/crates/egui_kittest/tests/snapshots/test_masking.png index 5bf5dd6fa..6473e407e 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:be0bd449166878ced27eff4966d1741731e926f9baabe8b590375c20103036dd -size 5527 +oid sha256:7ddac0bff10e6699c0ab2d9ea3679d5047d6099b7ba3540d1ebc144361f07d86 +size 5517 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png index 64ed153a6..e03db834f 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:6154c8bb550575bcb9fa0bba06da4d47079a00dffc5754b62ef2a6e7529e2090 -size 7489 +oid sha256:59939d3347679ff27c1de500a559cdc0e6387f633c86ec7ce51f2a36cd92547b +size 7511 diff --git a/crates/egui_kittest/tests/snapshots/test_shrink.png b/crates/egui_kittest/tests/snapshots/test_shrink.png index e4ff540f4..de27685a8 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:888f8a4d995d718a9a158e563d8ac1434775660b33aebb5f34feea54ffd12600 -size 2830 +oid sha256:db6f35e7dabe8f6d3766df022e5643f8461c3c2476959682622b793d59bf7c40 +size 2833 diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png b/crates/egui_kittest/tests/snapshots/test_tooltip_hidden.png index e4ff540f4..de27685a8 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:888f8a4d995d718a9a158e563d8ac1434775660b33aebb5f34feea54ffd12600 -size 2830 +oid sha256:db6f35e7dabe8f6d3766df022e5643f8461c3c2476959682622b793d59bf7c40 +size 2833 diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png index d6053700b..7396758ea 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:037f3e356d32e1a2c32767460399f919452bff0933e1db7aa113e7e2bdb083f0 -size 4927 +oid sha256:d785e2073627f33a2e7a23a47e0e0b8ca9570f45d4de3d8b545b473c1a9e75b5 +size 4940 diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 9d6e3eade..705f1f6a9 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -63,6 +63,7 @@ ecolor.workspace = true ahash.workspace = true font-types.workspace = true +harfrust.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. @@ -70,6 +71,8 @@ profiling.workspace = true self_cell.workspace = true skrifa.workspace = true smallvec.workspace = true +unicode-general-category.workspace = true +unicode-segmentation.workspace = true vello_cpu.workspace = true #! ### Optional dependencies diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 61f9f9f2f..48659fbe9 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -2,10 +2,7 @@ use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2}; use self_cell::self_cell; -use skrifa::{ - MetadataProvider as _, - raw::{TableProvider as _, tables::kern::SubtableKind}, -}; +use skrifa::{GlyphId, MetadataProvider as _}; use std::collections::BTreeMap; use vello_cpu::{color, kurbo}; @@ -44,12 +41,10 @@ impl UvRect { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GlyphInfo { - /// Used for pair-kerning. - /// /// Doesn't need to be unique. /// /// Is `None` for a special "invisible" glyph. - pub(crate) id: Option, + pub(crate) id: Option, /// In [`skrifa`]s "unscaled" coordinate system. pub advance_width_unscaled: OrderedFloat, @@ -124,17 +119,8 @@ impl SubpixelBin { } } -#[derive(Clone, Copy, Debug, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct GlyphAllocation { - /// Used for pair-kerning. - /// - /// Doesn't need to be unique. - /// 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, - /// UV rectangle for drawing. pub uv_rect: UvRect, } @@ -145,7 +131,7 @@ struct GlyphCacheKey(u64); impl nohash_hasher::IsEnabled for GlyphCacheKey {} impl GlyphCacheKey { - fn new(glyph_id: skrifa::GlyphId, metrics: &StyledMetrics, bin: SubpixelBin) -> Self { + fn new(glyph_id: GlyphId, metrics: &StyledMetrics, bin: SubpixelBin) -> Self { let StyledMetrics { pixels_per_point, px_scale_factor, @@ -198,12 +184,10 @@ impl FontCell { &mut self, atlas: &mut TextureAtlas, metrics: &StyledMetrics, - glyph_info: &GlyphInfo, + glyph_id: GlyphId, bin: SubpixelBin, location: skrifa::instance::LocationRef<'_>, ) -> Option { - let glyph_id = glyph_info.id?; - debug_assert!( glyph_id != skrifa::GlyphId::NOTDEF, "Can't allocate glyph for id 0" @@ -288,11 +272,7 @@ impl FontCell { } }; - Some(GlyphAllocation { - id: glyph_id, - advance_width_px: glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor, - uv_rect, - }) + Some(GlyphAllocation { uv_rect }) } } @@ -337,6 +317,10 @@ pub struct FontFace { font: FontCell, tweak: FontTweak, + /// Cached `harfrust` shaper data (parsed GSUB/GPOS tables). + /// `ShaperData` is `Copy` — lives outside the `self_cell`. + shaper_data: harfrust::ShaperData, + glyph_info_cache: ahash::HashMap, glyph_alloc_cache: ahash::HashMap, } @@ -393,10 +377,13 @@ impl FontFace { }) })?; + let shaper_data = harfrust::ShaperData::new(&font.borrow_dependent().skrifa); + Ok(Self { name, font, tweak, + shaper_data, glyph_info_cache: Default::default(), glyph_alloc_cache: Default::default(), }) @@ -483,7 +470,7 @@ impl FontFace { let glyph_id = font_data .charmap .map(c) - .filter(|id| *id != skrifa::GlyphId::NOTDEF)?; + .filter(|id| *id != GlyphId::NOTDEF)?; let glyph_info = GlyphInfo { id: Some(glyph_id), @@ -497,38 +484,6 @@ impl FontFace { Some(glyph_info) } - #[inline] - pub(super) fn pair_kerning_pixels( - &self, - metrics: &StyledMetrics, - last_glyph_id: skrifa::GlyphId, - glyph_id: skrifa::GlyphId, - ) -> f32 { - 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: &StyledMetrics, - 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 styled_metrics( &self, @@ -571,51 +526,67 @@ impl FontFace { } } + pub(crate) fn skrifa_font_ref(&self) -> &skrifa::FontRef<'_> { + &self.font.borrow_dependent().skrifa + } + + pub(crate) fn tweak(&self) -> &FontTweak { + &self.tweak + } + + pub(crate) fn shaper_data(&self) -> &harfrust::ShaperData { + &self.shaper_data + } + pub fn allocate_glyph( &mut self, atlas: &mut TextureAtlas, metrics: &StyledMetrics, - glyph_info: GlyphInfo, - chr: char, - h_pos: f32, + shaped: &ShapedGlyph, ) -> (GlyphAllocation, i32) { - let advance_width_px = glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor; + let ShapedGlyph { + glyph_id, + h_pos, + is_cjk, + } = *shaped; - let Some(glyph_id) = glyph_info.id else { - // Invisible. - return (GlyphAllocation::default(), h_pos as i32); - }; + if glyph_id == GlyphId::NOTDEF { + // invisible + return (GlyphAllocation::default(), h_pos.round() as i32); + } - // CJK scripts contain a lot of characters and could hog the glyph atlas if we stored 4 subpixel offsets per - // glyph. - let (h_pos_round, bin) = if is_cjk(chr) { + let (h_pos_round, bin) = if is_cjk { + // CJK scripts contain a lot of characters and could hog the glyph atlas + // if we stored 4 subpixel offsets per glyph. (h_pos.round() as i32, SubpixelBin::Zero) } else { SubpixelBin::new(h_pos) }; - let entry = match self - .glyph_alloc_cache - .entry(GlyphCacheKey::new(glyph_id, metrics, bin)) - { - std::collections::hash_map::Entry::Occupied(glyph_alloc) => { - let mut glyph_alloc = *glyph_alloc.get(); - glyph_alloc.advance_width_px = advance_width_px; // Hack to get `\t` and thin space to work, since they use the same glyph id as ` ` (space). - return (glyph_alloc, h_pos_round); - } - std::collections::hash_map::Entry::Vacant(entry) => entry, - }; + let cache_key = GlyphCacheKey::new(glyph_id, metrics, bin); - let allocation = self - .font - .allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, (&metrics.location).into()) - .unwrap_or_default(); + let alloc = *self.glyph_alloc_cache.entry(cache_key).or_insert_with(|| { + self.font + .allocate_glyph_uncached(atlas, metrics, glyph_id, bin, (&metrics.location).into()) + .unwrap_or_default() + }); - entry.insert(allocation); - (allocation, h_pos_round) + (alloc, h_pos_round) } } +/// Positioning info for a single glyph, ready for atlas allocation. +#[derive(Clone, Copy, Debug)] +pub(crate) struct ShapedGlyph { + pub glyph_id: GlyphId, + + /// Horizontal position of the glyph origin, in physical pixels. + pub h_pos: f32, + + /// CJK glyphs skip subpixel positioning to save atlas space. + pub is_cjk: bool, +} + // TODO(emilk): rename? /// Wrapper over multiple [`FontFace`] (e.g. a primary + fallbacks for emojis) pub struct Font<'a> { diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 5099e0085..135ddb6d5 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -765,6 +765,9 @@ pub struct FontsImpl { fonts_by_id: nohash_hasher::IntMap, fonts_by_name: ahash::HashMap, family_cache: ahash::HashMap, + + /// Recycled `harfrust` shaping buffer to avoid per-layout allocations. + shape_buffer: Option, } impl FontsImpl { @@ -798,6 +801,7 @@ impl FontsImpl { fonts_by_id, fonts_by_name, family_cache: Default::default(), + shape_buffer: Some(harfrust::UnicodeBuffer::new()), } } @@ -805,6 +809,16 @@ impl FontsImpl { self.atlas.options() } + /// Take the recycled shaping buffer (or create a new one if already taken). + pub fn take_shape_buffer(&mut self) -> harfrust::UnicodeBuffer { + self.shape_buffer.take().unwrap_or_default() + } + + /// Return a shaping buffer for reuse. + pub fn return_shape_buffer(&mut self, buffer: harfrust::UnicodeBuffer) { + self.shape_buffer = Some(buffer); + } + /// 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(|| { diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 0233c1c58..0f3089292 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -8,15 +8,35 @@ use crate::{ Color32, Mesh, Stroke, Vertex, stroke::PathStroke, text::{ - font::{StyledMetrics, is_cjk, is_cjk_break_allowed}, + TAB_SIZE, + font::{StyledMetrics, UvRect, is_cjk, is_cjk_break_allowed}, fonts::FontFaceKey, }, }; -use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals}; +use super::{ + FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals, + VariationCoords, + font::{Font, FontFace, ShapedGlyph}, +}; // ---------------------------------------------------------------------------- +/// Returns `true` if the character is a Unicode combining mark (categories Mn, Mc, Me). +/// +/// These characters modify the preceding base character and should not be +/// rendered as standalone replacement glyphs when the shaper can't handle them. +#[inline] +fn is_combining_mark(c: char) -> bool { + use unicode_general_category::{GeneralCategory, get_general_category}; + matches!( + get_general_category(c), + GeneralCategory::NonspacingMark + | GeneralCategory::SpacingMark + | GeneralCategory::EnclosingMark + ) +} + /// Represents GUI scale and convenience methods for rounding to pixels. #[derive(Clone, Copy)] struct PointScale { @@ -98,15 +118,21 @@ pub fn layout(fonts: &mut FontsImpl, pixels_per_point: f32, job: Arc) // For most of this we ignore the y coordinate: let mut paragraphs = vec![Paragraph::from_section_index(0)]; - for (section_index, section) in job.sections.iter().enumerate() { - layout_section( - fonts, - pixels_per_point, - &job, - section_index as u32, - section, - &mut paragraphs, - ); + { + let mut shape_buffer = fonts.take_shape_buffer(); + for (section_index, section) in job.sections.iter().enumerate() { + let mut font = fonts.font(§ion.format.font_id.family); + shape_buffer = layout_section( + &mut font, + shape_buffer, + pixels_per_point, + &job, + section_index as u32, + section, + &mut paragraphs, + ); + } + fonts.return_shape_buffer(shape_buffer); } let point_scale = PointScale::new(pixels_per_point); @@ -144,21 +170,198 @@ pub fn layout(fonts: &mut FontsImpl, pixels_per_point: f32, job: Arc) galley_from_rows(point_scale, job, rows, elided, intrinsic_size) } +/// Shared context for emitting shaped glyphs into a [`Paragraph`]. +struct ShapingContext { + pixels_per_point: f32, + font_size: f32, + line_height: f32, + extra_letter_spacing: f32, + section_index: u32, + font_metrics: StyledMetrics, + is_first_glyph_in_section: bool, + prev_cluster: Option, +} + +impl ShapingContext { + fn glyph( + &self, + chr: char, + physical_x: i32, + advance_width_px: f32, + face_metrics: &StyledMetrics, + uv_rect: UvRect, + ) -> Glyph { + Glyph { + chr, + pos: pos2(physical_x as f32 / self.pixels_per_point, f32::NAN), + advance_width: advance_width_px / self.pixels_per_point, + line_height: self.line_height, + font_face_height: face_metrics.row_height, + font_face_ascent: face_metrics.ascent, + font_height: self.font_metrics.row_height, + font_ascent: self.font_metrics.ascent, + uv_rect, + section_index: self.section_index, + first_vertex: 0, + } + } +} + +/// Produced by [`segment_into_runs`] for text shaping. +#[derive(Debug)] +struct TextRun { + /// Which font face should shape this run. + font_key: FontFaceKey, + + /// Byte range within the section text. + byte_range: std::ops::Range, +} + +/// Emit shaped glyphs from a [`harfrust::GlyphBuffer`] into a [`Paragraph`]. +fn layout_shaped_run( + font: &mut Font<'_>, + run: &TextRun, + run_text: &str, + glyph_buffer: &harfrust::GlyphBuffer, + face_metrics: &StyledMetrics, + ctx: &mut ShapingContext, + paragraph: &mut Paragraph, +) { + let px_scale = face_metrics.px_scale_factor; + + // Reset cluster tracking — cluster values are byte offsets within run_text, + // so they are not comparable across runs. + ctx.prev_cluster = None; + + for (info, pos) in glyph_buffer + .glyph_infos() + .iter() + .zip(glyph_buffer.glyph_positions()) + { + let glyph_id = skrifa::GlyphId::new(info.glyph_id); + let cluster = info.cluster; + let mut advance_width_px = pos.x_advance as f32 * px_scale; + let x_offset_px = pos.x_offset as f32 * px_scale; + let y_offset_px = -(pos.y_offset as f32 * px_scale); // harfrust Y+ up → screen Y+ down + + let chr = run_text + .get(cluster as usize..) + .and_then(|s| s.chars().next()) + .unwrap_or('\u{FFFD}'); // Unicode Replacement Character + + // Tab is a layout concept, not a glyph — the shaper doesn't know about tab stops. + // Override the advance width to TAB_SIZE × space width. + if chr == '\t' { + let (_, space_info) = font.glyph_info(' '); + let space_width_px = space_info.advance_width_unscaled.0 * px_scale; + advance_width_px = TAB_SIZE as f32 * space_width_px; + } + + // Apply extra_letter_spacing only at cluster boundaries, + // never between glyphs within the same cluster (e.g. base + mark). + let is_new_cluster = ctx.prev_cluster.is_none_or(|pc| pc != cluster); + if !ctx.is_first_glyph_in_section && is_new_cluster { + paragraph.cursor_x_px += ctx.extra_letter_spacing * ctx.pixels_per_point; + } + if is_new_cluster { + ctx.is_first_glyph_in_section = false; + } + ctx.prev_cluster = Some(cluster); + + let glyph = if glyph_id == skrifa::GlyphId::NOTDEF { + // The shaper couldn't map this character. Drop combining marks + // (Unicode category M) and duplicate NOTDEF glyphs within the same + // cluster — only the first base character gets a replacement glyph. + if is_combining_mark(chr) || !is_new_cluster { + continue; + } + + // Use the fallback font face (not run.font_key which returned NOTDEF). + let (fallback_key, glyph_info) = font.glyph_info(chr); + let fallback_metrics = font + .fonts_by_id + .get(&fallback_key) + .map(|ff| { + ff.styled_metrics(ctx.pixels_per_point, ctx.font_size, &Default::default()) + }) + .unwrap_or_default(); + let advance_width_px = + glyph_info.advance_width_unscaled.0 * fallback_metrics.px_scale_factor; + let (glyph_alloc, physical_x) = + if let Some(ff) = font.fonts_by_id.get_mut(&fallback_key) { + ff.allocate_glyph( + font.atlas, + &fallback_metrics, + &ShapedGlyph { + glyph_id: glyph_info.id.unwrap_or(skrifa::GlyphId::NOTDEF), + h_pos: paragraph.cursor_x_px, + is_cjk: is_cjk(chr), + }, + ) + } else { + Default::default() + }; + + paragraph.cursor_x_px += advance_width_px; + + ctx.glyph( + chr, + physical_x, + advance_width_px, + &fallback_metrics, + glyph_alloc.uv_rect, + ) + } else { + let (mut glyph_alloc, physical_x) = + if let Some(ff) = font.fonts_by_id.get_mut(&run.font_key) { + ff.allocate_glyph( + font.atlas, + face_metrics, + &ShapedGlyph { + glyph_id, + h_pos: paragraph.cursor_x_px + x_offset_px, + is_cjk: is_cjk(chr), + }, + ) + } else { + Default::default() + }; + + // Apply shaper y_offset — this varies per glyph instance so it + // is not part of the cached ShapedGlyph / GlyphAllocation. + glyph_alloc.uv_rect.offset.y += y_offset_px / ctx.pixels_per_point; + + paragraph.cursor_x_px += advance_width_px; + + ctx.glyph( + chr, + physical_x, + advance_width_px, + face_metrics, + glyph_alloc.uv_rect, + ) + }; + paragraph.glyphs.push(glyph); + } +} + // Ignores the Y coordinate. +#[must_use] fn layout_section( - fonts: &mut FontsImpl, + font: &mut Font<'_>, + mut shape_buffer: harfrust::UnicodeBuffer, pixels_per_point: f32, job: &LayoutJob, section_index: u32, section: &LayoutSection, out_paragraphs: &mut Vec, -) { +) -> harfrust::UnicodeBuffer { let LayoutSection { leading_space, byte_range, format, } = section; - let mut font = fonts.font(&format.font_id.family); + let font_size = format.font_id.size; let font_metrics = font.styled_metrics(pixels_per_point, font_size, &format.coords); let line_height = section @@ -169,76 +372,100 @@ fn layout_section( let mut paragraph = out_paragraphs.last_mut().unwrap(); if paragraph.glyphs.is_empty() { - paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? + paragraph.empty_paragraph_height = line_height; } - paragraph.cursor_x_px += leading_space * pixels_per_point; - let mut last_glyph_id = None; + let section_text = &job.text[byte_range.clone()]; + let mut ctx = ShapingContext { + pixels_per_point, + font_size, + line_height, + extra_letter_spacing, + section_index, + font_metrics, + is_first_glyph_in_section: paragraph.glyphs.is_empty(), + prev_cluster: None, + }; + let mut runs = Vec::new(); - // Optimization: only recompute `ScaledMetrics` when the concrete `FontImpl` changes. - let mut current_font = FontFaceKey::INVALID; - let mut current_font_face_metrics = StyledMetrics::default(); - - for chr in job.text[byte_range.clone()].chars() { - if job.break_on_newline && chr == '\n' { + // Process each paragraph segment (split on newlines — the shaper can't handle them). + for (seg_idx, segment) in SplitOrWhole::new(section_text, job.break_on_newline).enumerate() { + if 0 < seg_idx { out_paragraphs.push(Paragraph::from_section_index(section_index)); paragraph = out_paragraphs.last_mut().unwrap(); - paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? - } else { - let (font_id, glyph_info) = font.glyph_info(chr); - let mut font_face = font.fonts_by_id.get_mut(&font_id); - if current_font != font_id { - current_font = font_id; - current_font_face_metrics = font_face - .as_ref() - .map(|font_face| { - font_face.styled_metrics(pixels_per_point, font_size, &format.coords) - }) - .unwrap_or_default(); - } + paragraph.empty_paragraph_height = line_height; + ctx.is_first_glyph_in_section = true; + } - 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_face.pair_kerning_pixels( - ¤t_font_face_metrics, - last_glyph_id, - glyph_id, - ); + if segment.is_empty() { + continue; + } - // Only apply extra_letter_spacing to glyphs after the first one: - paragraph.cursor_x_px += extra_letter_spacing * pixels_per_point; - } + segment_into_runs(font, segment, &mut runs); - let (glyph_alloc, physical_x) = if let Some(font_face) = font_face.as_mut() { - font_face.allocate_glyph( - font.atlas, - ¤t_font_face_metrics, - glyph_info, - chr, - paragraph.cursor_x_px, - ) - } else { - Default::default() + let num_runs = runs.len(); + for (run_idx, run) in runs.iter().enumerate() { + let run_text = &segment[run.byte_range.clone()]; + let Some(font_face) = font.fonts_by_id.get(&run.font_key) else { + continue; }; - paragraph.glyphs.push(Glyph { - chr, - pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN), - advance_width: glyph_alloc.advance_width_px / pixels_per_point, - line_height, - 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, - section_index, - first_vertex: 0, // filled in later - }); + let face_metrics = + font_face.styled_metrics(pixels_per_point, font_size, &format.coords); - paragraph.cursor_x_px += glyph_alloc.advance_width_px; - last_glyph_id = Some(glyph_alloc.id); + // Set buffer flags for paragraph boundary context. + let mut flags = harfrust::BufferFlags::empty(); + if run_idx == 0 { + flags |= harfrust::BufferFlags::BEGINNING_OF_TEXT; + } + if run_idx + 1 == num_runs { + flags |= harfrust::BufferFlags::END_OF_TEXT; + } + + let glyph_buffer = shape_text(font_face, run_text, &format.coords, shape_buffer, flags); + + layout_shaped_run( + font, + run, + run_text, + &glyph_buffer, + &face_metrics, + &mut ctx, + paragraph, + ); + + shape_buffer = glyph_buffer.clear(); + } + } + + shape_buffer +} + +/// Iterator that either splits on `'\n'` or yields the whole string once. +/// Avoids `Box` and `Vec<&str>` allocation. +enum SplitOrWhole<'a> { + Split(std::str::Split<'a, char>), + Whole(std::iter::Once<&'a str>), +} + +impl<'a> SplitOrWhole<'a> { + fn new(text: &'a str, split: bool) -> Self { + if split { + Self::Split(text.split('\n')) + } else { + Self::Whole(std::iter::once(text)) + } + } +} + +impl<'a> Iterator for SplitOrWhole<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option<&'a str> { + match self { + Self::Split(iter) => iter.next(), + Self::Whole(iter) => iter.next(), } } } @@ -479,33 +706,14 @@ fn replace_last_glyph_with_overflow_character( .unwrap_or_default(); let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() { - // Kern the overflow character properly - let pair_kerning = font_face - .as_mut() - .map(|font_face| { - if let (Some(prev_glyph_id), Some(overflow_glyph_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_face.pair_kerning(&font_face_metrics, prev_glyph_id, overflow_glyph_id) - } else { - 0.0 - } - }) - .unwrap_or_default(); - - prev_glyph.max_x() + extra_letter_spacing + pair_kerning + prev_glyph.max_x() + extra_letter_spacing } else { 0.0 // TODO(emilk): heed paragraph leading_space 😬 }; - let replacement_glyph_width = font_face - .as_mut() - .and_then(|f| f.glyph_info(overflow_character)) - .map(|i| { - i.advance_width_unscaled.0 * font_face_metrics.px_scale_factor / pixels_per_point - }) - .unwrap_or_default(); + let advance_width_px = + glyph_info.advance_width_unscaled.0 * font_face_metrics.px_scale_factor; + let replacement_glyph_width = advance_width_px / pixels_per_point; // Check if we're within width budget: if overflow_glyph_x + replacement_glyph_width <= job.effective_wrap_width() @@ -519,9 +727,11 @@ fn replace_last_glyph_with_overflow_character( f.allocate_glyph( font.atlas, &font_face_metrics, - glyph_info, - overflow_character, - overflow_glyph_x * pixels_per_point, + &ShapedGlyph { + glyph_id: glyph_info.id.unwrap_or(skrifa::GlyphId::NOTDEF), + h_pos: overflow_glyph_x * pixels_per_point, + is_cjk: is_cjk(overflow_character), + }, ) }) .unwrap_or_default(); @@ -536,7 +746,7 @@ fn replace_last_glyph_with_overflow_character( row.glyphs.push(Glyph { chr: overflow_character, pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN), - advance_width: replacement_glyph_alloc.advance_width_px / pixels_per_point, + advance_width: advance_width_px / pixels_per_point, line_height, font_face_height: font_face_metrics.row_height, font_face_ascent: font_face_metrics.ascent, @@ -1060,6 +1270,90 @@ impl RowBreakCandidates { // ---------------------------------------------------------------------------- +/// Segment text into runs where each run uses a single font face. +/// +/// Grapheme clusters are never split across runs: if a combining mark +/// falls back to a different font than its base character, it stays +/// with the base character's font (the shaper will handle it). +/// +/// NOTE: Segmentation is by font face, not by Unicode script. A run may +/// mix scripts (e.g. Latin + Cyrillic) when they share the same font. +/// This is acceptable for scripts with similar shaping rules, but would +/// need script-aware splitting once RTL/bidi support is added. +/// +/// Results are appended to `out` (which is cleared first) to allow +/// the caller to reuse the allocation across calls. +fn segment_into_runs(font: &mut Font<'_>, text: &str, out: &mut Vec) { + use unicode_segmentation::UnicodeSegmentation as _; + + out.clear(); + + for (byte_offset, grapheme_str) in text.grapheme_indices(true) { + let byte_end = byte_offset + grapheme_str.len(); + + let base_char = grapheme_str.chars().next().unwrap_or(' '); + let (font_key, _) = font.glyph_info(base_char); + + if let Some(last_run) = out.last_mut() + && last_run.font_key == font_key + { + last_run.byte_range.end = byte_end; + continue; + } + out.push(TextRun { + font_key, + byte_range: byte_offset..byte_end, + }); + } +} + +/// Shape a text run and return the raw [`harfrust::GlyphBuffer`]. +/// +/// The caller should iterate `glyph_infos()` / `glyph_positions()` (both +/// `Copy` slices) and convert font units to pixels using `metrics.px_scale_factor`. +/// After iteration, recycle the buffer via `glyph_buffer.clear()`. +fn shape_text( + font_face: &FontFace, + text: &str, + coords: &VariationCoords, + mut buffer: harfrust::UnicodeBuffer, + flags: harfrust::BufferFlags, +) -> harfrust::GlyphBuffer { + let font_ref = font_face.skrifa_font_ref(); + let tweak = font_face.tweak(); + + // Build shaper with variable font instance if variation coordinates are set. + let variations: Vec = tweak + .coords + .as_ref() + .iter() + .chain(coords.as_ref().iter()) + .map(|&(tag, value)| harfrust::Variation { tag, value }) + .collect(); + + let instance = if variations.is_empty() { + None + } else { + Some(harfrust::ShaperInstance::from_variations( + font_ref, variations, + )) + }; + + let shaper = font_face + .shaper_data() + .shaper(font_ref) + .instance(instance.as_ref()) + .build(); + + buffer.set_flags(flags); + buffer.push_str(text); + buffer.guess_segment_properties(); + + shaper.shape(buffer, &[]) +} + +// ---------------------------------------------------------------------------- + #[cfg(test)] mod tests { @@ -1277,4 +1571,177 @@ mod tests { "Unexpected intrinsic size" ); } + + #[test] + fn test_combining_diacritics() { + // ɔ̃ = U+0254 (LATIN SMALL LETTER OPEN O) + U+0303 (COMBINING TILDE) + // With text shaping, the combining tilde should NOT produce a separate + // advance — it should be positioned above ɔ via GPOS anchors. + // Note: the default fonts don't contain U+0254, so the replacement glyph + // is used. The key test is that the combining mark does NOT add extra width. + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); + + let job_combined = LayoutJob::simple( + "ɔ\u{0303}".to_owned(), + FontId::proportional(14.0), + Color32::WHITE, + f32::INFINITY, + ); + let galley_combined = layout(&mut fonts, pixels_per_point, job_combined.into()); + + let job_base = LayoutJob::simple( + "ɔ".to_owned(), + FontId::proportional(14.0), + Color32::WHITE, + f32::INFINITY, + ); + let galley_base = layout(&mut fonts, pixels_per_point, job_base.into()); + + let width_combined = galley_combined.size().x; + let width_base = galley_base.size().x; + + assert!( + (width_combined - width_base).abs() < 2.0, + "Combining diacritic should not add significant width. \ + Base width: {width_base}, Combined width: {width_combined}" + ); + + let glyphs = &galley_combined.rows[0].row.glyphs; + assert!(!glyphs.is_empty(), "Expected at least 1 glyph for ɔ̃"); + } + + #[test] + fn test_shaping_basic_latin() { + // Basic test: shaped Latin text should produce the same number of glyphs as characters. + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); + + let job = LayoutJob::simple( + "Hello".to_owned(), + FontId::proportional(14.0), + Color32::WHITE, + f32::INFINITY, + ); + let galley = layout(&mut fonts, pixels_per_point, job.into()); + + assert_eq!(galley.rows.len(), 1); + assert_eq!(galley.rows[0].row.glyphs.len(), 5); + assert!(galley.size().x > 0.0); + } + + #[test] + fn test_shaping_empty_string() { + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); + + let job = LayoutJob::simple( + String::new(), + FontId::proportional(14.0), + Color32::WHITE, + f32::INFINITY, + ); + let galley = layout(&mut fonts, pixels_per_point, job.into()); + + assert_eq!(galley.rows.len(), 1); + assert_eq!(galley.rows[0].row.glyphs.len(), 0); + } + + #[test] + fn test_shaping_multiple_newlines() { + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); + + let job = LayoutJob::simple( + "A\n\nB".to_owned(), + FontId::proportional(14.0), + Color32::WHITE, + f32::INFINITY, + ); + let galley = layout(&mut fonts, pixels_per_point, job.into()); + + assert_eq!(galley.rows.len(), 3, "Expected 3 rows for 'A\\n\\nB'"); + assert_eq!(galley.rows[0].row.glyphs.len(), 1); // "A" + assert_eq!(galley.rows[1].row.glyphs.len(), 0); // empty line + assert_eq!(galley.rows[2].row.glyphs.len(), 1); // "B" + } + + #[test] + fn test_shaping_mixed_font_fallback() { + // Text with both Latin and emoji should work without panicking, + // even though they use different font faces. + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); + + let job = LayoutJob::simple( + "Hi 🎉 bye".to_owned(), + FontId::proportional(14.0), + Color32::WHITE, + f32::INFINITY, + ); + let galley = layout(&mut fonts, pixels_per_point, job.into()); + + assert_eq!(galley.rows.len(), 1); + // "Hi " (3) + "🎉" (1) + " bye" (4) = at least 8 glyphs + assert!( + galley.rows[0].row.glyphs.len() >= 8, + "Expected >= 8 glyphs, got {}", + galley.rows[0].row.glyphs.len() + ); + } + + #[test] + fn test_gpos_kerning() { + // GPOS kerning: pairs like "AV", "VA", "AT" should be tighter than + // the sum of individual character widths. Without text shaping, egui + // only uses the legacy `kern` table, so these pairs had diff ≈ 0. + // With harfrust, GPOS kerning applies proper negative adjustments. + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); + let font_id = FontId::proportional(14.0); + + for pair in ["AV", "VA", "AT"] { + let (pair_w, _, _) = measure_text(&mut fonts, pair, &font_id, pixels_per_point); + let chars: Vec = pair.chars().collect(); + let (w1, _, _) = measure_text( + &mut fonts, + &chars[0].to_string(), + &font_id, + pixels_per_point, + ); + let (w2, _, _) = measure_text( + &mut fonts, + &chars[1].to_string(), + &font_id, + pixels_per_point, + ); + let sum = w1 + w2; + let kern_adjustment = sum - pair_w; + + assert!( + kern_adjustment > 0.5, + "GPOS kerning for '{pair}': expected pair to be noticeably tighter \ + than sum of individuals. pair_width={pair_w:.2}, sum={sum:.2}, \ + kern_adjustment={kern_adjustment:.2} (should be > 0.5)", + ); + } + } + + fn measure_text( + fonts: &mut FontsImpl, + text: &str, + font_id: &FontId, + pixels_per_point: f32, + ) -> (f32, usize, Vec<(char, f32)>) { + let job = LayoutJob::simple( + text.to_owned(), + font_id.clone(), + Color32::WHITE, + f32::INFINITY, + ); + let galley = layout(fonts, pixels_per_point, job.into()); + let glyphs = &galley.rows[0].row.glyphs; + let details: Vec<_> = glyphs.iter().map(|g| (g.chr, g.advance_width)).collect(); + (galley.size().x, glyphs.len(), details) + } } diff --git a/tests/egui_tests/tests/snapshots/atom_letter_spacing.png b/tests/egui_tests/tests/snapshots/atom_letter_spacing.png index 89fba254e..8a022896b 100644 --- a/tests/egui_tests/tests/snapshots/atom_letter_spacing.png +++ b/tests/egui_tests/tests/snapshots/atom_letter_spacing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4aaf541ed0245777c802d31f01edb0cc4e53ebd2f4444e094336c180b98091d3 -size 2221 +oid sha256:14a8e05b81da82b086fe1ba006a39951a8bca3ff7a2b05c5385a425383b30961 +size 2207 diff --git a/tests/egui_tests/tests/snapshots/button_shortcut.png b/tests/egui_tests/tests/snapshots/button_shortcut.png index de7d64b4d..6ee227fca 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:cbf68b6934dae0868bc9cf0891baf5acf110284d297cfa348e756237fca64a28 -size 1564 +oid sha256:1cdfd5e248b3a1f2053f038a4e45c68f085ddbc085a6e1719b1c4c43b18f8d6c +size 1566 diff --git a/tests/egui_tests/tests/snapshots/grow_all.png b/tests/egui_tests/tests/snapshots/grow_all.png index 89b96aba7..9771b6ef1 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:83c3e19004462b793a5929f60f8b81a795c57529bfc74c6e87890aa4b9b8d939 -size 13930 +oid sha256:d74498e867f1ede9fa5e8769afeb24d7cfdd464db7accf0e4770aa94c7860bbc +size 13929 diff --git a/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height.png b/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height.png index e6ac8e446..277c93280 100644 --- a/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height.png +++ b/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef21b42f90401f6b85685e1cc37d07970b38d2b40394f53bbde5bd4f0d54fb95 -size 5340 +oid sha256:1bf4b21569bb28659808ee668be82ac89275dfbbbefba3237560aa2bcbc986b2 +size 5339 diff --git a/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height_reference.png b/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height_reference.png index a533a8401..60aede644 100644 --- a/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height_reference.png +++ b/tests/egui_tests/tests/snapshots/horizontal_wrapped_multiline_row_height_reference.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5025f4cb528ae5edc387149f1d14523ab4b93058f0862e775a1c2276a3e77af6 -size 5377 +oid sha256:3b2a7bfced8ffd7d0002e52f98a01585c9667bc9821bc392e00fb890fb317696 +size 5376 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 672418f84..845dfb4ad 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:3a5669c2c354c6ea42d8eaeb2eb39b65130a87807cbba8382dcc24d59790e794 -size 12181 +oid sha256:78daa3415e3012c0834819de8338dc380e262c5199f0e477b8796b6d11fe606f +size 12212 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_image.png b/tests/egui_tests/tests/snapshots/layout/atoms_image.png index 765e63f05..9bde044b4 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:24c85a987b0b80961b656f386f529b7538ddee59a030d02a0946d0f714ce7004 -size 368329 +oid sha256:c6f941a45026d7842292366b70cfa3824fe16a9f78fd745b6fcd44c48e046a36 +size 369410 diff --git a/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png b/tests/egui_tests/tests/snapshots/layout/atoms_minimal.png index 3e37969f7..6d77c9127 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:97b26c9abaf655fa5ef0625b8bc61042291a8ea18ecc89ea16abd3be6368c006 -size 367314 +oid sha256:a9ec7559bb6f57f446fd32f1fc342907ad4b888688195a5c147a2faaeaf5cd7a +size 369296 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 54a8a3e1c..350194332 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:47b09261afe84892cdb169cb99ae59c49f671e68b3e99fc170e304de9b2bf526 -size 290633 +oid sha256:f0402dcf47b05586a9792d4583a7b7c76072b2f147a57b94bc7f3ba2b3471b26 +size 291012 diff --git a/tests/egui_tests/tests/snapshots/layout/button.png b/tests/egui_tests/tests/snapshots/layout/button.png index 635858aba..464daa5de 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:3cbc6f95073cbbb26729d287e5fe073c76e8bddee7eef95b431a873522234297 -size 313244 +oid sha256:1bafb447c177f2bd94980c1d146a2826ecf62070fb6ee18e63e6a925e2741210 +size 314478 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png index 6c63fb759..ce2844f8f 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:8f14f770785d01b1673d1c8ca780bfff72e51992794dc7233cf5ec4ea99cb3e9 -size 350648 +oid sha256:eadf74cc7f7e10be804df0e7df65c232ecaed7d77c2c89197548ab7206706843 +size 351669 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 9c74cd8be..3a8d09f95 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:231ceab75a602eedcd11f4f4ed34f38fb9d072f5cb54e135a7e02d33d257f86b -size 433973 +oid sha256:60739a18972a5e866610ae2c8f5af719a1bbaeffcdd8c6319e783c37e28d3107 +size 435108 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox.png b/tests/egui_tests/tests/snapshots/layout/checkbox.png index 766aedaca..1f958fd57 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:2f17fe1f7b2cccaa8991559218a7f13f61e459dc8443cf0fe2d24df7e9bd2eea -size 388959 +oid sha256:46d28fc14a4fb8e4a5fb962b17f6a1ad9b8c952453df62d49f846fda9945b409 +size 389363 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png index 8f79ec659..3a2676aba 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:552a4d4933768ea1ee2323e7946f74f9ddd7e2f7b7c6d9f94bb92c8e7dd230a4 -size 416630 +oid sha256:2fb6db99b4dfa1b0c6f97799ffd72c264c02eefd803eee5737075231760c80bd +size 417925 diff --git a/tests/egui_tests/tests/snapshots/layout/drag_value.png b/tests/egui_tests/tests/snapshots/layout/drag_value.png index bfe289a61..105cd3453 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:339772a7974a2136b222697af2dd6e0202295d78e0720645204feb3c291481af -size 263181 +oid sha256:f54f444c25fbd1def8d0f11d03e1fd8bde6745a2593d0029e346cbaafa584ed3 +size 264007 diff --git a/tests/egui_tests/tests/snapshots/layout/radio.png b/tests/egui_tests/tests/snapshots/layout/radio.png index 2fbd917a8..043e6b1a7 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:275c5358d3cfcbae7dfbeae4eac6606e2f394023837da492adc85934a972203e -size 325936 +oid sha256:5be1a82841c5d9916ad0c69eb08ca782797e42ee6f085a50ce2f5bcaa1ec917d +size 328013 diff --git a/tests/egui_tests/tests/snapshots/layout/radio_checked.png b/tests/egui_tests/tests/snapshots/layout/radio_checked.png index e95932c0d..6ef9b7b05 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:8bde6a904873ec2ffd7a194b820f3d76db5cacb3c266f3cb99f1c77ca2bd69fb -size 346473 +oid sha256:0cd7790301453097e1109a2f6600a986a12e0b02406471f12d8c1984395870e4 +size 347530 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value.png b/tests/egui_tests/tests/snapshots/layout/selectable_value.png index fd2daeeb0..b66e2e68a 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:e0b87d78fce32144f1c694beb637461cb70b9127346c90d0276a877db0700291 -size 387935 +oid sha256:fd2d4946dcff38c5fbb1c3af64faa9686f93bd163f54eef0aef9c92084527671 +size 388797 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 8ce768dae..a52c4de9a 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:77d4dd1a05771c25af933398d4f118e5e21a31b2e4db66161cf054fb1d7ebe24 -size 400911 +oid sha256:97c2f56dad8d6aa45385901f9b23a13c49c21fc5aa811ab50dcf7c8451b177dd +size 401596 diff --git a/tests/egui_tests/tests/snapshots/layout/slider.png b/tests/egui_tests/tests/snapshots/layout/slider.png index 83da462b7..306d2a97f 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:b4071301c08f980ee26d914e4a4724b3f46f1113c62495483d9b0df980d8cbcd -size 274770 +oid sha256:619139eb166d383cc9b35653c8c4a97c55cd21b171a24e253d923558cc0d35e1 +size 275512 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit.png b/tests/egui_tests/tests/snapshots/layout/text_edit.png index d5d853f5e..5936c68a7 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:29363b37f1260f9f39edf9ba873f4c33c0d8a8b6670f6fc178459019539ae7e3 -size 220588 +oid sha256:260b2f77cfa51fbc5a948725d1219cfee8723362f228eb768255ee3f271faa39 +size 221505 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 d87f37561..251ecc9ea 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:94186c0b9331fd0d13284126f4f5e92e66014105fb6533422516d4fbe765e4c7 -size 372041 +oid sha256:35342ae9a83bcd19ba169c762f21f1667d9fcc781e6370b939e1781ff5a4e477 +size 372996 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 e65f04b1c..8adc7676b 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:aed677ddda9544258ddc58ed602655f6a62ab2d1d8342accd025593bbcb25e2f -size 506926 +oid sha256:01795b279efc5c680d77817f7d0e6e9f18dc3969ec964d8d3419d0175b0f4d87 +size 512262 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 578e4d9db..900845aff 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:8ff058ef716689c309ae9806aaf08fb64eca545ef8f92ce89e1f8e9b7b7733bc -size 330200 +oid sha256:263f38d4053119d3b5104575ceaba749d29582f6f021b7b9089054ecba4b22a0 +size 330923 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png b/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png index bdcab38f2..439e89094 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf00e99dbfdf7497688955feb8c417fab0a366588d92182eccee775abade5179 -size 361876 +oid sha256:a40004fe56075f31162e16c7c59c00d7e1b8132bbea603b3c54c4dec0875b1bb +size 364491 diff --git a/tests/egui_tests/tests/snapshots/max_width.png b/tests/egui_tests/tests/snapshots/max_width.png index be50f81db..cc1d4f913 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:df3c1ba38afa30d22106d21a54621c28a0de2b98f77f4d7e398f09089286ef3e -size 8367 +oid sha256:67a7d4da3fd41cae45abc5c0dbc0257de64c8d136291b92a6885ab4d9589ff6e +size 8388 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 d49489c41..abb0ceb28 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:f5919c35a3d736e0c432b3a94d6ab2a2f936f71852b94f2f95475fa6ab5281ad -size 8369 +oid sha256:e15a6273a919fe06f7f096f0f7f4ef23eec739841cae58c9b27bd948ea025fef +size 8390 diff --git a/tests/egui_tests/tests/snapshots/rotated_ellipse.png b/tests/egui_tests/tests/snapshots/rotated_ellipse.png index e32f7864c..55a51d681 100644 --- a/tests/egui_tests/tests/snapshots/rotated_ellipse.png +++ b/tests/egui_tests/tests/snapshots/rotated_ellipse.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8f222733524b21969834a9ccc15aa9b0a4deb1d41e1086c80750f7cdd9711c8 -size 17324 +oid sha256:7591c9001a1b4b8cf1f19d7b3efa3bb1f43f7a695c1818b602e3c1f19b179a69 +size 17299 diff --git a/tests/egui_tests/tests/snapshots/rotated_rect.png b/tests/egui_tests/tests/snapshots/rotated_rect.png index 52255aa7f..d09ee268f 100644 --- a/tests/egui_tests/tests/snapshots/rotated_rect.png +++ b/tests/egui_tests/tests/snapshots/rotated_rect.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb4f1d10aa664e04da4b2e38c52cb6516a4c43a98884c9223e15266ea28ccd3d -size 14191 +oid sha256:816105d33ea4233c62a577377a1bf447bf5022ce4df553480924aee97fc374c1 +size 14153 diff --git a/tests/egui_tests/tests/snapshots/shrink_first_text.png b/tests/egui_tests/tests/snapshots/shrink_first_text.png index a623d8b3b..7b9ef4003 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:73b1cc01da110554dd41f4e5134f5d6d34b7e2079d5ac776f40980d616481ffc -size 11448 +oid sha256:224d55c987c8528bb6f9d051893572fbbb36e82bb5acec8b9614784d25093af1 +size 11383 diff --git a/tests/egui_tests/tests/snapshots/shrink_last_text.png b/tests/egui_tests/tests/snapshots/shrink_last_text.png index cda1a2add..f1fa64de7 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:00e129a40ea9815472ab9d823a1801fbdd268bd58745cad1c1c3dd91309c61fc -size 12010 +oid sha256:cee1c5d11e895f997b95606bf2f7c789dbff11e3e95327ab6e958586c334f5b0 +size 11932 diff --git a/tests/egui_tests/tests/snapshots/sides/default_long.png b/tests/egui_tests/tests/snapshots/sides/default_long.png index ae862c32d..695e679fc 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:9c970aab8c09558b806c81f57fc1d695992cb9f6e735a3fb2be75997c106a141 -size 8214 +oid sha256:a1ebcf48076404bf375d9cb66fed752fab48e83adcaa3a08372340f5e68caf2f +size 8111 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 534b55d92..308a273d6 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:7d7c49df327cdea8cc7d6a0b7278a831574a38e8998dba0733fcae2fd44256a9 -size 8833 +oid sha256:b844510cda2e4ba73adf0fb711299d268eedbd05e8a84ea04ce73451093a3b63 +size 8786 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 ebf7424c3..451859fa0 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:46bca727290bb0fc5a9a28137385e7ee4821390d1594704ce5e0ea089f28dacf -size 7079 +oid sha256:140fe4f323fbcd752d1f091c3095674e5392c9de72bfc43ee7032546aca5ce8a +size 7035 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 534b55d92..308a273d6 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:7d7c49df327cdea8cc7d6a0b7278a831574a38e8998dba0733fcae2fd44256a9 -size 8833 +oid sha256:b844510cda2e4ba73adf0fb711299d268eedbd05e8a84ea04ce73451093a3b63 +size 8786 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 d1cfeb533..2fa858ad9 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:841f69878a4b9331f8ab4730d212384a82a9de14b9fba0d6964cd3010900132a -size 6939 +oid sha256:059ea9c691e07377307aca905d2ad70797362138228c6bd173e80951d2170ff0 +size 6833 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 534b55d92..308a273d6 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:7d7c49df327cdea8cc7d6a0b7278a831574a38e8998dba0733fcae2fd44256a9 -size 8833 +oid sha256:b844510cda2e4ba73adf0fb711299d268eedbd05e8a84ea04ce73451093a3b63 +size 8786 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 be67eaf7a..757d74fb5 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:602bc370e3929995c9b17415b513b412e0e12433f2c2b9120c58ea63c747ed79 -size 9184 +oid sha256:b35bec0c6bd97d9941fc4ffd1072869a8875eef0080f6348b036e885c37ceeb1 +size 9073 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 534b55d92..308a273d6 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:7d7c49df327cdea8cc7d6a0b7278a831574a38e8998dba0733fcae2fd44256a9 -size 8833 +oid sha256:b844510cda2e4ba73adf0fb711299d268eedbd05e8a84ea04ce73451093a3b63 +size 8786 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 cb31d61e1..0e6711016 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:b9165daef8acd038a1527192ded0b7cd5d03f235be737308ade467df33b6c8a0 -size 9192 +oid sha256:6e478664951aa3561e74573a0e28a977c00e43db8730b086299fd33c5b4c52da +size 9099 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 534b55d92..308a273d6 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:7d7c49df327cdea8cc7d6a0b7278a831574a38e8998dba0733fcae2fd44256a9 -size 8833 +oid sha256:b844510cda2e4ba73adf0fb711299d268eedbd05e8a84ea04ce73451093a3b63 +size 8786 diff --git a/tests/egui_tests/tests/snapshots/size_max_size.png b/tests/egui_tests/tests/snapshots/size_max_size.png index 499259fd4..d157d2053 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:11a987f7376f8a3174958a8c21bece8bfb7ec284077940d87038271717d2c397 -size 8655 +oid sha256:64287f55195452d41eb94729ab39ea7667abeec7cfd53c4c7e92235dbcd03bf9 +size 8675 diff --git a/tests/egui_tests/tests/snapshots/text_edit_delay_0_empty.png b/tests/egui_tests/tests/snapshots/text_edit_delay_0_empty.png index 58b0b13f2..7f85cf977 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_delay_0_empty.png +++ b/tests/egui_tests/tests/snapshots/text_edit_delay_0_empty.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf62a248bcec1054cbd97251e6fc429972ef2318c24b9a56698d7c80115aa57e -size 2262 +oid sha256:570216d6ffa3cc278705582d95b96200bc3ef1608b8f2983a6ed3f8b2ee7276c +size 2280 diff --git a/tests/egui_tests/tests/snapshots/text_edit_delay_1_h_invisible.png b/tests/egui_tests/tests/snapshots/text_edit_delay_1_h_invisible.png index c1920bcf1..66ad3f4a4 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_delay_1_h_invisible.png +++ b/tests/egui_tests/tests/snapshots/text_edit_delay_1_h_invisible.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef70c95f7e171984f992e1b9366b4a0fe11a4871746cb8cfaa8ee263e59de702 -size 2272 +oid sha256:e74e32fb12decf36c8685274226c29c4e748b2bf276bf6b47ba19ac9a3def66a +size 2290 diff --git a/tests/egui_tests/tests/snapshots/text_edit_halign.png b/tests/egui_tests/tests/snapshots/text_edit_halign.png index 29546a036..bda8964c8 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_halign.png +++ b/tests/egui_tests/tests/snapshots/text_edit_halign.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:502607c803b884e4e1640d39c97b03b0a40df93c2da328f889168e386f837f36 -size 13261 +oid sha256:f567547c446ffa75f968e0ffc505560f3b3d4171319fbe59be27dde4e553e287 +size 13273 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png index e6978c70f..4978b5a3b 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:9ed487544a84f9f128af550030bc7fe8a960bc70897b38f7c858440a42b6ce44 -size 11197 +oid sha256:2eb21d7b99171d249127377698225f0b2e103733864f6809785dfba169e133bf +size 11213 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png index 6cb7241bf..9d3669597 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:d53f67fb3a3717f7bc5ce99b93bc21d1d6580899dfe8e1371ff22bb416af0786 -size 12114 +oid sha256:3322b616a292f685f6051d2bf9be0df28ca7472e8fc3c982cd094ca44dc47ff8 +size 12117 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 b278f6c25..41d88992d 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:e298d89e6fb434e5010d96661fca40bf119118b6b31fdd9fc13201bcd74c8ffd -size 15149 +oid sha256:2021446525a7b1090aaa6b3f12fdd9295c67cde00de61c7f0d36838b6dfc2593 +size 15148 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 9a1e15c20..3bb097aeb 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:a0581d601f1e536298cb52bfc8a167aa37aebdf065fc910973a752c9c159223d -size 14733 +oid sha256:e341f6d3fb3d9d09bd573584f68e4ada4e9af0dcb27cf1bc164797f62865c398 +size 14734 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png index 29f33406f..e45ea9fb3 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:929d06a271e98f0c0a2a72fc63648693b22885f80beda0a7baa5858ceb6f952b -size 13507 +oid sha256:9fd43ec4633a59798d2077d9fd3ad8292d5da66678a2d75d33c957a062d7848a +size 13218 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index 145020884..31f84dbeb 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:880881761251b70d2c841b8aef3895e95a5eea499d2ff554d3f68b888fb167f9 -size 14571 +oid sha256:3e2252dbe6b26cc6b484d09c57a47f4ecd0d75df88f2279a7dd95ca43b597e9d +size 14282 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png index 7cd2d2ce8..b327692cb 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:60ad2d88535977244ac0fa153700489b454a582af2829dc2f41a531943a21d7a -size 9079 +oid sha256:75e5610bd64f6beda7abb005344eff1f5ee23220272077d3b6ee5345baa616aa +size 9095 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png index b35eb2d51..d8d6a052a 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:4502cc58a4085d1e0f9945d0bd1d25adeefe71094ce94a210c57f113727f3a5a -size 11806 +oid sha256:bceb25ddc48567a79389d8333eeffa3db4f083d47488a05772602d6145faf2ea +size 11830 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index 7bdae8cf1..c746c028e 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:cdedec6788b1a5026603322db9dd9f5fa398813d8aa2c56bc60acad390110501 -size 12499 +oid sha256:30e17cfb5c21e3fd6f850e022f301787bd04d1e6453e3bd30ff695de1f61b8c1 +size 12524 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png index 2f3192a74..bb2b2a32b 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:23d1cddf87ea10d6735403ea0b2a16811d4f92246415633d393c991c3bfab2a1 -size 13716 +oid sha256:b42c8d774d1ecb50f5bde1aa358a37cbb669b59d934786fb1cc4c6f0c3400701 +size 13711 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 66f3df875..87163dd91 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:e8ac8bbdf9331dbe4244aa2964adf9f49ab8981b899aee9f3200b2799cdf7bc0 -size 13731 +oid sha256:7037f694fef1211f2ea9d8e7e60836d8eb97c9242c7b396efeb8390eaf0852f1 +size 13730 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index 8a1b9cf30..8b758e108 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:ff67666113e08d60f1d2310a3a5fca30316953cdabfcc4b259ee816eb498f209 -size 9900 +oid sha256:2d09706e0dbbbf6d07e39470285dc8bcd1b3c6d09bedd91cd20d86c96dba1131 +size 9911 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png index 4719c8ce9..2cd2c737a 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:7e1fb3fb0a00a447906aa205c27aa496dcb3d79e98aadf6092811a0514efb5a0 -size 8127 +oid sha256:bf723b58802e9cf5aefb0a06689ed2bfaa45afff9f0fc48d37e742244da097e6 +size 8149 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 8a5999742..666364d68 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:077e7de9fdaaa222ee75f6ad620967fb1e29da37f60407d584be7141e9d0badd -size 10143 +oid sha256:e900a4004a1fc808bf74bef7b1ece7c98e16582c40155e222c04f759d229fc70 +size 10125 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 f02b65693..fff21ac13 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:f6cf861a5c1682add50f9bdee4672e5fcaf882329566097faecab5312ac509b7 -size 21419 +oid sha256:2119fd3aa9aefe8198e7b79968f7fafc4aa93294ba4c2577dc55929302067996 +size 21827 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 19c231b45..0fb7a4708 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:b022e27d7275764df45039abd26f80d69af40fb18bec98cca85565850df859ae -size 8838 +oid sha256:02a333af5e58c53a89d436c4540579d3ea0c1e99c1cad4c019db1da34ff9de10 +size 8840 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png index d27f6f8c4..c61f99ed0 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:337dcbf0b3a344c6cadaf9500376a627739e19e9c47b5da23786c98c612ef4dc -size 10028 +oid sha256:e11dbb1d48a3eadecb5c0e36917785fa1f107e4e283ff2f76831482fe7cd2042 +size 10051 From ab4bca65eafea37b5a41959417bee264e53e6135 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 6 Apr 2026 18:09:54 +0200 Subject: [PATCH 09/84] Make the width of the thin space configurable (#8070) Adds `FontTweak::thin_space_width` and `FontTweak::tab_size` --- crates/egui/src/lib.rs | 4 +-- crates/egui/src/style.rs | 17 +++++++++++ .../egui/src/widgets/text_edit/text_buffer.rs | 8 ++--- .../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 +-- crates/epaint/src/text/font.rs | 30 ++++++++----------- crates/epaint/src/text/fonts.rs | 15 ++++++++++ crates/epaint/src/text/mod.rs | 3 -- crates/epaint/src/text/text_layout.rs | 17 +++++++++-- 11 files changed, 73 insertions(+), 37 deletions(-) diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index cd098eaea..d706fe46d 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -458,8 +458,8 @@ pub use epaint::{ pub mod text { pub use crate::text_selection::CCursorRange; pub use epaint::text::{ - FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TAB_SIZE, - TextFormat, TextWrapping, cursor::CCursor, + FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat, + TextWrapping, cursor::CCursor, }; } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index f7b889506..b84fe727d 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2915,6 +2915,8 @@ impl Widget for &mut FontTweak { y_offset, hinting_override, coords, + thin_space_width, + tab_size, } = self; ui.label("Scale"); @@ -2987,6 +2989,21 @@ impl Widget for &mut FontTweak { } ui.end_row(); + ui.label("thin_space_width"); + ui.horizontal(|ui| { + ui.add( + DragValue::new(thin_space_width) + .range(0.0..=1.0) + .speed(0.01), + ); + ui.label("1\u{2009}234\u{2009}567\u{2009}890"); + }); + ui.end_row(); + + ui.label("tab_size"); + ui.add(DragValue::new(tab_size).range(0.0..=16.0).speed(0.1)); + ui.end_row(); + if ui.button("Reset").clicked() { *self = Default::default(); } diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index a67dc1b38..dbc2db26e 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -1,9 +1,9 @@ use std::{borrow::Cow, ops::Range}; -use epaint::{ - Galley, - text::{TAB_SIZE, cursor::CCursor}, -}; +use epaint::{Galley, text::cursor::CCursor}; + +/// One `\t` character is this many spaces wide (for indentation purposes). +const TAB_SIZE: usize = 4; use crate::{ text::CCursorRange, 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 218a4e258..d65dcb44f 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:2ef07080ca2aa10e128c646479b8b322a092d63f15b35c52ed59015e7c2a0f60 -size 15434 +oid sha256:6d64cc7d014cf063689a4dd8a6cdd87eb944f9637890baf32f59a258342400bf +size 15400 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 8da2d19a9..147a692f2 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:4edb61c6619d5e44892fa29da0aa0a624306ae637dbbaa057e3fa47c14dc06bd -size 35988 +oid sha256:9e9f800546cc98bbd92f31072aceef10b5b8b9bbef0db4c8f4dbae652aefec61 +size 35918 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 1085f9969..ad6dd5637 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:f8da0c2e37497968864a91fa6bef7c545c37791f4f9b788ea9a2f43dd4ac16b1 -size 16116 +oid sha256:e3f6e1cc6a069ac96ff9e039ce85462453ee943b4cb46080550bc1c0749ad658 +size 16085 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 d89daa1c0..71b8a1cee 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:2a603fcb2eb97943d6be3239b91cacee093adcb55a6dd14af93a72fc8b3a61fa -size 39270 +oid sha256:5901b5a8201b85b51118193eef300749a4569eedc62043cd4e03f76e73a12f51 +size 39222 diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 48659fbe9..311b17a05 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -432,8 +432,7 @@ impl FontFace { && let Some(space) = self.glyph_info(' ') { let glyph_info = GlyphInfo { - advance_width_unscaled: (crate::text::TAB_SIZE as f32 - * space.advance_width_unscaled.0) + advance_width_unscaled: (self.tweak.tab_size * space.advance_width_unscaled.0) .into(), ..space }; @@ -441,21 +440,18 @@ impl FontFace { return Some(glyph_info); } - if c == '\u{2009}' { - // Thin space, often used as thousands deliminator: 1 234 567 890 - // https://www.compart.com/en/unicode/U+2009 - // https://en.wikipedia.org/wiki/Thin_space - - if let Some(space) = self.glyph_info(' ') { - 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(), - ..space - }; - self.glyph_info_cache.insert(c, glyph_info); - return Some(glyph_info); - } + if (c == '\u{2009}' || c == '\u{202F}') + && let Some(space) = self.glyph_info(' ') + { + // Thin space (U+2009) and narrow no-break space (U+202F), + // often used as thousands separator: 1 234 567 890 + let advance_width = self.tweak.thin_space_width * space.advance_width_unscaled.0; + let glyph_info = GlyphInfo { + advance_width_unscaled: advance_width.into(), + ..space + }; + self.glyph_info_cache.insert(c, glyph_info); + return Some(glyph_info); } if invisible_char(c) { diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 135ddb6d5..b6c8f3504 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -193,6 +193,19 @@ pub struct FontTweak { /// Override the font's default variation coordinates. pub coords: VariationCoords, + + /// Width of a thin space (`\u{2009}`) and narrow no-break space (`\u{202F}`), + /// as a fraction of the normal space width. + /// + /// Thin space is often used as a thousands separator: `1 234 567`. + /// + /// Default: `0.5` (half a normal space). + pub thin_space_width: f32, + + /// Width of a tab character (`\t`), measured in number of space widths. + /// + /// Default: `4.0`. + pub tab_size: f32, } impl Default for FontTweak { @@ -203,6 +216,8 @@ impl Default for FontTweak { y_offset: 0.0, hinting_override: None, coords: VariationCoords::default(), + thin_space_width: 0.5, + tab_size: 4.0, } } } diff --git a/crates/epaint/src/text/mod.rs b/crates/epaint/src/text/mod.rs index b40ba45b8..7d37c0db6 100644 --- a/crates/epaint/src/text/mod.rs +++ b/crates/epaint/src/text/mod.rs @@ -6,9 +6,6 @@ mod fonts; mod text_layout; mod text_layout_types; -/// One `\t` character is this many spaces wide. -pub const TAB_SIZE: usize = 4; - pub use { fonts::{ FontData, FontDefinitions, FontFamily, FontId, FontInsert, FontPriority, FontTweak, Fonts, diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 0f3089292..75cbb5be0 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -8,7 +8,6 @@ use crate::{ Color32, Mesh, Stroke, Vertex, stroke::PathStroke, text::{ - TAB_SIZE, font::{StyledMetrics, UvRect, is_cjk, is_cjk_break_allowed}, fonts::FontFaceKey, }, @@ -250,11 +249,23 @@ fn layout_shaped_run( .unwrap_or('\u{FFFD}'); // Unicode Replacement Character // Tab is a layout concept, not a glyph — the shaper doesn't know about tab stops. - // Override the advance width to TAB_SIZE × space width. + // Override the advance width using the font's configured tab size. if chr == '\t' { + let tweak = font.fonts_by_id.get(&run.font_key).map(|ff| ff.tweak()); + let tab_size = tweak.map_or(4.0, |t| t.tab_size); let (_, space_info) = font.glyph_info(' '); let space_width_px = space_info.advance_width_unscaled.0 * px_scale; - advance_width_px = TAB_SIZE as f32 * space_width_px; + advance_width_px = tab_size * space_width_px; + } + + // Thin space (U+2009) and narrow no-break space (U+202F): + // override the shaper's advance width with the configured fraction of a space. + if chr == '\u{2009}' || chr == '\u{202F}' { + let tweak = font.fonts_by_id.get(&run.font_key).map(|ff| ff.tweak()); + let thin_space_width = tweak.map_or(0.5, |t| t.thin_space_width); + let (_, space_info) = font.glyph_info(' '); + let space_width_px = space_info.advance_width_unscaled.0 * px_scale; + advance_width_px = thin_space_width * space_width_px; } // Apply extra_letter_spacing only at cluster boundaries, From 3abba21f2db984294f912768f496e3b175f2a984 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Apr 2026 10:38:37 +0200 Subject: [PATCH 10/84] Add `subpixel_binning` to `TextOptions` and `FontTweak` (#8072) This lets you turn off subpixel horizontal binning of glyphs. The option is a trade-off between even kerning and sharp text. * Closes https://github.com/emilk/egui/issues/8034 --- crates/egui/src/style.rs | 33 +++++++++++++++++++-------------- crates/epaint/src/text/font.rs | 12 ++++++++---- crates/epaint/src/text/fonts.rs | 12 +++++++++--- crates/epaint/src/text/mod.rs | 15 +++++++++++++++ 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index b84fe727d..c88ee45fe 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2344,11 +2344,13 @@ impl Visuals { max_texture_side: _, alpha_from_coverage, font_hinting, + subpixel_binning, } = text_options; text_alpha_from_coverage_ui(ui, alpha_from_coverage); - ui.checkbox(font_hinting, "Enable font hinting"); + ui.checkbox(font_hinting, "Font hinting (sharper text)"); + ui.checkbox(subpixel_binning, "Sub-pixel binning (more even kerning)"); }); ui.collapsing("Text cursor", |ui| { @@ -2913,10 +2915,11 @@ impl Widget for &mut FontTweak { scale, y_offset_factor, y_offset, - hinting_override, + hinting, coords, thin_space_width, tab_size, + subpixel_binning, } = self; ui.label("Scale"); @@ -2932,18 +2935,20 @@ 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"); - }); + ui.label("hinting"); + ui.horizontal(|ui| { + ui.radio_value(hinting, Some(true), "on"); + ui.radio_value(hinting, Some(false), "off"); + ui.radio_value(hinting, None, "default"); + }); + ui.end_row(); + + ui.label("subpixel_binning"); + ui.horizontal(|ui| { + ui.radio_value(subpixel_binning, Some(true), "on"); + ui.radio_value(subpixel_binning, Some(false), "off"); + ui.radio_value(subpixel_binning, None, "default"); + }); ui.end_row(); ui.label("coords"); diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 311b17a05..2d5edf1cf 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -316,6 +316,7 @@ pub struct FontFace { name: String, font: FontCell, tweak: FontTweak, + subpixel_binning: bool, /// Cached `harfrust` shaper data (parsed GSUB/GPOS tables). /// `ShaperData` is `Copy` — lives outside the `self_cell`. @@ -352,7 +353,7 @@ impl FontFace { skrifa::instance::LocationRef::default(), ); - let hinting_enabled = tweak.hinting_override.unwrap_or(options.font_hinting); + let hinting_enabled = tweak.hinting.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 @@ -379,10 +380,13 @@ impl FontFace { let shaper_data = harfrust::ShaperData::new(&font.borrow_dependent().skrifa); + let subpixel_binning = tweak.subpixel_binning.unwrap_or(options.subpixel_binning); + Ok(Self { name, font, tweak, + subpixel_binning, shaper_data, glyph_info_cache: Default::default(), glyph_alloc_cache: Default::default(), @@ -551,12 +555,12 @@ impl FontFace { return (GlyphAllocation::default(), h_pos.round() as i32); } - let (h_pos_round, bin) = if is_cjk { + let (h_pos_round, bin) = if self.subpixel_binning && !is_cjk { + SubpixelBin::new(h_pos) + } else { // CJK scripts contain a lot of characters and could hog the glyph atlas // if we stored 4 subpixel offsets per glyph. (h_pos.round() as i32, SubpixelBin::Zero) - } else { - SubpixelBin::new(h_pos) }; let cache_key = GlyphCacheKey::new(glyph_id, metrics, bin); diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index b6c8f3504..f1a6e63b4 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -188,8 +188,13 @@ pub struct FontTweak { /// Override the global font hinting setting for this specific font. /// - /// `None` means use the global setting. - pub hinting_override: Option, + /// `None` means use the global setting in [`TextOptions::font_hinting`]. + pub hinting: Option, + + /// Override the global sub-pixel binning setting for this specific font. + /// + /// `None` means use the global setting in [`TextOptions::subpixel_binning`]. + pub subpixel_binning: Option, /// Override the font's default variation coordinates. pub coords: VariationCoords, @@ -214,7 +219,8 @@ impl Default for FontTweak { scale: 1.0, y_offset_factor: 0.0, y_offset: 0.0, - hinting_override: None, + hinting: None, + subpixel_binning: None, coords: VariationCoords::default(), thin_space_width: 0.5, tab_size: 4.0, diff --git a/crates/epaint/src/text/mod.rs b/crates/epaint/src/text/mod.rs index 7d37c0db6..6d2d783c2 100644 --- a/crates/epaint/src/text/mod.rs +++ b/crates/epaint/src/text/mod.rs @@ -34,6 +34,20 @@ pub struct TextOptions { /// /// Default is `true`. pub font_hinting: bool, + + /// Enable sub-pixel binning for glyphs. + /// + /// Sub-pixel binning renders each glyph at up to four fractional horizontal offsets, + /// giving more even kerning at the cost of more atlas space. + /// + /// It also lead to text looking more blurry. + /// + /// This is always disabled for CJK characters (which have too many unique glyphs). + /// + /// Can be overridden per font with [`FontTweak::subpixel_binning`]. + /// + /// Default: `true`. + pub subpixel_binning: bool, } impl Default for TextOptions { @@ -42,6 +56,7 @@ impl Default for TextOptions { max_texture_side: 2048, // Small but portable alpha_from_coverage: crate::AlphaFromCoverage::default(), font_hinting: true, + subpixel_binning: true, } } } From 188ffacf415f95152b904c40ac48fa9980b05bf4 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:38:57 +0900 Subject: [PATCH 11/84] Log `localStorage` write failures in `local_storage_set` (#8062) Log `localStorage` write failures in `local_storage_set` **Description** This PR improves `local_storage_set()` logging in `eframe/src/web/storage.rs`. It logs: - write failures with key and browser error - unavailable local storage This helps diagnose web persistence issues such as `QuotaExceededError` when `localStorage.setItem()` fails. --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/web/storage.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/web/storage.rs b/crates/eframe/src/web/storage.rs index 170798dc6..baa3ad83b 100644 --- a/crates/eframe/src/web/storage.rs +++ b/crates/eframe/src/web/storage.rs @@ -9,7 +9,19 @@ pub fn local_storage_get(key: &str) -> Option { /// Write data to local storage. pub fn local_storage_set(key: &str, value: &str) { - local_storage().map(|storage| storage.set_item(key, value)); + match local_storage() { + Some(storage) => { + if let Err(err) = storage.set_item(key, value) { + log::warn!( + "local_storage_set failed: key={key}, err={}", + crate::web::string_from_js_value(&err) + ); + } + } + None => { + log::warn!("local_storage unavailable"); + } + } } #[cfg(feature = "persistence")] From a511282e94c364609a2ceb89c4399af67112d53e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Apr 2026 12:42:13 +0200 Subject: [PATCH 12/84] Update to wgpu 29.0.1 (#8073) This fixes an important bug on OpenGL: * https://github.com/emilk/egui/issues/8012 --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58817670a..cd91fea8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2722,9 +2722,9 @@ dependencies = [ [[package]] name = "naga" -version = "29.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b4372fed0bd362d646d01b6926df0e837859ccc522fed720c395e0460f29c8" +checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647" dependencies = [ "arrayvec", "bit-set 0.9.1", @@ -5218,9 +5218,9 @@ checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" -version = "29.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f9f386699b1fb8b8a05bfe82169b24d151f05702d2905a0bf93bc454fcc825" +checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837" dependencies = [ "arrayvec", "bitflags 2.9.4", @@ -5248,9 +5248,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "29.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c34181b0acb8f98168f78f8e57ec66f57df5522b39143dbe5f2f45d7ca927c" +checksum = "1e80ac6cf1895df6342f87d975162108f9d98772a0d74bc404ab7304ac29469e" dependencies = [ "arrayvec", "bit-set 0.9.1", @@ -5318,9 +5318,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "29.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058b6047337cf323a4f092486443a9337f3d81325347e5d77deed7e563aeaedc" +checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e" dependencies = [ "android_system_properties", "arrayvec", @@ -5371,9 +5371,9 @@ dependencies = [ [[package]] name = "wgpu-naga-bridge" -version = "29.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0b8e1e505095f24cb4a578f04b1421d456257dca7fac114d9d9dd3d978c34b8" +checksum = "7b4684f4410da0cf95a4cb63bb5edaac022461dedb6adf0b64d0d9b5f6890d51" dependencies = [ "naga", "wgpu-types", @@ -5381,9 +5381,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "29.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15ece45db77dd5451f11c0ce898334317ce8502d304a20454b531fdc0652fae" +checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6" dependencies = [ "bitflags 2.9.4", "bytemuck", diff --git a/Cargo.toml b/Cargo.toml index 2f5d5cf50..0e592637c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -146,7 +146,7 @@ wayland-cursor = { version = "0.31.11", default-features = false } web-sys = "0.3.77" web-time = "1.1.0" # Timekeeping for native and web webbrowser = "1.0.5" -wgpu = { version = "29.0.0", default-features = false, features = ["std"] } +wgpu = { version = "29.0.1", default-features = false, features = ["std"] } windows-sys = "0.61.2" winit = { version = "0.30.13", default-features = false } From 74b9970a9f65b1b2d0c04176de79c2922ee66bea Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Apr 2026 12:59:46 +0200 Subject: [PATCH 13/84] Fix wrong color of last glyph of selected text (#8075) * Closes https://github.com/emilk/egui/issues/8059 --- crates/egui/src/text_selection/visuals.rs | 2 +- crates/egui_demo_lib/tests/snapshots/text_selection_0.png | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index e41d7a436..e114ddb55 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -61,7 +61,7 @@ pub fn paint_text_selection( let last_glyph_index = if ri == max.row { max.column } else { - row.glyphs.len() - 1 + row.glyphs.len() }; let first_vertex_index = row diff --git a/crates/egui_demo_lib/tests/snapshots/text_selection_0.png b/crates/egui_demo_lib/tests/snapshots/text_selection_0.png index a6d590fdf..a7aac34bf 100644 --- a/crates/egui_demo_lib/tests/snapshots/text_selection_0.png +++ b/crates/egui_demo_lib/tests/snapshots/text_selection_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16e993bb27d32162fcf573cc4d977af775e6e0b94373fc5e8e1a9890453e508c -size 5278 +oid sha256:ce1e16fa09588ec5351d031408657137f42ff40eb7cf6dd00b8c65a4f51ec680 +size 5332 From 1fdc5c07751253c3738b3a2a495ed97ae3516c2c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 7 Apr 2026 13:29:57 +0200 Subject: [PATCH 14/84] Fix text selection of centered and right-aligned text (#8076) * Fixes https://github.com/emilk/egui/issues/8049 * Bug introduced in https://github.com/emilk/egui/pull/7831 --- crates/egui_demo_lib/src/demo/text_layout.rs | 32 ++++++++++++++++--- crates/egui_demo_lib/tests/misc.rs | 20 +++++++----- .../tests/snapshots/demos/Text Layout.png | 4 +-- .../tests/snapshots/text_selection_0.png | 4 +-- .../tests/snapshots/text_selection_1.png | 4 +-- crates/epaint/src/text/text_layout_types.rs | 4 +-- 6 files changed, 48 insertions(+), 20 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/text_layout.rs b/crates/egui_demo_lib/src/demo/text_layout.rs index ef84fb326..656d5a6d2 100644 --- a/crates/egui_demo_lib/src/demo/text_layout.rs +++ b/crates/egui_demo_lib/src/demo/text_layout.rs @@ -7,17 +7,21 @@ pub struct TextLayoutDemo { overflow_character: Option, extra_letter_spacing: f32, line_height_pixels: u32, + halign: egui::Align, + justify: bool, lorem_ipsum: bool, } impl Default for TextLayoutDemo { fn default() -> Self { Self { - max_rows: 6, - break_anywhere: true, + max_rows: 1000, + break_anywhere: false, overflow_character: Some('…'), extra_letter_spacing: 0.0, line_height_pixels: 0, + halign: egui::Align::LEFT, + justify: false, lorem_ipsum: true, } } @@ -48,6 +52,8 @@ impl crate::View for TextLayoutDemo { overflow_character, extra_letter_spacing, line_height_pixels, + halign, + justify, lorem_ipsum, } = self; @@ -109,6 +115,18 @@ impl crate::View for TextLayoutDemo { }); ui.end_row(); + ui.label("Horizontal align:"); + ui.horizontal(|ui| { + ui.selectable_value(halign, egui::Align::LEFT, "Left"); + ui.selectable_value(halign, egui::Align::Center, "Center"); + ui.selectable_value(halign, egui::Align::RIGHT, "Right"); + }); + ui.end_row(); + + ui.label("Justify:"); + ui.checkbox(justify, "Fill row width"); + ui.end_row(); + ui.label("Text:"); ui.horizontal(|ui| { ui.selectable_value(lorem_ipsum, true, "Lorem Ipsum"); @@ -145,8 +163,14 @@ impl crate::View for TextLayoutDemo { ..Default::default() }; - // NOTE: `Label` overrides some of the wrapping settings, e.g. wrap width - ui.label(job); + // NOTE: `Label` overrides some of the wrapping settings, + // e.g. wrap width, halign, and justify. + ui.with_layout( + egui::Layout::top_down(*halign).with_cross_justify(*justify), + |ui| { + ui.label(job); + }, + ); }); } } diff --git a/crates/egui_demo_lib/tests/misc.rs b/crates/egui_demo_lib/tests/misc.rs index 6d66abfb1..427710629 100644 --- a/crates/egui_demo_lib/tests/misc.rs +++ b/crates/egui_demo_lib/tests/misc.rs @@ -62,21 +62,25 @@ fn test_italics() { fn test_text_selection() { let mut results = egui_kittest::SnapshotResults::new(); - for (test_idx, drag_start_x) in [0.2_f32, 0.9].into_iter().enumerate() { - let mut harness = Harness::builder().build_ui(|ui| { - let visuals = ui.visuals_mut(); - visuals.selection.bg_fill = Color32::LIGHT_GREEN; - visuals.selection.stroke.color = Color32::RED; + for (test_idx, drag_start_x) in [0.2_f32, 0.95].into_iter().enumerate() { + let mut harness = Harness::builder() + .with_pixels_per_point(1.0) // TODO(emilk): why does this test fail with 2.0? + .build_ui(|ui| { + let visuals = ui.visuals_mut(); + visuals.selection.bg_fill = Color32::LIGHT_GREEN; + visuals.selection.stroke.color = Color32::RED; - ui.label("Some varied ☺ text :)\nAnd it has a second line!"); - }); + ui.vertical_centered(|ui| { + ui.label("Some varied ☺ text :)\nAnd it has a second line!\nAnd a third!"); + }); + }); harness.run(); harness.fit_contents(); // Drag to select text: let label = harness.get_by_role(Role::Label); harness.drag_at(label.rect().lerp_inside([drag_start_x, 0.25])); - harness.drop_at(label.rect().lerp_inside([0.6, 0.75])); + harness.drop_at(label.rect().lerp_inside([0.5, 0.75])); harness.run(); harness.snapshot(format!("text_selection_{test_idx}")); 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 61065db1d..0bd5b9ceb 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:4c47faa75a002577220a92efceab5c0ccee1ddaa17c2d2f3d6d1429dbf8fe718 -size 60887 +oid sha256:661570ed4bdf24d52ab049e7d3cf22ef4d50542ee5486d133e0a618a6146da42 +size 98582 diff --git a/crates/egui_demo_lib/tests/snapshots/text_selection_0.png b/crates/egui_demo_lib/tests/snapshots/text_selection_0.png index a7aac34bf..dae75cfc3 100644 --- a/crates/egui_demo_lib/tests/snapshots/text_selection_0.png +++ b/crates/egui_demo_lib/tests/snapshots/text_selection_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce1e16fa09588ec5351d031408657137f42ff40eb7cf6dd00b8c65a4f51ec680 -size 5332 +oid sha256:d7d8578b55ed8fdaf2062cb53d7320555e490289ff9efb30f39ebb697d2e252e +size 6529 diff --git a/crates/egui_demo_lib/tests/snapshots/text_selection_1.png b/crates/egui_demo_lib/tests/snapshots/text_selection_1.png index 3506845cb..4be490adc 100644 --- a/crates/egui_demo_lib/tests/snapshots/text_selection_1.png +++ b/crates/egui_demo_lib/tests/snapshots/text_selection_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79676693e9f1804c7e0a32ffa9bec3bde281446ffc184b55a1a3fcf34074ac34 -size 5304 +oid sha256:5e09ec77bd473ad6695557b78d4967ade09b12100f04bc66d0e29779dfb0d551 +size 6495 diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 22fb03c57..481e3ebbf 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -1047,7 +1047,7 @@ impl Galley { return self.end_pos(); }; - let x = row.x_offset(layout_cursor.column) + row.pos.x - self.rect.left(); + let x = row.x_offset(layout_cursor.column) + row.pos.x; Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y())) } @@ -1092,7 +1092,7 @@ impl Galley { if is_pos_within_row || y_dist < best_y_dist { best_y_dist = y_dist; // char_at is `Row` not `PlacedRow` relative which means we have to subtract the pos. - let column = row.char_at(pos.x - row.pos.x + self.rect.left()); + let column = row.char_at(pos.x - row.pos.x); let prefer_next_row = column < row.char_count_excluding_newline(); cursor = CCursor { index: ccursor_index + column, From b117a1ac19954199bb60230ee758b89bd91ab1d8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Apr 2026 09:52:28 +0200 Subject: [PATCH 15/84] Fix `Context::is_pointer_over_egui` and `Context::egui_wants_pointer_input` (#8081) * Closes https://github.com/emilk/egui/issues/8041 These functions were broken when using the new `run_ui`. --- crates/egui/src/context.rs | 52 ++++++++++++++++-------- crates/egui/src/pass_state.rs | 27 ++++++++++++ tests/test_inline_glow_paint/Cargo.toml | 3 +- tests/test_inline_glow_paint/src/main.rs | 19 +++++++++ 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 824a5318c..809f5dac5 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -793,7 +793,7 @@ impl Context { let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); #[expect(deprecated)] self.run(new_input, |ctx| { - let mut top_ui = Ui::new( + let mut root_ui = Ui::new( ctx.clone(), Id::new((ctx.viewport_id(), "__top_ui")), UiBuilder::new() @@ -802,14 +802,15 @@ impl Context { ); { - plugins.on_begin_pass(&mut top_ui); - run_ui(&mut top_ui); - plugins.on_end_pass(&mut top_ui); + plugins.on_begin_pass(&mut root_ui); + run_ui(&mut root_ui); + plugins.on_end_pass(&mut root_ui); } - // Inform ctx about what we actually used, so we can shrink the native window to fit. - // TODO(emilk): make better use of this somehow - ctx.pass_state_mut(|state| state.allocate_central_panel(top_ui.min_rect())); + ctx.pass_state_mut(|state| { + state.root_ui_available_rect = Some(root_ui.available_rect_before_wrap()); + state.root_ui_min_rect = Some(root_ui.min_rect()); + }); }) } @@ -2853,13 +2854,21 @@ impl Context { /// How much space is still available after panels have been added. #[deprecated = "Use content_rect (or viewport_rect) instead"] pub fn available_rect(&self) -> Rect { + #[expect(deprecated)] // legacy self.pass_state(|s| s.available_rect()).round_ui() } /// How much space is used by windows and the top-level [`Ui`]. pub fn globally_used_rect(&self) -> Rect { self.write(|ctx| { - let mut used = ctx.viewport().this_pass.used_by_panels; + let viewport = ctx.viewport(); + let root_ui_min_rect = + (viewport.this_pass.root_ui_min_rect).or(viewport.prev_pass.root_ui_min_rect); + + let mut used = root_ui_min_rect.unwrap_or_else(|| { + #[expect(deprecated)] // legacy + ctx.viewport().this_pass.used_by_panels + }); for (_id, window) in ctx.memory.areas().visible_windows() { used |= window.rect(); } @@ -2886,18 +2895,27 @@ impl Context { /// Is the pointer (mouse/touch) over any egui area? pub fn is_pointer_over_egui(&self) -> bool { let pointer_pos = self.input(|i| i.pointer.interact_pos()); - if let Some(pointer_pos) = pointer_pos { - if let Some(layer) = self.layer_id_at(pointer_pos) { - if layer.order == Order::Background { - !self.pass_state(|state| state.unused_rect.contains(pointer_pos)) - } else { - true - } + let Some(pointer_pos) = pointer_pos else { + return false; + }; + let Some(layer) = self.layer_id_at(pointer_pos) else { + return false; + }; + if layer.order == Order::Background { + let root_ui_available_rect = self + .pass_state(|state| state.root_ui_available_rect) + .or_else(|| self.prev_pass_state(|state| state.root_ui_available_rect)); + + if let Some(root_ui_available_rect) = root_ui_available_rect { + // Modern `run_ui` code + !root_ui_available_rect.contains(pointer_pos) } else { - false + // Legacy code + #[expect(deprecated)] + !self.pass_state(|state| state.unused_rect.contains(pointer_pos)) } } else { - false + true } } diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index 56564541d..b6e655705 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -1,3 +1,5 @@ +#![expect(deprecated)] // TODO(emilk): Remove legacy panels + use ahash::HashMap; use crate::{Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects, id::IdSet, style}; @@ -199,15 +201,28 @@ pub struct PassState { pub tooltips: TooltipPassState, + /// What the root UI had available at the end of the previous pass. + /// + /// Only set if [`crate::Context::run_ui`] has been called. + pub root_ui_available_rect: Option, + + /// What the root UI had used at the end of the previous pass. + /// + /// Only set if [`crate::Context::run_ui`] has been called. + pub root_ui_min_rect: Option, + /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`crate::CentralPanel`] does not change this. + #[deprecated = "Only used by legacy Context-Panels"] pub available_rect: Rect, /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`crate::CentralPanel`] retracts from this. + #[deprecated = "Only used by legacy Context-Panels"] pub unused_rect: Rect, /// How much space is used by panels. + #[deprecated = "Only used by legacy Context-Panels"] pub used_by_panels: Rect, /// The current scroll area should scroll to this range (horizontal, vertical). @@ -240,6 +255,8 @@ impl Default for PassState { widgets: Default::default(), layers: Default::default(), tooltips: Default::default(), + root_ui_available_rect: None, + root_ui_min_rect: None, available_rect: Rect::NAN, unused_rect: Rect::NAN, used_by_panels: Rect::NAN, @@ -262,6 +279,8 @@ impl PassState { widgets, tooltips, layers, + root_ui_available_rect, + root_ui_min_rect, available_rect, unused_rect, used_by_panels, @@ -278,6 +297,8 @@ impl PassState { widgets.clear(); tooltips.clear(); layers.clear(); + *root_ui_available_rect = None; + *root_ui_min_rect = None; *available_rect = content_rect; *unused_rect = content_rect; *used_by_panels = Rect::NOTHING; @@ -295,6 +316,7 @@ impl PassState { } /// How much space is still available after panels has been added. + #[deprecated = "Only used by legacy Context-Panels"] pub(crate) fn available_rect(&self) -> Rect { debug_assert!( self.available_rect.is_finite(), @@ -304,6 +326,7 @@ impl PassState { } /// Shrink `available_rect`. + #[deprecated = "Only used by legacy Context-Panels"] pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) { debug_assert!( panel_rect.min.distance(self.available_rect.min) < 0.1, @@ -315,6 +338,7 @@ impl PassState { } /// Shrink `available_rect`. + #[deprecated = "Only used by legacy Context-Panels"] pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) { debug_assert!( panel_rect.max.distance(self.available_rect.max) < 0.1, @@ -326,6 +350,7 @@ impl PassState { } /// Shrink `available_rect`. + #[deprecated = "Only used by legacy Context-Panels"] pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) { debug_assert!( panel_rect.min.distance(self.available_rect.min) < 0.1, @@ -337,6 +362,7 @@ impl PassState { } /// Shrink `available_rect`. + #[deprecated = "Only used by legacy Context-Panels"] pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) { debug_assert!( panel_rect.max.distance(self.available_rect.max) < 0.1, @@ -347,6 +373,7 @@ impl PassState { self.used_by_panels |= panel_rect; } + #[deprecated = "Only used by legacy Context-Panels"] pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) { // Note: we do not shrink `available_rect`, because // we allow windows to cover the CentralPanel. diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index 090ccae4c..6aa9428e4 100644 --- a/tests/test_inline_glow_paint/Cargo.toml +++ b/tests/test_inline_glow_paint/Cargo.toml @@ -13,7 +13,8 @@ workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -eframe = { workspace = true, features = [ +eframe = { workspace = true, default_features = false, features = [ + "glow", "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } diff --git a/tests/test_inline_glow_paint/src/main.rs b/tests/test_inline_glow_paint/src/main.rs index 576934bf3..fd4abc35f 100644 --- a/tests/test_inline_glow_paint/src/main.rs +++ b/tests/test_inline_glow_paint/src/main.rs @@ -30,6 +30,10 @@ struct MyTestApp {} impl eframe::App for MyTestApp { fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { + egui::Panel::top("top").show_inside(ui, |ui| { + ui.label("This is a test of painting directly with glow."); + }); + use glow::HasContext as _; let gl = frame.gl().unwrap(); @@ -43,6 +47,21 @@ impl eframe::App for MyTestApp { egui::Window::new("Floating Window").show(ui.ctx(), |ui| { ui.label("The background should be purple."); + ui.label(format!( + "is_pointer_over_egui: {}", + ui.is_pointer_over_egui() + )); + ui.label(format!( + "egui_wants_pointer_input: {}", + ui.egui_wants_pointer_input() + )); + ui.label(format!( + "egui_is_using_pointer: {}", + ui.egui_is_using_pointer() + )); + if let Some(pos) = ui.pointer_latest_pos() { + ui.label(format!("layer_id_at: {:?}", ui.layer_id_at(pos))); + } }); } } From 86a7f47738b70e0b5c39950ca0962ee79e128033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uma=C4=B5o?= <107099960+umajho@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:04:15 +0800 Subject: [PATCH 16/84] Delegate handling of IME interruptions to integrations to fix virtual keyboard flickering on web (#8078) * Closes N/A * Partially replaces #7983 * Related: #8045 * [x] I have followed the instructions in the PR template ## Details In #7983, I modified `Memory::request_focus` to interrupt any ongoing IME composition. This fixed a bug where clicking inside an already focused `TextEdit` failed to cancel the active composition, resulting in duplicated text: https://github.com/emilk/egui/pull/8045#issuecomment-4193310616 To avoid introducing API changes in that PR, I ensured the IME state was reset by forcing `PlatformOutput::ime` to `None` for at least one frame. While this works well on desktop platforms, it causes virtual keyboard flickering on the web: https://github.com/emilk/egui/pull/8045#issuecomment-4193035008 In this PR, I delegate the responsibility for handling IME composition interruptions to integrations, allowing each integration to decide how to interrupt compositions in a flexible manner. ### The new field `should_interrupt_composition` on `IMEOutput`. Instead of introducing a new `OutputCommand` variant, this PR adds a new field `should_interrupt_composition` to `IMEOutput`. Interrupting an active composition is only meaningful when IME remains allowed. If IME should be disabled altogether, `PlatformOutput::ime` can simply be set to `None`. Given this, IMO, it is more appropriate to attach the interrupt signal to `IMEOutput` (i.e., the type of `PlatformOutput::ime`). --- crates/eframe/src/web/text_agent.rs | 15 ++++- crates/egui-winit/src/lib.rs | 11 +++- crates/egui/src/context.rs | 6 ++ crates/egui/src/data/output.rs | 3 + crates/egui/src/memory/mod.rs | 59 ++++++-------------- crates/egui/src/widgets/text_edit/builder.rs | 1 + 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index e3d4f8860..20b904244 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -56,8 +56,13 @@ impl TextAgent { let input = input.clone(); move |event: web_sys::InputEvent, runner: &mut AppRunner| { let text = input.value(); - // Fix android virtual keyboard Gboard - // This removes the virtual keyboard's suggestion. + // Workaround for an Android Gboard issue: after typing a word, + // the user has to delete invisible characters (whose count + // matches the length of the current suggestion) before actual + // characters are deleted, unless the focus has been reset. + // + // this issue appears to have been fixed in Gboard sometime + // between versions 14.7.09 and 17.0.12. if !event.is_composing() { input.blur().ok(); input.focus().ok(); @@ -132,6 +137,12 @@ impl TextAgent { let Some(ime) = ime else { return Ok(()) }; + if ime.should_interrupt_composition { + // no-op for now: currently, the text agent is sizeless, so any + // click shifts focus to the canvas, which naturally interrupts the + // composition. + } + let mut canvas_rect = super::canvas_content_rect(canvas); // Fix for safari with virtual keyboard flapping position if is_mobile_safari() { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 99a9894f3..3526f92be 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1049,7 +1049,8 @@ impl State { self.set_cursor_icon(window, cursor_icon); let allow_ime = ime.is_some(); - if self.allow_ime != allow_ime { + let is_toggling_ime = self.allow_ime != allow_ime; + if is_toggling_ime { self.allow_ime = allow_ime; #[cfg(target_os = "windows")] if !self.allow_ime { @@ -1066,6 +1067,14 @@ impl State { } if let Some(ime) = ime { + if !is_toggling_ime && ime.should_interrupt_composition { + // TODO(umajho): use a more proper way to interrupt composition + // if `winit` provides one in the future. + + window.set_ime_allowed(false); + window.set_ime_allowed(true); + } + let pixels_per_point = pixels_per_point(&self.egui_ctx, window); let ime_rect_px = pixels_per_point * ime.rect; if self.ime_rect_px != Some(ime_rect_px) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 809f5dac5..433446648 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2612,6 +2612,12 @@ impl ContextImpl { let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); + if self.memory.should_interrupt_ime() + && let Some(ime) = &mut platform_output.ime + { + ime.should_interrupt_composition = true; + } + { profiling::scope!("accesskit"); let state = viewport.this_pass.accesskit_state.take(); diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index ea3ff4eec..77a0eee63 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -79,6 +79,9 @@ pub struct IMEOutput { /// /// This is a very thin rectangle. pub cursor_rect: crate::Rect, + + /// Whether any ongoing IME composition should be interrupted. + pub should_interrupt_composition: bool, } /// Commands that the egui integration should execute at the end of a frame. diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 34e0fe319..8d109c254 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -117,21 +117,10 @@ pub struct Memory { #[cfg_attr(feature = "persistence", serde(skip))] popups: ViewportIdMap, - /// When the last IME interruption was made. + /// Whether to inform the backend to interrupt any ongoing IME composition + /// this pass. #[cfg_attr(feature = "persistence", serde(skip))] - ime_interruption_time: ImeInterruptionTime, -} - -#[derive(Clone, Copy, Debug, Default)] -enum ImeInterruptionTime { - #[default] - None, - - /// The IME was interrupted in the current frame. - ThisFrame, - - /// The IME was interrupted in the previous frame. - LastFrame, + requested_interrupt_ime: bool, } impl Default for Memory { @@ -149,7 +138,7 @@ impl Default for Memory { popups: Default::default(), everything_is_visible: Default::default(), add_fonts: Default::default(), - ime_interruption_time: Default::default(), + requested_interrupt_ime: Default::default(), }; slf.interactions.entry(slf.viewport_id).or_default(); slf.areas.entry(slf.viewport_id).or_default(); @@ -778,15 +767,7 @@ impl Memory { self.areas.entry(self.viewport_id).or_default(); - match self.ime_interruption_time { - ImeInterruptionTime::ThisFrame => { - self.ime_interruption_time = ImeInterruptionTime::LastFrame; - } - ImeInterruptionTime::LastFrame => { - self.ime_interruption_time = ImeInterruptionTime::None; - } - ImeInterruptionTime::None => {} - } + self.requested_interrupt_ime = false; // self.interactions is handled elsewhere @@ -1028,30 +1009,22 @@ impl Memory { /// /// A widget should only consume IME events if this returns `true`. At most /// one widget can own IME events for each frame. + #[inline(always)] pub fn owns_ime_events(&self, id: Id) -> bool { - let Some(focus) = self.focus() else { - return false; - }; - // We check across two frames because the widget that called - // `interrupt_ime` may run after other widgets that call this method - // within the same frame. - if matches!( - self.ime_interruption_time, - ImeInterruptionTime::ThisFrame | ImeInterruptionTime::LastFrame - ) { - return false; - } - focus.focused() == Some(id) + // Note: Even if the IME is being interrupted in the current frame, we + // should not return `false` here, since we still need + // `PlatformOutput::ime` to be set in such cases. + + self.has_focus(id) } /// Interrupt the current IME composition, if any. - /// - /// This causes [`Self::owns_ime_events`] to return `false` for all widgets - /// for the remainder of this frame and the next frame, giving time - /// for the IME to be dismissed (by making `platform_output.ime` be `None` - /// for at least one frame). pub fn interrupt_ime(&mut self) { - self.ime_interruption_time = ImeInterruptionTime::ThisFrame; + self.requested_interrupt_ime = true; + } + + pub(crate) fn should_interrupt_ime(&self) -> bool { + self.requested_interrupt_ime } } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 9905a2a55..8ba188443 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -871,6 +871,7 @@ impl TextEdit<'_> { o.ime = Some(crate::output::IMEOutput { rect: to_global * inner_rect, cursor_rect: to_global * primary_cursor_rect, + should_interrupt_composition: false, }); }); } From 41b64fc6f35e811beebeec83ce75408ae4505f30 Mon Sep 17 00:00:00 2001 From: Dimitris Papaioannou Date: Sun, 12 Apr 2026 17:06:12 +0300 Subject: [PATCH 17/84] Call pre_present_notify before presenting (#8089) --- crates/eframe/src/native/wgpu_integration.rs | 2 ++ crates/egui-wgpu/src/winit.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 6d300d513..de691153f 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -722,6 +722,7 @@ impl WgpuWinitRunning<'_> { &clipped_primitives, &textures_delta, screenshot_commands, + window, ); for action in viewport.actions_requested.drain(..) { @@ -1111,6 +1112,7 @@ fn render_immediate_viewport( &clipped_primitives, &textures_delta, vec![], + window, ); egui_winit.handle_platform_output(window, platform_output); diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 3f6adfc27..d64644330 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -411,6 +411,7 @@ impl Painter { /// and the captures captured screenshot if it was requested. /// /// If `capture_data` isn't empty, a screenshot will be captured. + #[expect(clippy::too_many_arguments)] pub fn paint_and_update_textures( &mut self, viewport_id: ViewportId, @@ -419,6 +420,7 @@ impl Painter { clipped_primitives: &[epaint::ClippedPrimitive], textures_delta: &epaint::textures::TexturesDelta, capture_data: Vec, + window: &winit::window::Window, ) -> f32 { profiling::function_scope!(); @@ -654,6 +656,8 @@ impl Painter { ); } + window.pre_present_notify(); + { profiling::scope!("present"); // wgpu doesn't document where vsync can happen. Maybe here? From ba9e0eb6672f21d8cf7b7c4ef7938daece6f30cb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 13 Apr 2026 11:57:34 +0200 Subject: [PATCH 18/84] Add taplo-fmt CI step (#8095) --- .github/workflows/taplo.yml | 25 +++++++++++++++++++++++++ Cargo.toml | 8 ++++++-- crates/egui_glow/Cargo.toml | 9 +++++++-- crates/egui_kittest/Cargo.toml | 2 +- crates/epaint/Cargo.toml | 9 ++++++++- 5 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/taplo.yml diff --git a/.github/workflows/taplo.yml b/.github/workflows/taplo.yml new file mode 100644 index 000000000..11a7b978a --- /dev/null +++ b/.github/workflows/taplo.yml @@ -0,0 +1,25 @@ +# Checks that all TOML files are formatted with taplo. +name: Taplo + +on: + push: + branches: + - "main" + pull_request: + types: [opened, synchronize] + +jobs: + taplo: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Taplo + uses: taiki-e/install-action@v2.48.7 + with: + tool: taplo-cli@0.9.3 + + - name: Check TOML formatting + run: | + taplo fmt --check diff --git a/Cargo.toml b/Cargo.toml index 0e592637c..4559152a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,12 +134,16 @@ syntect = { version = "5.3.0", default-features = false } tempfile = "3.23.0" thiserror = "2.0.17" tokio = "1.49" -toml = {version = "1.0.0", default-features = false } +toml = { version = "1.0.0", default-features = false } type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } unicode-general-category = "1.1.0" unicode-segmentation = "1.12.0" -vello_cpu = { version = "0.0.7", default-features = false, features = ["std", "u8_pipeline", "f32_pipeline"] } +vello_cpu = { version = "0.0.7", default-features = false, features = [ + "std", + "u8_pipeline", + "f32_pipeline", +] } wasm-bindgen = "0.2.108" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml. Don't update this spuriously, because of https://github.com/rerun-io/rerun/issues/8766 wasm-bindgen-futures = "0.4.58" wayland-cursor = { version = "0.31.11", default-features = false } diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index f714dd8e0..c5ff714b3 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -11,7 +11,13 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/egui_glow" categories = ["gui", "game-development"] keywords = ["glow", "egui", "gui", "gamedev"] -include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "Cargo.toml", "src/shader/*.glsl"] +include = [ + "../../LICENSE-APACHE", + "../../LICENSE-MIT", + "**/*.rs", + "Cargo.toml", + "src/shader/*.glsl", +] [lints] workspace = true @@ -65,7 +71,6 @@ winit = { workspace = true, optional = true, default-features = false, features web-sys = { workspace = true, features = ["console"] } wasm-bindgen.workspace = true - [dev-dependencies] glutin = { workspace = true, default-features = true } # examples/pure_glow glutin-winit = { workspace = true, default-features = true } diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index 10938cd37..2d26e6bd8 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -38,7 +38,7 @@ egui.workspace = true eframe = { workspace = true, optional = true } kittest.workspace = true serde.workspace = true -toml = {workspace = true, features = ["parse", "serde"] } +toml = { workspace = true, features = ["parse", "serde"] } # wgpu dependencies egui-wgpu = { workspace = true, optional = true } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 705f1f6a9..f16903730 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -48,7 +48,14 @@ mint = ["emath/mint"] rayon = ["dep:rayon"] ## Allow serialization using [`serde`](https://docs.rs/serde). -serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde", "font-types/serde", "smallvec/serde"] +serde = [ + "dep:serde", + "ahash/serde", + "ecolor/serde", + "emath/serde", + "font-types/serde", + "smallvec/serde", +] ## Change Vertex layout to be compatible with unity unity = [] From 170b46a0c860c429ddf5774fd6ebd07a46f5f234 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 13 Apr 2026 11:57:41 +0200 Subject: [PATCH 19/84] Update `rand` (#8096) --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd91fea8e..6efed68d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1337,7 +1337,7 @@ dependencies = [ "image", "jiff", "mimalloc", - "rand 0.9.2", + "rand 0.9.3", "serde", "unicode_names2", ] @@ -3657,9 +3657,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", From db87c712a1c02e6f1577c682b4f90fb926482364 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 13 Apr 2026 17:48:43 +0200 Subject: [PATCH 20/84] Replace `cargo machete` with `cargo shear` (#8094) We've had good experiences with `cargo shear` at Rerun --- .github/workflows/cargo_machete.yml | 19 -------------- .github/workflows/cargo_shear.yml | 25 +++++++++++++++++++ Cargo.lock | 2 -- crates/egui-winit/Cargo.toml | 3 +++ crates/egui_demo_app/Cargo.toml | 4 +-- crates/egui_demo_lib/Cargo.toml | 3 +++ crates/egui_glow/Cargo.toml | 4 --- examples/custom_style/Cargo.toml | 4 +-- examples/hello_world_par/Cargo.toml | 2 +- examples/images/Cargo.toml | 2 +- examples/puffin_profiler/Cargo.toml | 2 +- tests/egui_tests/Cargo.toml | 3 +++ tests/test_egui_extras_compilation/Cargo.toml | 2 +- 13 files changed, 42 insertions(+), 33 deletions(-) delete mode 100644 .github/workflows/cargo_machete.yml create mode 100644 .github/workflows/cargo_shear.yml diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml deleted file mode 100644 index 1dc162e56..000000000 --- a/.github/workflows/cargo_machete.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Cargo Machete - -on: [push, pull_request] - -jobs: - cargo-machete: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: 1.92 - - name: Machete install - ## The official cargo-machete action - uses: bnjbvr/cargo-machete@v0.9.1 - - name: Checkout - uses: actions/checkout@v4 - - name: Machete Check - run: cargo machete diff --git a/.github/workflows/cargo_shear.yml b/.github/workflows/cargo_shear.yml new file mode 100644 index 000000000..734abacb5 --- /dev/null +++ b/.github/workflows/cargo_shear.yml @@ -0,0 +1,25 @@ +# Looks for unused crates. +name: Cargo Shear + +on: + push: + branches: + - "main" + pull_request: + types: [opened, synchronize] + +jobs: + cargo-shear: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Cargo Shear + uses: taiki-e/install-action@v2.48.7 + with: + tool: cargo-shear@1.11.2 + + - name: Run Cargo Shear + run: | + cargo shear diff --git a/Cargo.lock b/Cargo.lock index 6efed68d0..caac14876 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1375,8 +1375,6 @@ dependencies = [ "log", "memoffset", "profiling", - "wasm-bindgen", - "web-sys", "winit", ] diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index dd4aa8f9d..7fea2341b 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -20,6 +20,9 @@ workspace = true all-features = true rustdoc-args = ["--generate-link-to-definition"] +[package.metadata.cargo-shear] +ignored = ["wayland-cursor"] # TODO(emilk): remove when we update winit + [features] default = ["clipboard", "links", "wayland", "winit/default", "x11"] diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 5bb093ced..3b4de53e6 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -8,8 +8,8 @@ rust-version.workspace = true publish = false default-run = "egui_demo_app" -[package.metadata.cargo-machete] -ignored = ["profiling"] +[package.metadata.cargo-shear] +ignored = ["image", "profiling", "wasm-bindgen-futures"] [lints] workspace = true diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index dc57fb092..d2586fa11 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -20,6 +20,9 @@ workspace = true all-features = true rustdoc-args = ["--generate-link-to-definition"] +[package.metadata.cargo-shear] +ignored = ["image"] # We need the png feature + [lib] diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index c5ff714b3..d5a9f3716 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -66,10 +66,6 @@ document-features = { workspace = true, optional = true } # Native: winit = { workspace = true, optional = true, default-features = false, features = ["rwh_06"] } -# Web: -[target.'cfg(target_arch = "wasm32")'.dependencies] -web-sys = { workspace = true, features = ["console"] } -wasm-bindgen.workspace = true [dev-dependencies] glutin = { workspace = true, default-features = true } # examples/pure_glow diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index 83174df40..3352aa474 100644 --- a/examples/custom_style/Cargo.toml +++ b/examples/custom_style/Cargo.toml @@ -10,8 +10,8 @@ publish = false workspace = true -[package.metadata.cargo-machete] -ignored = ["image"] # We need the .png feature +[package.metadata.cargo-shear] +ignored = ["image"] # We need the png feature [dependencies] diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index ef719b742..04a3c0e25 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -11,7 +11,7 @@ publish = false workspace = true -[package.metadata.cargo-machete] +[package.metadata.cargo-shear] ignored = ["winit"] # Just enable some features of it; see below diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index 7c3b77b7c..adac2a540 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -11,7 +11,7 @@ publish = false workspace = true -[package.metadata.cargo-machete] +[package.metadata.cargo-shear] ignored = ["image"] # We only use the dependency to add more features to it diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 49e6598db..ed8ce3aa0 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" rust-version = "1.92" publish = false -[package.metadata.cargo-machete] +[package.metadata.cargo-shear] ignored = ["profiling"] [lints] diff --git a/tests/egui_tests/Cargo.toml b/tests/egui_tests/Cargo.toml index 8c09042b1..44a7b9c8f 100644 --- a/tests/egui_tests/Cargo.toml +++ b/tests/egui_tests/Cargo.toml @@ -5,6 +5,9 @@ license.workspace = true rust-version.workspace = true version.workspace = true +[package.metadata.cargo-shear] +ignored = ["image"] # We need the png feature + [dev-dependencies] egui = { workspace = true, default-features = true } egui_kittest = { workspace = true, features = ["snapshot", "wgpu"] } diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index 3598362a9..3e1d166f7 100644 --- a/tests/test_egui_extras_compilation/Cargo.toml +++ b/tests/test_egui_extras_compilation/Cargo.toml @@ -9,7 +9,7 @@ publish = false [lints] workspace = true -[package.metadata.cargo-machete] +[package.metadata.cargo-shear] ignored = ["eframe", "egui_extras"] # We don't use them, just check that things compile [dependencies] From 770090a6fff5500b22d73da1ee2d6da609d0e719 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 14 Apr 2026 10:52:28 +0200 Subject: [PATCH 21/84] Fix `egui_demo_app` on Linux (#8100) * Closes https://github.com/emilk/egui/issues/8098 --- crates/egui_demo_app/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 3b4de53e6..f9a153268 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 = ["wgpu", "persistence"] +default = ["wgpu", "persistence", "wayland", "x11"] web_app = ["http", "persistence"] From 152b97b434b6a2ff11b5d77b50a659ce1945e185 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 14 Apr 2026 11:35:52 +0200 Subject: [PATCH 22/84] Warn if using a software rasterizer (#8101) * Related to https://github.com/emilk/egui/issues/8093 --- crates/egui-wgpu/src/lib.rs | 41 +++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 180b305be..5ab91557b 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -138,29 +138,12 @@ async fn request_adapter( } })?; - if cfg!(target_arch = "wasm32") { - log::debug!( - "Picked wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) + if 1 < available_adapters.len() { + log::info!( + "There are {} available wgpu adapters: {}", + available_adapters.len(), + describe_adapters(available_adapters) ); - } else { - // native: - if available_adapters.len() == 1 { - log::debug!( - "Picked the only available wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) - ); - } else { - log::info!( - "There were {} available wgpu adapters: {}", - available_adapters.len(), - describe_adapters(available_adapters) - ); - log::debug!( - "Picked wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) - ); - } } Ok(adapter) @@ -236,6 +219,8 @@ impl RenderState { }) => (adapter, device, queue), }; + log_adapter_info(&adapter.get_info()); + let surface_formats = { profiling::scope!("get_capabilities"); compatible_surface.map_or_else( @@ -406,6 +391,18 @@ pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option String { let wgpu::AdapterInfo { From 5c96f4f080b9124e9a1bbb3c53c51b1ac272eeab Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 14 Apr 2026 13:00:35 +0200 Subject: [PATCH 23/84] Document glow-only fields in `NativeOptions` (#8104) --- crates/eframe/src/epi.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index b9a178a1d..bc19951bf 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -317,6 +317,8 @@ pub struct NativeOptions { /// Turn on vertical syncing, limiting the FPS to the display refresh rate. /// /// The default is `true`. + /// + /// Only affects the `glow` backend. pub vsync: bool, /// Set the level of the multisampling anti-aliasing (MSAA). @@ -343,6 +345,8 @@ pub struct NativeOptions { /// Specify whether or not hardware acceleration is preferred, required, or not. /// /// Default: [`HardwareAcceleration::Preferred`]. + /// + /// Only affects the `glow` backend. pub hardware_acceleration: HardwareAcceleration, /// What rendering backend to use. From 902906f9893a07f893ddc876c943febb8cd4da8d Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 14 Apr 2026 13:13:59 +0200 Subject: [PATCH 24/84] Fix centered & right aligned `TextEdit` (#8082) A couple improvements to centered and right-aligned text edits: - Fix text selection in centered and right aligned text edits (ironically, this broke in #8076) - Fix cursor movement in centered and right aligned text edits (horizontal cursor position will be retained on vertical movement) - Multiline text edit exceeding available width if there are atoms - Added atoms & alignment options to text edit demo - Improve how vertical_align and horizontal_align are applied - Textedit atom is grow now, removing the need for the extra seperate grow atom - This allows us to apply the `align` on the text edit atom instead of the whole AtomLayout - Fixes https://github.com/emilk/egui/pull/8022 - Fixes https://github.com/emilk/egui/issues/7999 --- crates/egui/src/widgets/text_edit/builder.rs | 36 +++++++++------- crates/egui_demo_lib/src/demo/text_edit.rs | 42 ++++++++++++++++++- .../tests/snapshots/demos/TextEdit.png | 4 +- crates/epaint/src/text/text_layout_types.rs | 6 ++- .../layout/text_edit_prefix_suffix.png | 4 +- .../tests/snapshots/text_edit_halign.png | 4 +- .../visuals/text_edit_prefix_suffix.png | 4 +- 7 files changed, 74 insertions(+), 26 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 8ba188443..416cd8c9b 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -4,8 +4,8 @@ use emath::{Rect, TSTransform}; use epaint::text::{Galley, LayoutJob, TextWrapMode, cursor::CCursor}; use crate::{ - Align, Align2, Atom, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context, CursorIcon, - Event, EventFilter, FontSelection, Frame, Id, ImeEvent, IntoAtoms, IntoSizedResult, Key, + Align, Align2, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context, CursorIcon, Event, + EventFilter, FontSelection, Frame, Id, ImeEvent, IntoAtoms, IntoSizedResult, Key, KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, SizedAtomKind, TextBuffer, TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint, os::OperatingSystem, @@ -480,11 +480,12 @@ impl TextEdit<'_> { let font_id_clone = font_id.clone(); let mut default_layouter = move |ui: &Ui, text: &dyn TextBuffer, wrap_width: f32| { let text = mask_if_password(password, text.as_str()); - let layout_job = if multiline { + let mut layout_job = if multiline { LayoutJob::simple(text, font_id_clone.clone(), text_color, wrap_width) } else { LayoutJob::simple_singleline(text, font_id_clone.clone(), text_color) }; + layout_job.halign = align.x(); ui.fonts_mut(|f| f.layout_job(layout_job)) }; @@ -591,6 +592,7 @@ impl TextEdit<'_> { if !shrunk && matches!(atom.kind, AtomKind::Text(_)) { // elide the hint_text if needed atom = atom.atom_shrink(true); + atom = atom.atom_grow(true); shrunk = true; } @@ -619,6 +621,11 @@ impl TextEdit<'_> { get_galley = Some(galley); } else { + // We need to shrink when clip_text, so that we don't exceed the available size + // and thus clip. We also need to shrink in multi line text edits, so text can + // wrap appropriately. + let should_shrink = clip_text || multiline; + // We need a closure here, so we can calculate the galley based on the available // width (after adding suffix and prefix), for correct wrapping in multi line text // edits @@ -645,16 +652,13 @@ impl TextEdit<'_> { sized: SizedAtomKind::Empty { size: Some(size) }, } }) + .atom_grow(true) + .atom_align(self.align) .atom_id(inner_rect_id) - .atom_shrink(clip_text), + .atom_shrink(should_shrink), ); } - // Ensure the suffix is always right-aligned - if !suffix.is_empty() { - atoms.push_right(Atom::grow()); - } - // TODO(servo/rust-smallvec#146): Use extend_right instead of the loop once we have // smallvec 2.0. Using `extend_right` here won't compile, due to lifetime issues. for atom in suffix { @@ -679,7 +683,7 @@ impl TextEdit<'_> { .max_width(allocate_width) .sense(sense) .frame(frame) - .align2(Align2::LEFT_TOP) + .align2(align) .wrap_mode(wrap_mode) .allocate(ui); @@ -740,16 +744,18 @@ impl TextEdit<'_> { // TODO(emilk): drag selected text to either move or clone (ctrl on windows, alt on mac) - let cursor_at_pointer = - galley.cursor_from_pos(pointer_pos - inner_rect.min + state.text_offset); + let cursor_at_pointer = galley.cursor_from_pos( + pointer_pos - inner_rect.min + state.text_offset + vec2(galley.rect.left(), 0.0), + ); if ui.visuals().text_cursor.preview && response.hovered() && ui.input(|i| i.pointer.is_moving()) { // text cursor preview: - let cursor_rect = TSTransform::from_translation(inner_rect.min.to_vec2()) - * cursor_rect(&galley, &cursor_at_pointer, row_height); + let cursor_rect = TSTransform::from_translation( + inner_rect.min.to_vec2() - vec2(galley.rect.left(), 0.0), + ) * cursor_rect(&galley, &cursor_at_pointer, row_height); text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect); } @@ -835,7 +841,7 @@ impl TextEdit<'_> { if has_focus && let Some(cursor_range) = state.cursor.range(&galley) { let primary_cursor_rect = cursor_rect(&galley, &cursor_range.primary, row_height) - .translate(galley_pos.to_vec2()); + .translate(galley_pos.to_vec2() - vec2(galley.rect.left(), 0.0)); if response.changed() || selection_changed { // Scroll to keep primary cursor in view: diff --git a/crates/egui_demo_lib/src/demo/text_edit.rs b/crates/egui_demo_lib/src/demo/text_edit.rs index 3ec53a523..8fd42da11 100644 --- a/crates/egui_demo_lib/src/demo/text_edit.rs +++ b/crates/egui_demo_lib/src/demo/text_edit.rs @@ -1,15 +1,21 @@ +use egui::{Align, Align2, AtomExt as _}; + /// Showcase [`egui::TextEdit`]. #[derive(PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct TextEditDemo { pub text: String, + halign: egui::Align, + valign: egui::Align, } impl Default for TextEditDemo { fn default() -> Self { Self { text: "Edit this text".to_owned(), + halign: egui::Align::LEFT, + valign: egui::Align::TOP, } } } @@ -37,7 +43,11 @@ impl crate::View for TextEditDemo { ui.add(crate::egui_github_link_file!()); }); - let Self { text } = self; + let Self { + text, + halign, + valign, + } = self; ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; @@ -46,10 +56,40 @@ impl crate::View for TextEditDemo { ui.label("."); }); + ui.horizontal(|ui| { + ui.label("Horizontal align:"); + ui.selectable_value(halign, egui::Align::LEFT, "Left"); + ui.selectable_value(halign, egui::Align::Center, "Center"); + ui.selectable_value(halign, egui::Align::RIGHT, "Right"); + }); + ui.horizontal(|ui| { + ui.label("Vertical align:"); + ui.selectable_value(valign, egui::Align::TOP, "Top"); + ui.selectable_value(valign, egui::Align::Center, "Center"); + ui.selectable_value(valign, egui::Align::BOTTOM, "Bottom"); + }); + + let clear_id = egui::Id::new("clear_button"); + let clear_size = egui::Vec2::splat(ui.spacing().interact_size.y); + let output = egui::TextEdit::multiline(text) .hint_text("Type something!") + // Atoms are centered by default, so we need to pass the right align here: + .prefix("🔎".atom_align(Align2([Align::LEFT, *valign]))) + .suffix( + egui::Atom::custom(clear_id, clear_size) + .atom_align(Align2([Align::RIGHT, *valign])), + ) + .horizontal_align(*halign) + .vertical_align(*valign) .show(ui); + if let Some(rect) = output.response.rect(clear_id) + && ui.place(rect, egui::Button::new("❌")).clicked() + { + text.clear(); + } + ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; ui.label("Selected text: "); diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 81b49688b..839a15faa 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:9c09529c3a1c26c8f28c00fc15cc5f495842862276870c24b5ee0713954f97fc -size 21916 +oid sha256:94c4af5715992f4dbb5bbec6ce67eec1e2f66cfc078a3e704ec386bdb482cac4 +size 30064 diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 481e3ebbf..fc987890d 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -1244,7 +1244,8 @@ impl Galley { let new_layout_cursor = { // keep same X coord - let column = self.rows[new_row].char_at(h_pos); + // char_at is Row-relative, so subtract the row's position + let column = self.rows[new_row].char_at(h_pos - self.rows[new_row].pos.x); LayoutCursor { row: new_row, column, @@ -1266,7 +1267,8 @@ impl Galley { let new_layout_cursor = { // keep same X coord - let column = self.rows[new_row].char_at(h_pos); + // char_at is Row-relative, so subtract the row's position + let column = self.rows[new_row].char_at(h_pos - self.rows[new_row].pos.x); LayoutCursor { row: new_row, column, diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png b/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png index 439e89094..642a8c5fd 100644 --- a/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png +++ b/tests/egui_tests/tests/snapshots/layout/text_edit_prefix_suffix.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a40004fe56075f31162e16c7c59c00d7e1b8132bbea603b3c54c4dec0875b1bb -size 364491 +oid sha256:a9f298f8ea6692e7ccbddbe182a91824ce262913d50e9a7df104a6c63d39d8a0 +size 372564 diff --git a/tests/egui_tests/tests/snapshots/text_edit_halign.png b/tests/egui_tests/tests/snapshots/text_edit_halign.png index bda8964c8..2f81509e6 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_halign.png +++ b/tests/egui_tests/tests/snapshots/text_edit_halign.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f567547c446ffa75f968e0ffc505560f3b3d4171319fbe59be27dde4e553e287 -size 13273 +oid sha256:48114ad6d116fb9288ce9fe3b173017bda317f69753e7ac03a090b8d02d6cb4d +size 13258 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png index c61f99ed0..e00868b1f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_prefix_suffix.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e11dbb1d48a3eadecb5c0e36917785fa1f107e4e283ff2f76831482fe7cd2042 -size 10051 +oid sha256:a37ed30425967301ffa7dda3fdc8f316dfd7f4665c731b17778e3e5942783e81 +size 10534 From 3607aae91d7bd5b3895cfa4a7e2056c41e0a5bfe Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 14 Apr 2026 13:14:16 +0200 Subject: [PATCH 25/84] Configure wgpu to be low-latency by default (#8103) This changes the default value of `WgpuConfiguration::desired_maximum_frame_latency` to `Some(1)`. For low-Hz displays, this results in significantly lower input latency. * Closes https://github.com/emilk/egui/issues/5037 ? * Related to https://github.com/emilk/egui/issues/7761 --- crates/egui-wgpu/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 5ab91557b..75155899d 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -328,7 +328,12 @@ impl Default for WgpuConfiguration { fn default() -> Self { Self { present_mode: wgpu::PresentMode::AutoVsync, - desired_maximum_frame_latency: None, + desired_maximum_frame_latency: if cfg!(target_os = "ios") { + None // The default is good on iOS, while `Some(1)` cuts FPS in half + } else { + Some(1) // Low-latency by default. + }, + // No display handle available at this point — callers should replace this with // `WgpuSetup::from_display_handle(...)` before creating the instance if one is available. wgpu_setup: WgpuSetup::without_display_handle(), From fe5533e4501848430dfa77af723321b321af11d3 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:49:54 +0900 Subject: [PATCH 26/84] Optimize text selection performance for large documents (#7917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Perf: Optimize text selection and navigation performance for large documents** #### **Summary** This PR significantly improves the performance of text selection (double-clicking) and cursor navigation within `TextEdit` and `Label` widgets, particularly when handling large documents (e.g., 1MB+ or logs). It eliminates several $O(N^2)$ bottlenecks and unnecessary memory allocations in `text_cursor_state.rs`. #### **Problems Identified** 1. **$O(N^2)$ Word Boundary Scanning:** In `next_word_boundary_char_index`, `char_index_from_byte_index` was called repeatedly inside a loop. This caused the entire document to be scanned from the beginning for every word found, leading to quadratic time complexity. 2. **Heavy String Allocations:** `ccursor_previous_word` used `collect::()` and `rev()` to search backwards, causing a full copy and memory allocation of the text (or line) every time the user moved the cursor or double-clicked. 3. **Inefficient Line Start Finding:** `find_line_start` performed global character counts (`text.chars().count()`) and global skips, which is very slow for large files. 4. **Global Search Scope:** `select_word_at` was performing word boundary searches across the entire document even for simple double-click actions. #### **Key Changes & Optimizations** 1. **Line-Scoped Selection:** Updated `select_word_at` to first identify the current line and then perform word boundary searches within that local scope. This reduces the search space from millions of characters to hundreds. 2. **Linear Time ($O(N)$) Boundary Search:** Refactored `next_word_boundary_char_index` to use a running cumulative character counter. This ensures the text is scanned only once. 3. **Zero-Allocation Backwards Search:** Optimized `ccursor_previous_word` to use `next_back()` on the `DoubleEndedIterator` provided by `unicode-segmentation`. This removes all temporary `String` allocations. 4. **Byte-Based Line Search:** Optimized `find_line_start` to use byte-based reverse scanning (`rfind('\n')`), which is significantly faster than counting characters from the start of the document. #### **Performance Impact** In my tests with large text files (over 10,000 lines / 1MB+): - **Before:** Double-clicking a word caused a UI freeze for 2–5 seconds. - **After:** Word selection and navigation are near-instantaneous (0–1ms), providing a smooth "native-like" experience even in WASM environments. --- .../src/text_selection/text_cursor_state.rs | 136 +++++++++++------- 1 file changed, 85 insertions(+), 51 deletions(-) diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index 12e0656d8..ee7f6e512 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -106,38 +106,26 @@ impl TextCursorState { } fn select_word_at(text: &str, ccursor: CCursor) -> CCursorRange { - if ccursor.index == 0 { - CCursorRange::two(ccursor, ccursor_next_word(text, ccursor)) - } else { - let it = text.chars(); - let mut it = it.skip(ccursor.index - 1); - if let Some(char_before_cursor) = it.next() { - if let Some(char_after_cursor) = it.next() { - if is_word_char(char_before_cursor) && is_word_char(char_after_cursor) { - let min = ccursor_previous_word(text, ccursor + 1); - let max = ccursor_next_word(text, min); - CCursorRange::two(min, max) - } else if is_word_char(char_before_cursor) { - let min = ccursor_previous_word(text, ccursor); - let max = ccursor_next_word(text, min); - CCursorRange::two(min, max) - } else if is_word_char(char_after_cursor) { - let max = ccursor_next_word(text, ccursor); - CCursorRange::two(ccursor, max) - } else { - let min = ccursor_previous_word(text, ccursor); - let max = ccursor_next_word(text, ccursor); - CCursorRange::two(min, max) - } - } else { - let min = ccursor_previous_word(text, ccursor); - CCursorRange::two(min, ccursor) - } - } else { - let max = ccursor_next_word(text, ccursor); - CCursorRange::two(ccursor, max) - } + if text.is_empty() { + return CCursorRange::one(ccursor); } + + let line_start = find_line_start(text, ccursor); + let line_end = ccursor_next_line(text, line_start); + + let line_range = line_start.index..line_end.index; + let current_line_text = slice_char_range(text, line_range.clone()); + + let relative_idx = ccursor.index - line_start.index; + let relative_ccursor = CCursor::new(relative_idx); + + let min = ccursor_previous_word(current_line_text, relative_ccursor); + let max = ccursor_next_word(current_line_text, relative_ccursor); + + CCursorRange::two( + CCursor::new(line_start.index + min.index), + CCursor::new(line_start.index + max.index), + ) } fn select_line_at(text: &str, ccursor: CCursor) -> CCursorRange { @@ -209,16 +197,20 @@ fn ccursor_previous_line(text: &str, ccursor: CCursor) -> CCursor { } 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); + let mut current_char_idx = 0; + + for (_word_byte_index, word) in text.split_word_bound_indices() { + let word_ci = current_char_idx; // 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; + let mut word_char_count = 0; + for chr in word.chars() { + let dot_ci = word_ci + word_char_count; if chr == '.' && cursor_ci < dot_ci { return dot_ci; } + word_char_count += 1; } // Splitting considers contiguous whitespace as one word, such words must be skipped, @@ -228,9 +220,11 @@ fn next_word_boundary_char_index(text: &str, cursor_ci: usize) -> usize { if cursor_ci < word_ci && !all_word_chars(word) { return word_ci; } + + current_char_idx += word_char_count; } - char_index_from_byte_index(text, text.len()) + current_char_idx } fn all_word_chars(text: &str) -> bool { @@ -265,22 +259,14 @@ fn is_linebreak(c: char) -> bool { /// Accepts and returns character offset (NOT byte offset!). pub fn find_line_start(text: &str, current_index: CCursor) -> CCursor { - // We know that new lines, '\n', are a single byte char, but we have to - // work with char offsets because before the new line there may be any - // number of multi byte chars. - // We need to know the char index to be able to correctly set the cursor - // later. - let chars_count = text.chars().count(); + let byte_idx = byte_index_from_char_index(text, current_index.index); + let text_before = &text[..byte_idx]; - let position = text - .chars() - .rev() - .skip(chars_count - current_index.index) - .position(|x| x == '\n'); - - match position { - Some(pos) => CCursor::new(current_index.index - pos), - None => CCursor::new(0), + if let Some(last_newline_byte) = text_before.rfind('\n') { + let char_idx = char_index_from_byte_index(text, last_newline_byte + 1); + CCursor::new(char_idx) + } else { + CCursor::new(0) } } @@ -367,3 +353,51 @@ mod test { assert_eq!(next_word_boundary_char_index(text, 20), 21); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_previous_word_graphemes() { + let cases = [ + ("", 0, 0), + ("hello", 0, 0), + ("hello", "hello".chars().count(), 0), + ("hello world", 6, 0), + ("hello world", 8, 6), + ("hello world", "hello world".chars().count(), 6), + ("hello world ", "hello world ".chars().count(), 6), + ("hello world", "hello world".chars().count(), 8), + (" ", " ".chars().count(), 0), + ("hello, world", "hello, world".chars().count(), 7), + ("www.example.com", "www.example.com".chars().count(), 12), + ("안녕! 😊 세상", 8, 6), + ("❤️👍 skvělá knihovna 👍❤️", 18, 11), + ( + "a e\u{301} b", + "a e\u{301} b".chars().count(), + "a e\u{301} ".chars().count(), + ), + ( + "hi 🙂 world", + "hi 🙂 world".chars().count(), + "hi 🙂 ".chars().count(), + ), + ( + "hi 👨‍👩‍👧‍👦 world", + "hi 👨‍👩‍👧‍👦 world".chars().count(), + "hi 👨‍👩‍👧‍👦 ".chars().count(), + ), + ]; + + for (text, cursor, expected) in cases { + let result = ccursor_previous_word(text, CCursor::new(cursor)); + assert_eq!( + result.index, expected, + "text={text:?}, cursor={cursor}, got={}, expected={expected}", + result.index + ); + } + } +} From 1cd89b5edcbaa3ab6080e9d00e760492a0d30be1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 14 Apr 2026 20:19:36 +0200 Subject: [PATCH 27/84] Remove everything that was marked `#[deprecated]` (#8105) Simplify. Streamline. Spring cleaning. --- crates/eframe/src/epi.rs | 24 +- crates/eframe/src/lib.rs | 65 +- crates/eframe/src/native/epi_integration.rs | 8 +- crates/eframe/src/web/app_runner.rs | 3 - crates/egui/src/containers/area.rs | 4 +- .../egui/src/containers/collapsing_header.rs | 9 - crates/egui/src/containers/combo_box.rs | 6 - crates/egui/src/containers/frame.rs | 15 - crates/egui/src/containers/menu.rs | 5 +- crates/egui/src/containers/mod.rs | 2 - crates/egui/src/containers/old_popup.rs | 212 ----- crates/egui/src/containers/panel.rs | 226 +---- crates/egui/src/containers/popup.rs | 8 +- crates/egui/src/containers/resize.rs | 7 - crates/egui/src/containers/scroll_area.rs | 33 - crates/egui/src/containers/tooltip.rs | 18 - crates/egui/src/containers/window.rs | 6 +- crates/egui/src/context.rs | 230 +----- crates/egui/src/data/input.rs | 6 +- crates/egui/src/data/output.rs | 2 +- crates/egui/src/hit_test.rs | 2 +- crates/egui/src/input_state/mod.rs | 10 +- crates/egui/src/layers.rs | 8 +- crates/egui/src/lib.rs | 12 +- crates/egui/src/memory/mod.rs | 63 +- crates/egui/src/menu.rs | 781 ------------------ crates/egui/src/painter.rs | 41 - crates/egui/src/pass_state.rs | 93 +-- crates/egui/src/style.rs | 27 - .../egui/src/text_selection/cursor_range.rs | 6 - crates/egui/src/ui.rs | 276 +------ crates/egui/src/util/mod.rs | 4 - crates/egui/src/viewport.rs | 2 +- crates/egui/src/widget_text.rs | 4 +- crates/egui/src/widgets/button.rs | 6 - crates/egui/src/widgets/drag_value.rs | 16 - crates/egui/src/widgets/image.rs | 12 - crates/egui/src/widgets/image_button.rs | 164 ---- crates/egui/src/widgets/mod.rs | 28 +- crates/egui/src/widgets/progress_bar.rs | 6 - crates/egui/src/widgets/selected_label.rs | 13 - crates/egui/src/widgets/slider.rs | 12 +- crates/egui_extras/src/datepicker/button.rs | 8 - crates/egui_extras/src/table.rs | 11 +- crates/egui_kittest/src/app_kind.rs | 22 +- crates/egui_kittest/src/builder.rs | 63 -- crates/egui_kittest/src/lib.rs | 68 +- crates/egui_kittest/src/node.rs | 27 - crates/epaint/src/lib.rs | 6 - crates/epaint/src/margin_f32.rs | 15 - crates/epaint/src/shapes/shape.rs | 6 - crates/epaint/src/tessellator.rs | 10 - 52 files changed, 78 insertions(+), 2633 deletions(-) delete mode 100644 crates/egui/src/containers/old_popup.rs delete mode 100644 crates/egui/src/menu.rs delete mode 100644 crates/egui/src/widgets/image_button.rs delete mode 100644 crates/egui/src/widgets/selected_label.rs diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index bc19951bf..a9ce726fe 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -2,7 +2,7 @@ //! //! `epi` provides interfaces for window management and serialization. //! -//! Start by looking at the [`App`] trait, and implement [`App::update`]. +//! Start by looking at the [`App`] trait, and implement [`App::ui`]. #![warn(missing_docs)] // Let's keep `epi` well-documented. @@ -161,22 +161,6 @@ pub trait App { /// (A "viewport" in egui means an native OS window). fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame); - /// Called each time the UI needs repainting, which may be many times per second. - /// - /// 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. - /// - /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread). - /// - /// This is called for the root viewport ([`egui::ViewportId::ROOT`]). - /// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows). - /// (A "viewport" in egui means an native OS window). - #[deprecated = "Use Self::ui instead"] - fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { - _ = (ctx, frame); - } - /// Get a handle to the app. /// /// Can be used from web to interact or other external context. @@ -256,7 +240,7 @@ pub trait App { true } - /// A hook for manipulating or filtering raw input before it is processed by [`Self::update`]. + /// A hook for manipulating or filtering raw input before it is processed by [`Self::ui`]. /// /// This function provides a way to modify or filter input events before they are processed by egui. /// @@ -780,7 +764,7 @@ impl Frame { /// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`). /// * Render things behind the egui windows. /// - /// Note that all egui painting is deferred to after the call to [`App::update`] + /// Note that all egui painting is deferred to after the call to [`App::ui`] /// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on). /// /// To get a [`glow`] context you need to compile with the `glow` feature flag, @@ -886,7 +870,7 @@ pub struct IntegrationInfo { /// Seconds of cpu usage (in seconds) on the previous frame. /// - /// This includes [`App::update`] as well as rendering (except for vsync waiting). + /// This includes [`App::ui`] as well as rendering (except for vsync waiting). /// /// For a more detailed view of cpu usage, connect your preferred profiler by enabling it's feature in [`profiling`](https://crates.io/crates/profiling). /// diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index c644289e0..9f7a4f3ef 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -6,7 +6,7 @@ //! To get started, see the [examples](https://github.com/emilk/egui/tree/main/examples). //! To learn how to set up `eframe` for web and native, go to and follow the instructions there! //! -//! In short, you implement [`App`] (especially [`App::update`]) and then +//! In short, you implement [`App`] (especially [`App::ui`]) and then //! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`. //! //! ## Compiling for web @@ -19,7 +19,7 @@ //! //! ## Simplified usage //! If your app is only for native, and you don't need advanced features like state persistence, -//! then you can use the simpler function [`run_simple_native`]. +//! then you can use the simpler function [`run_ui_native`]. //! //! ## Usage, native: //! ``` no_run @@ -446,67 +446,6 @@ pub fn run_ui_native( ) } -/// The simplest way to get started when writing a native app. -/// -/// This does NOT support persistence of custom user data. For that you need to use [`run_native`]. -/// However, it DOES support persistence of egui data (window positions and sizes, how far the user has scrolled in a -/// [`ScrollArea`](egui::ScrollArea), etc.) if the persistence feature is enabled. -/// -/// # Example -/// ``` no_run -/// fn main() -> eframe::Result { -/// // Our application state: -/// let mut name = "Arthur".to_owned(); -/// let mut age = 42; -/// -/// let options = eframe::NativeOptions::default(); -/// eframe::run_simple_native("My egui App", options, move |ctx, _frame| { -/// egui::CentralPanel::default().show(ctx, |ui| { -/// ui.heading("My egui Application"); -/// ui.horizontal(|ui| { -/// let name_label = ui.label("Your name: "); -/// ui.text_edit_singleline(&mut name) -/// .labelled_by(name_label.id); -/// }); -/// ui.add(egui::Slider::new(&mut age, 0..=120).text("age")); -/// if ui.button("Increment").clicked() { -/// age += 1; -/// } -/// ui.label(format!("Hello '{name}', age {age}")); -/// }); -/// }) -/// } -/// ``` -/// -/// # Errors -/// This function can fail if we fail to set up a graphics context. -#[deprecated = "Use run_ui_native instead"] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] -pub fn run_simple_native( - app_name: &str, - native_options: NativeOptions, - update_fun: impl FnMut(&egui::Context, &mut Frame) + 'static, -) -> Result { - struct SimpleApp { - update_fun: U, - } - - impl App for SimpleApp { - fn ui(&mut self, _ui: &mut egui::Ui, _frame: &mut Frame) {} - - fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { - (self.update_fun)(ctx, frame); - } - } - - run_native( - app_name, - native_options, - Box::new(|_cc| Ok(Box::new(SimpleApp { update_fun }))), - ) -} - // ---------------------------------------------------------------------------- /// The different problems that can occur when trying to run `eframe`. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 5b22eb08c..e558b0a2b 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -259,7 +259,7 @@ impl EpiIntegration { /// Run user code - this can create immediate viewports, so hold no locks over this! /// - /// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::update`]. + /// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::ui`]. pub fn update( &mut self, app: &mut dyn epi::App, @@ -287,12 +287,6 @@ impl EpiIntegration { } if is_visible { - { - profiling::scope!("App::update"); - #[expect(deprecated)] - app.update(ui.ctx(), &mut self.frame); - } - { profiling::scope!("App::ui"); app.ui(ui, &mut self.frame); diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 7161a665e..c15e78d68 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -284,9 +284,6 @@ impl AppRunner { self.app.logic(ui.ctx(), &mut self.frame); if is_visible { - #[expect(deprecated)] - self.app.update(ui.ctx(), &mut self.frame); - self.app.ui(ui, &mut self.frame); } }); diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index d44c0ae41..09488058d 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -280,7 +280,7 @@ impl Area { self } - /// Constrains this area to [`Context::screen_rect`]? + /// Constrains this area to [`Context::content_rect`]? /// /// Default: `true`. #[inline] @@ -291,7 +291,7 @@ impl Area { /// Constrain the movement of the window to the given rectangle. /// - /// For instance: `.constrain_to(ctx.screen_rect())`. + /// For instance: `.constrain_to(ctx.content_rect())`. #[inline] pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { self.constrain = true; diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index aca8ab138..de3581288 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -451,15 +451,6 @@ impl CollapsingHeader { self } - /// Explicitly set the source of the [`Id`] of this widget, instead of using title label. - /// This is useful if the title label is dynamic or not unique. - #[deprecated = "Renamed id_salt"] - #[inline] - pub fn id_source(mut self, id_salt: impl Hash) -> Self { - self.id_salt = Id::new(id_salt); - self - } - /// If you set this to `false`, the [`CollapsingHeader`] will be grayed out and un-clickable. /// /// This is a convenience for [`Ui::disable`]. diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index c4097f803..adbda583c 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -94,12 +94,6 @@ impl ComboBox { } } - /// Without label. - #[deprecated = "Renamed from_id_salt"] - pub fn from_id_source(id_salt: impl std::hash::Hash) -> Self { - Self::from_id_salt(id_salt) - } - /// Set the outer width of the button and menu. /// /// Default is [`Spacing::combo_width`]. diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index d556f827e..5bbce626d 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -174,11 +174,6 @@ impl Frame { Self::NONE } - #[deprecated = "Use `Frame::NONE` or `Frame::new()` instead."] - pub const fn none() -> Self { - Self::NONE - } - /// For when you want to group a few widgets together within a frame. pub fn group(style: &Style) -> Self { Self::new() @@ -283,16 +278,6 @@ impl Frame { self } - /// The rounding of the _outer_ corner of the [`Self::stroke`] - /// (or, if there is no stroke, the outer corner of [`Self::fill`]). - /// - /// In other words, this is the corner radius of the _widget rect_. - #[inline] - #[deprecated = "Renamed to `corner_radius`"] - pub fn rounding(self, corner_radius: impl Into) -> Self { - self.corner_radius(corner_radius) - } - /// Margin outside the painted frame. /// /// Similar to what is called `margin` in CSS. diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index cfdaac827..d4c95b298 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -197,7 +197,7 @@ impl MenuState { /// Horizontal menu bar where you can add [`MenuButton`]s. /// -/// 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`]. /// @@ -219,9 +219,6 @@ pub struct MenuBar { style: StyleModifier, } -#[deprecated = "Renamed to `egui::MenuBar`"] -pub type Bar = MenuBar; - impl Default for MenuBar { fn default() -> Self { Self { diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index a8f3306e9..d828ce0f8 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -9,7 +9,6 @@ mod combo_box; pub mod frame; pub mod menu; pub mod modal; -pub mod old_popup; pub mod panel; mod popup; pub(crate) mod resize; @@ -26,7 +25,6 @@ pub use { combo_box::*, frame::Frame, modal::{Modal, ModalResponse}, - old_popup::*, panel::*, popup::*, resize::Resize, diff --git a/crates/egui/src/containers/old_popup.rs b/crates/egui/src/containers/old_popup.rs deleted file mode 100644 index f8e7cc900..000000000 --- a/crates/egui/src/containers/old_popup.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! Old and deprecated API for popups. Use [`Popup`] instead. -#![expect(deprecated)] - -use crate::containers::tooltip::Tooltip; -use crate::{ - Align, Context, Id, LayerId, Layout, Popup, PopupAnchor, PopupCloseBehavior, Pos2, Rect, - Response, Ui, Widget as _, WidgetText, -}; -use emath::RectAlign; -// ---------------------------------------------------------------------------- - -/// Show a tooltip at the current pointer position (if any). -/// -/// Most of the time it is easier to use [`Response::on_hover_ui`]. -/// -/// See also [`show_tooltip_text`]. -/// -/// Returns `None` if the tooltip could not be placed. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// # #[expect(deprecated)] -/// if ui.ui_contains_pointer() { -/// egui::show_tooltip(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), |ui| { -/// ui.label("Helpful text"); -/// }); -/// } -/// # }); -/// ``` -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - show_tooltip_at_pointer(ctx, parent_layer, widget_id, add_contents) -} - -/// Show a tooltip at the current pointer position (if any). -/// -/// Most of the time it is easier to use [`Response::on_hover_ui`]. -/// -/// See also [`show_tooltip_text`]. -/// -/// Returns `None` if the tooltip could not be placed. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// if ui.ui_contains_pointer() { -/// egui::show_tooltip_at_pointer(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), |ui| { -/// ui.label("Helpful text"); -/// }); -/// } -/// # }); -/// ``` -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_at_pointer( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - Tooltip::always_open(ctx.clone(), parent_layer, widget_id, PopupAnchor::Pointer) - .gap(12.0) - .show(add_contents) - .map(|response| response.inner) -} - -/// Show a tooltip under the given area. -/// -/// If the tooltip does not fit under the area, it tries to place it above it instead. -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_for( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - widget_rect: &Rect, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - Tooltip::always_open(ctx.clone(), parent_layer, widget_id, *widget_rect) - .show(add_contents) - .map(|response| response.inner) -} - -/// Show a tooltip at the given position. -/// -/// Returns `None` if the tooltip could not be placed. -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_at( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - suggested_position: Pos2, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - Tooltip::always_open(ctx.clone(), parent_layer, widget_id, suggested_position) - .show(add_contents) - .map(|response| response.inner) -} - -/// Show some text at the current pointer position (if any). -/// -/// Most of the time it is easier to use [`Response::on_hover_text`]. -/// -/// See also [`show_tooltip`]. -/// -/// Returns `None` if the tooltip could not be placed. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// if ui.ui_contains_pointer() { -/// egui::show_tooltip_text(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), "Helpful text"); -/// } -/// # }); -/// ``` -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_text( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - text: impl Into, -) -> Option<()> { - show_tooltip(ctx, parent_layer, widget_id, |ui| { - crate::widgets::Label::new(text).ui(ui); - }) -} - -/// Was this tooltip visible last frame? -#[deprecated = "Use `Tooltip::was_tooltip_open_last_frame` instead"] -pub fn was_tooltip_open_last_frame(ctx: &Context, widget_id: Id) -> bool { - Tooltip::was_tooltip_open_last_frame(ctx, widget_id) -} - -/// Indicate whether a popup will be shown above or below the box. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum AboveOrBelow { - Above, - Below, -} - -/// Helper for [`popup_above_or_below_widget`]. -#[deprecated = "Use `egui::Popup` instead"] -pub fn popup_below_widget( - ui: &Ui, - popup_id: Id, - widget_response: &Response, - close_behavior: PopupCloseBehavior, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - popup_above_or_below_widget( - ui, - popup_id, - widget_response, - AboveOrBelow::Below, - close_behavior, - add_contents, - ) -} - -/// Shows a popup above or below another widget. -/// -/// Useful for drop-down menus (combo boxes) or suggestion menus under text fields. -/// -/// The opened popup will have a minimum width matching its parent. -/// -/// You must open the popup with [`crate::Memory::open_popup`] or [`crate::Memory::toggle_popup`]. -/// -/// Returns `None` if the popup is not open. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// let response = ui.button("Open popup"); -/// let popup_id = ui.make_persistent_id("my_unique_id"); -/// if response.clicked() { -/// ui.memory_mut(|mem| mem.toggle_popup(popup_id)); -/// } -/// let below = egui::AboveOrBelow::Below; -/// let close_on_click_outside = egui::PopupCloseBehavior::CloseOnClickOutside; -/// # #[expect(deprecated)] -/// egui::popup_above_or_below_widget(ui, popup_id, &response, below, close_on_click_outside, |ui| { -/// ui.set_min_width(200.0); // if you want to control the size -/// ui.label("Some more info, or things you can select:"); -/// ui.label("…"); -/// }); -/// # }); -/// ``` -#[deprecated = "Use `egui::Popup` instead"] -pub fn popup_above_or_below_widget( - _parent_ui: &Ui, - popup_id: Id, - widget_response: &Response, - above_or_below: AboveOrBelow, - close_behavior: PopupCloseBehavior, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - let response = Popup::from_response(widget_response) - .layout(Layout::top_down_justified(Align::LEFT)) - .open_memory(None) - .close_behavior(close_behavior) - .id(popup_id) - .align(match above_or_below { - AboveOrBelow::Above => RectAlign::TOP_START, - AboveOrBelow::Below => RectAlign::BOTTOM_START, - }) - .width(widget_response.rect.width()) - .show(|ui| { - ui.set_min_width(ui.available_width()); - add_contents(ui) - })?; - Some(response.inner) -} diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 4a83ce8d1..d75a90bf4 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -18,9 +18,8 @@ use emath::{GuiRounding as _, Pos2}; use crate::{ - Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef, - Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetType, lerp, - vec2, + Align, Context, CursorIcon, Frame, Id, InnerResponse, Layout, NumExt as _, Rangef, Rect, Sense, + Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, lerp, vec2, }; fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 { @@ -451,59 +450,6 @@ impl Panel { } } -// 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`]. @@ -515,41 +461,6 @@ impl Panel { self.show_inside_dyn(ui, Box::new(add_contents)) } - /// Show the panel at the top level. - #[deprecated = "Use show_inside() instead"] - 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. - #[deprecated = "Use show_animated_inside() instead"] - pub fn show_animated( - self, - ctx: &Context, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - #![expect(deprecated)] - - 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( @@ -577,34 +488,6 @@ impl Panel { } } - /// Show either a collapsed or a expanded panel, with a nice animation between. - #[deprecated = "Use show_animated_between_inside() instead"] - 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> { - #![expect(deprecated)] - - 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, @@ -756,59 +639,6 @@ impl Panel { inner_response } - /// Show the panel at the top level. - fn show_dyn<'c, R>( - self, - ctx: &Context, - add_contents: Box R + 'c>, - ) -> InnerResponse { - #![expect(deprecated)] - - let side = self.side; - let available_rect = ctx.available_rect(); - 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()); - panel_ui - .response() - .widget_info(|| WidgetInfo::new(WidgetType::Panel)); - - let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); - let rect = inner_response.response.rect; - - match side { - 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 - } - 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); @@ -1044,61 +874,9 @@ impl CentralPanel { response } - - /// Show the panel at the top level. - #[deprecated = "Use show_inside() instead"] - 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 { - #![expect(deprecated)] - - let id = Id::new((ctx.viewport_id(), "central_panel")); - - let mut panel_ui = Ui::new( - ctx.clone(), - id, - UiBuilder::new() - .layer_id(LayerId::background()) - .max_rect(ctx.available_rect()), - ); - 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. - ctx.pass_state_mut(|state| state.allocate_central_panel(inner_response.response.rect)); - - inner_response - } } 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/containers/popup.rs b/crates/egui/src/containers/popup.rs index 0fb2a9f2a..cf78a6650 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] // This is a new, safe wrapper around the old `Memory::popup` API. - use std::iter::once; use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2}; @@ -87,7 +85,7 @@ pub enum PopupCloseBehavior { /// but in the popup's body CloseOnClickOutside, - /// Clicks will be ignored. Popup might be closed manually by calling [`crate::Memory::close_all_popups`] + /// Clicks will be ignored. Popup might be closed manually by calling [`Popup::close_all`] /// or by pressing the escape button IgnoreClicks, } @@ -666,10 +664,6 @@ impl Popup<'_> { } /// Open the given popup and close all others. - /// - /// If you are NOT using [`Popup::show`], you must - /// also call [`crate::Memory::keep_popup_open`] as long as - /// you're showing the popup. pub fn open_id(ctx: &Context, popup_id: Id) { ctx.memory_mut(|mem| mem.open_popup(popup_id)); } diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 7ff943b3f..8dcce6b20 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -69,13 +69,6 @@ impl Resize { self } - /// A source for the unique [`Id`], e.g. `.id_source("second_resize_area")` or `.id_source(loop_index)`. - #[inline] - #[deprecated = "Renamed id_salt"] - pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt(id_salt) - } - /// A source for the unique [`Id`], e.g. `.id_salt("second_resize_area")` or `.id_salt(loop_index)`. #[inline] pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index b99bcf5da..c0c29a9ab 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -423,13 +423,6 @@ impl ScrollArea { self } - /// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`. - #[inline] - #[deprecated = "Renamed id_salt"] - pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt(id_salt) - } - /// A source for the unique [`Id`], e.g. `.id_salt("second_scroll_area")` or `.id_salt(loop_index)`. #[inline] pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { @@ -530,32 +523,6 @@ impl ScrollArea { /// This can be used, for example, to optionally freeze scrolling while the user /// is typing text in a [`crate::TextEdit`] widget contained within the scroll area. /// - /// This controls both scrolling directions. - #[deprecated = "Use `ScrollArea::scroll_source()"] - #[inline] - pub fn enable_scrolling(mut self, enable: bool) -> Self { - self.scroll_source = if enable { - ScrollSource::ALL - } else { - ScrollSource::NONE - }; - self - } - - /// Can the user drag the scroll area to scroll? - /// - /// This is useful for touch screens. - /// - /// If `true`, the [`ScrollArea`] will sense drags. - /// - /// Default: `true`. - #[deprecated = "Use `ScrollArea::scroll_source()"] - #[inline] - pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { - self.scroll_source.drag = drag_to_scroll; - self - } - /// What sources does the [`ScrollArea`] use for scrolling the contents. #[inline] pub fn scroll_source(mut self, scroll_source: ScrollSource) -> Self { diff --git a/crates/egui/src/containers/tooltip.rs b/crates/egui/src/containers/tooltip.rs index 78c5a726b..22c319569 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -16,24 +16,6 @@ pub struct Tooltip<'a> { } impl Tooltip<'_> { - /// Show a tooltip that is always open. - #[deprecated = "Use `Tooltip::always_open` instead."] - pub fn new( - parent_widget: Id, - ctx: Context, - anchor: impl Into, - parent_layer: LayerId, - ) -> Self { - Self { - popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer) - .kind(PopupKind::Tooltip) - .gap(4.0) - .sense(Sense::hover()), - parent_layer, - parent_widget, - } - } - /// Show a tooltip that is always open. pub fn always_open( ctx: Context, diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 5ade37014..9f25d6131 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -262,7 +262,7 @@ impl<'open> Window<'open> { self } - /// Constrains this window to [`Context::screen_rect`]. + /// Constrains this window to [`Context::content_rect`]. /// /// To change the area to constrain to, use [`Self::constrain_to`]. /// @@ -275,7 +275,7 @@ impl<'open> Window<'open> { /// Constrain the movement of the window to the given rectangle. /// - /// For instance: `.constrain_to(ctx.screen_rect())`. + /// For instance: `.constrain_to(ctx.content_rect())`. #[inline] pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { self.area = self.area.constrain_to(constrain_rect); @@ -427,7 +427,7 @@ impl<'open> Window<'open> { /// Enable/disable scrolling on the window by dragging with the pointer. `true` by default. /// - /// See [`ScrollArea::drag_to_scroll`] for more. + /// See [`ScrollArea::scroll_source`] for more. #[inline] pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { self.scroll = self.scroll.scroll_source(ScrollSource { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 433446648..d348517ef 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -300,7 +300,7 @@ impl RepaintCause { struct ViewportRepaintInfo { /// Monotonically increasing counter. /// - /// Incremented at the end of [`Context::run`]. + /// Incremented at the end of [`Context::run_ui`]. /// This can be smaller than [`Self::cumulative_pass_nr`], /// but never larger. cumulative_frame_nr: u64, @@ -463,7 +463,7 @@ impl ContextImpl { let content_rect = viewport.input.content_rect(); - viewport.this_pass.begin_pass(content_rect); + viewport.this_pass.begin_pass(); { let mut layers: Vec = viewport.prev_pass.widgets.layer_ids().collect(); @@ -697,8 +697,8 @@ impl ContextImpl { /// // Game loop: /// loop { /// let raw_input = egui::RawInput::default(); -/// let full_output = ctx.run(raw_input, |ctx| { -/// egui::CentralPanel::default().show(&ctx, |ui| { +/// let full_output = ctx.run_ui(raw_input, |ui| { +/// egui::CentralPanel::default().show_inside(ui, |ui| { /// ui.label("Hello world!"); /// if ui.button("Click me").clicked() { /// // take some action here @@ -780,9 +780,6 @@ impl Context { /// }); /// // 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) @@ -791,14 +788,13 @@ impl Context { #[must_use] fn run_ui_dyn(&self, new_input: RawInput, run_ui: &mut dyn FnMut(&mut Ui)) -> FullOutput { let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); - #[expect(deprecated)] - self.run(new_input, |ctx| { + self.run_dyn(new_input, &mut |ctx| { let mut root_ui = Ui::new( ctx.clone(), Id::new((ctx.viewport_id(), "__top_ui")), UiBuilder::new() .layer_id(LayerId::background()) - .max_rect(ctx.available_rect()), + .max_rect(ctx.viewport_rect()), ); { @@ -814,38 +810,6 @@ impl Context { }) } - /// 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. - /// - /// 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`]. - /// - /// ``` - /// // 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(input, |ctx| { - /// egui::CentralPanel::default().show(&ctx, |ui| { - /// ui.label("Hello egui!"); - /// }); - /// }); - /// // handle full_output - /// ``` - /// - /// ## See also - /// * [`Self::run_ui`] - #[must_use] - #[deprecated = "Call run_ui instead"] - 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!(); @@ -915,10 +879,10 @@ impl Context { output } - /// An alternative to calling [`Self::run`]. + /// An alternative to calling [`Self::run_ui`]. /// - /// It is usually better to use [`Self::run`], because - /// `run` supports multi-pass layout using [`Self::request_discard`]. + /// It is usually better to use [`Self::run_ui`], because + /// `run_ui` supports multi-pass layout using [`Self::request_discard`]. /// /// ``` /// // One egui context that you keep reusing: @@ -928,9 +892,7 @@ impl Context { /// let input = egui::RawInput::default(); /// ctx.begin_pass(input); /// - /// egui::CentralPanel::default().show(&ctx, |ui| { - /// ui.label("Hello egui!"); - /// }); + /// // … add panels and windows here … /// /// let full_output = ctx.end_pass(); /// // handle full_output @@ -943,12 +905,6 @@ impl Context { self.write(|ctx| ctx.begin_pass(new_input)); } - - /// See [`Self::begin_pass`]. - #[deprecated = "Renamed begin_pass"] - pub fn begin_frame(&self, new_input: RawInput) { - self.begin_pass(new_input); - } } /// ## Borrows parts of [`Context`] @@ -1049,7 +1005,7 @@ impl Context { /// Read-only access to [`PassState`]. /// - /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). + /// This is only valid during the call to [`Self::run_ui`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state(&self, reader: impl FnOnce(&PassState) -> R) -> R { self.write(move |ctx| reader(&ctx.viewport().this_pass)) @@ -1057,7 +1013,7 @@ impl Context { /// Read-write access to [`PassState`]. /// - /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). + /// This is only valid during the call to [`Self::run_ui`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state_mut(&self, writer: impl FnOnce(&mut PassState) -> R) -> R { self.write(move |ctx| writer(&mut ctx.viewport().this_pass)) @@ -1073,7 +1029,7 @@ impl Context { /// Read-only access to [`Fonts`]. /// - /// Not valid until first call to [`Context::run()`]. + /// Not valid until first call to [`Context::run_ui()`]. /// That's because since we don't know the proper `pixels_per_point` until then. #[inline] pub fn fonts(&self, reader: impl FnOnce(&FontsView<'_>) -> R) -> R { @@ -1090,7 +1046,7 @@ impl Context { /// Read-write access to [`Fonts`]. /// - /// Not valid until first call to [`Context::run()`]. + /// Not valid until first call to [`Context::run_ui()`]. /// That's because since we don't know the proper `pixels_per_point` until then. #[inline] pub fn fonts_mut(&self, reader: impl FnOnce(&mut FontsView<'_>) -> R) -> R { @@ -1666,7 +1622,7 @@ impl Context { /// The total number of completed frames. /// - /// Starts at zero, and is incremented once at the end of each call to [`Self::run`]. + /// Starts at zero, and is incremented once at the end of each call to [`Self::run_ui`]. /// /// This is always smaller or equal to [`Self::cumulative_pass_nr`]. pub fn cumulative_frame_nr(&self) -> u64 { @@ -1675,7 +1631,7 @@ impl Context { /// The total number of completed frames. /// - /// Starts at zero, and is incremented once at the end of each call to [`Self::run`]. + /// Starts at zero, and is incremented once at the end of each call to [`Self::run_ui`]. /// /// This is always smaller or equal to [`Self::cumulative_pass_nr_for`]. pub fn cumulative_frame_nr_for(&self, id: ViewportId) -> u64 { @@ -1695,7 +1651,7 @@ impl Context { /// The total number of completed passes (usually there is one pass per rendered frame). /// - /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). + /// Starts at zero, and is incremented for each completed pass inside of [`Self::run_ui`] (usually once). /// /// If you instead want to know which pass index this is within the current frame, /// use [`Self::current_pass_index`]. @@ -1705,7 +1661,7 @@ impl Context { /// The total number of completed passes (usually there is one pass per rendered frame). /// - /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). + /// Starts at zero, and is incremented for each completed pass inside of [`Self::run_ui`] (usually once). pub fn cumulative_pass_nr_for(&self, id: ViewportId) -> u64 { self.read(|ctx| { ctx.viewports @@ -2080,7 +2036,7 @@ impl Context { self.options(|opt| opt.theme()) } - /// The [`Theme`] used to select between dark and light [`Self::style`] + /// The [`Theme`] used to select between dark and light [`Self::global_style`] /// as the active style used by all subsequent popups, menus, etc. /// /// Example: @@ -2097,12 +2053,6 @@ impl Context { self.options(|opt| Arc::clone(opt.style())) } - /// The currently active [`Style`] used by all subsequent popups, menus, etc. - #[deprecated = "Renamed to `global_style` to avoid confusion with `ui.style()`"] - pub fn style(&self) -> Arc