From 2ccc8e8baba10af2a4153ed1813e18816a7c62c1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 25 Mar 2026 09:21:39 +0100 Subject: [PATCH 01/13] Add `Context::text_edit_focused` (#8014) --- crates/egui/src/context.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 51a663ce5..a9151f9cd 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1933,7 +1933,7 @@ impl Context { } } -/// Callbacks +/// Plugins impl Context { /// Call the given callback at the start of each pass of each viewport. /// @@ -2945,6 +2945,15 @@ impl Context { self.egui_wants_keyboard_input() } + /// Is the currently focused widget a text edit? + pub fn text_edit_focused(&self) -> bool { + if let Some(id) = self.memory(|mem| mem.focused()) { + crate::text_edit::TextEditState::load(self, id).is_some() + } else { + false + } + } + /// Highlight this widget, to make it look like it is hovered, even if it isn't. /// /// If you call this after the widget has been fully rendered, From 12b504563307b6bc7e083f0b2b4dc0b88e5e4d04 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 25 Mar 2026 10:11:21 +0100 Subject: [PATCH 02/13] Add `Context::time` (#8017) --- crates/egui/src/context.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index a9151f9cd..a8751bffc 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1545,6 +1545,11 @@ impl Context { crate::debug_text::print(self, text); } + /// Current time in seconds, relative to some unknown epoch. + pub fn time(&self) -> f64 { + self.input(|i| i.time) + } + /// What operating system are we running on? /// /// When compiling natively, this is From bfbf23b4fb05795f9f51768f8258ec6898f5c3f7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 25 Mar 2026 10:11:30 +0100 Subject: [PATCH 03/13] Add `Ui::is_tooltip` (#8016) --- crates/egui/src/ui.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d55f4174f..0caf7bec5 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -487,6 +487,12 @@ impl Ui { &mut self.style_mut().visuals } + /// Is this [`Ui`] in a tooltip? + #[inline] + pub fn is_tooltip(&self) -> bool { + self.layer_id().order == Order::Tooltip + } + /// Get a reference to this [`Ui`]'s [`UiStack`]. #[inline] pub fn stack(&self) -> &Arc { From 0887b54c93b19aa5ae0054bb5da65bca8741bb62 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 25 Mar 2026 10:54:17 +0100 Subject: [PATCH 04/13] Add `eframe::WindowChromeMetrics` (macOS only) (#8015) When using `egui::ViewportBuilder::with_fullsize_content_view` one must be careful not to paint anything where the "traffic light" buttons are: image `eframe::WindowChromeMetrics` helps you with that! --- crates/eframe/src/lib.rs | 3 ++ crates/eframe/src/native/macos.rs | 76 +++++++++++++++++++++++++++++++ crates/eframe/src/native/mod.rs | 3 ++ 3 files changed, 82 insertions(+) create mode 100644 crates/eframe/src/native/macos.rs diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 151fb79ce..86260ee5f 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -190,6 +190,9 @@ pub use web::{WebLogger, WebRunner}; #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] mod native; +#[cfg(target_os = "macos")] +pub use native::macos::WindowChromeMetrics; + #[cfg(not(target_arch = "wasm32"))] #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use native::run::EframeWinitApplication; diff --git a/crates/eframe/src/native/macos.rs b/crates/eframe/src/native/macos.rs new file mode 100644 index 000000000..b1f2552e5 --- /dev/null +++ b/crates/eframe/src/native/macos.rs @@ -0,0 +1,76 @@ +use egui::Vec2; +use objc2_app_kit::{NSView, NSWindow, NSWindowButton}; +use raw_window_handle::{AppKitWindowHandle, RawWindowHandle}; + +/// Size of the "traffic lights" (red/yellow/green close/minimize/maximize buttons) +/// on the native macOS window. +/// +/// This is very useful together with [`egui::ViewportBuilder::with_fullsize_content_view`]. +#[derive(Debug)] +pub struct WindowChromeMetrics { + /// Size of the "traffic lights" (red/yellow/green close/minimize/maximize buttons), + /// including margins. + /// + /// The unit here is in "native scale", which means it needs to be divided by [`egui::Context::zoom_factor`] + /// to get the size in egui points. + pub traffic_lights_size: Vec2, +} + +impl WindowChromeMetrics { + /// Get the window chrome metrics for a given window handle. + pub fn from_window_handle(window_handle: &RawWindowHandle) -> Option { + window_chrome_metrics(window_handle) + } +} + +fn window_chrome_metrics(window_handle: &RawWindowHandle) -> Option { + let RawWindowHandle::AppKit(appkit_handle) = window_handle else { + return None; + }; + + let ns_view = ns_view_from_handle(appkit_handle)?; + let ns_window = ns_view.window()?; + + Some(WindowChromeMetrics { + traffic_lights_size: traffic_lights_metrics(&ns_window)?, + }) +} + +fn traffic_lights_metrics(ns_window: &NSWindow) -> Option { + // Button order is CloseButton, MiniaturizeButton, ZoomButton: + let close_button = ns_window + .standardWindowButton(NSWindowButton::CloseButton)? + .frame(); + let zoom_button = ns_window + .standardWindowButton(NSWindowButton::ZoomButton)? + .frame(); + + let left_margin = close_button.origin.x; + let right_margin = left_margin; // for symmetry + + let total_width = zoom_button.origin.x + zoom_button.size.width + right_margin; + + let top_margin = close_button.origin.y; + let bottom_margin = top_margin; // Usually symmetric + let total_height = top_margin + close_button.size.height + bottom_margin; + + Some(Vec2::new(total_width as f32, total_height as f32)) +} + +fn ns_view_from_handle(handle: &AppKitWindowHandle) -> Option<&NSView> { + let ns_view_ptr = handle.ns_view.as_ptr().cast::(); + + // Validate the pointer is non-null + if ns_view_ptr.is_null() { + None + } else { + // SAFETY: + // - We've verified the pointer is non-null + // - The pointer comes from the windowing system, so it should be valid + // - NSView pointers from AppKit are expected to remain valid for the window lifetime + #[expect(unsafe_code)] + unsafe { + ns_view_ptr.as_ref() + } + } +} diff --git a/crates/eframe/src/native/mod.rs b/crates/eframe/src/native/mod.rs index eb9413717..771964ae7 100644 --- a/crates/eframe/src/native/mod.rs +++ b/crates/eframe/src/native/mod.rs @@ -3,6 +3,9 @@ mod epi_integration; mod event_loop_context; pub mod run; +#[cfg(target_os = "macos")] +pub(crate) mod macos; + /// File storage which can be used by native backends. #[cfg(feature = "persistence")] pub mod file_storage; From e8f04292a9bfed66a8deaaff92efcdd60d2a4142 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 25 Mar 2026 12:41:35 +0100 Subject: [PATCH 05/13] Add `UiStack::bg_color` (#8020) This lets you ask for the background color of a ui with `ui.stack().bg_color()` --- crates/egui/src/ui_stack.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 07026c45b..0686dc086 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -1,6 +1,8 @@ use std::sync::Arc; use std::{any::Any, iter::FusedIterator}; +use epaint::Color32; + use crate::{Direction, Frame, Id, Rect}; /// What kind is this [`crate::Ui`]? @@ -253,6 +255,24 @@ impl UiStack { pub fn has_visible_frame(&self) -> bool { !self.info.frame.stroke.is_empty() } + + /// The background color of this [`Ui`]. + /// + /// This blend together all [`Frame::fill`] colors + /// up to the root. + #[inline] + pub fn bg_color(&self) -> Color32 { + let mut total = Color32::TRANSPARENT; + for node in self.iter() { + let fill = node.frame().fill; + if fill.is_opaque() { + return fill; + } else if fill != Color32::TRANSPARENT { + total = fill.blend(total); + } + } + total + } } // these methods act on the entire stack From 845b8c2f094e434b81a956519f638062faa3d3fc Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 25 Mar 2026 12:46:49 +0100 Subject: [PATCH 06/13] Make `egui::IdSet` public (#8019) It wasn't public before --- crates/egui/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index d86851a1d..e7d1fc25e 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -478,7 +478,7 @@ pub use self::{ drag_and_drop::DragAndDrop, epaint::text::TextWrapMode, grid::Grid, - id::{Id, IdMap}, + id::{Id, IdMap, IdSet}, input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn}, layers::{LayerId, Order}, layout::*, From 0b0c561a813ca03462fd698ad3d880cbfe0c9926 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 25 Mar 2026 12:53:00 +0100 Subject: [PATCH 07/13] Fade out the edges of `ScrollAreas` (#8018) ## Before: image It is very hard here to realize this is a scrollable area ## After image The fade at the bottom tells the user they should try scrolling. You can turn if off with `style.spacing.scroll.fade.enabled` --- crates/egui/src/containers/scroll_area.rs | 96 ++++++++++++++++++- crates/egui/src/layout.rs | 32 +------ crates/egui/src/lib.rs | 2 +- crates/egui/src/style.rs | 49 ++++++++++ crates/egui/src/ui_stack.rs | 2 +- .../tests/snapshots/easymarkeditor.png | 4 +- crates/egui_demo_lib/src/demo/strip_demo.rs | 2 +- .../tests/snapshots/demos/Font Book.png | 4 +- .../tests/snapshots/demos/Panels.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 4 +- .../tests/snapshots/demos/Table.png | 4 +- .../tests/snapshots/demos/Tooltips.png | 4 +- .../snapshots/demos/Window Resize Test.png | 4 +- .../tests/snapshots/test_scroll_initial.png | 4 +- .../tests/snapshots/test_scroll_scrolled.png | 4 +- crates/epaint/src/direction.rs | 27 ++++++ crates/epaint/src/lib.rs | 2 + crates/epaint/src/mesh.rs | 18 +++- crates/epaint/src/shapes/shape.rs | 28 +++++- crates/epaint/src/tessellator.rs | 11 +-- .../snapshots/text_edit_scroll_0_focus.png | 4 +- .../tests/snapshots/text_edit_scroll_1_5.png | 4 +- 22 files changed, 241 insertions(+), 72 deletions(-) create mode 100644 crates/epaint/src/direction.rs diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 2616fb414..6dc264a0a 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -5,7 +5,7 @@ use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; use emath::GuiRounding as _; -use epaint::Margin; +use epaint::{Color32, Direction, Margin, Shape}; use crate::{ Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, @@ -1019,13 +1019,17 @@ impl ScrollArea { .inner; let (content_size, state) = prepared.end(ui); - ScrollAreaOutput { + let output = ScrollAreaOutput { inner, id, state, content_size, inner_rect, - } + }; + + paint_fade_areas(ui, &output); + + output } } @@ -1504,3 +1508,89 @@ impl Prepared { (content_size, state) } } + +/// Paint fade-out gradients at the top and/or bottom of a scroll area to +/// indicate that more content is available beyond the visible region. +fn paint_fade_areas(ui: &Ui, scroll_output: &ScrollAreaOutput) { + let crate::style::ScrollFadeStyle { + enabled, + size: fade_size, + } = ui.spacing().scroll.fade; + + if !enabled { + return; + } + + let bg = ui.stack().bg_color(); + + let offset = scroll_output.state.offset; + let overflow = scroll_output.content_size - scroll_output.inner_rect.size(); + + let paint_rect = scroll_output + .inner_rect + .intersect(ui.min_rect()) + .expand(ui.visuals().clip_rect_margin); + + // Top fade: animate opacity based on how far we've scrolled down. + if 0.0 < offset.y { + let t = (offset.y / fade_size).clamp(0.0, 1.0); + let bg_faded = bg.gamma_multiply(t); + let rect = Rect::from_min_max( + paint_rect.left_top(), + pos2(paint_rect.right(), paint_rect.top() + fade_size), + ); + ui.painter().add(Shape::gradient_rect( + rect, + Direction::TopDown, + [bg_faded, Color32::TRANSPARENT], + )); + } + + // Bottom fade: animate opacity based on distance from the bottom. + let distance_from_bottom = overflow.y - offset.y; + if 0.0 < distance_from_bottom { + let t = (distance_from_bottom / fade_size).clamp(0.0, 1.0); + let bg_faded = bg.gamma_multiply(t); + let rect = Rect::from_min_max( + pos2(paint_rect.left(), paint_rect.bottom() - fade_size), + paint_rect.right_bottom(), + ); + ui.painter().add(Shape::gradient_rect( + rect, + Direction::BottomUp, + [bg_faded, Color32::TRANSPARENT], + )); + } + + // Left fade: animate opacity based on how far we've scrolled right. + + if 0.0 < offset.x { + let t = (offset.x / fade_size).clamp(0.0, 1.0); + let bg_faded = bg.gamma_multiply(t); + let rect = Rect::from_min_max( + paint_rect.left_top(), + pos2(paint_rect.left() + fade_size, paint_rect.bottom()), + ); + ui.painter().add(Shape::gradient_rect( + rect, + Direction::LeftToRight, + [bg_faded, Color32::TRANSPARENT], + )); + } + + // Right fade: animate opacity based on distance from the right edge. + let distance_from_right = overflow.x - offset.x; + if 0.0 < distance_from_right { + let t = (distance_from_right / fade_size).clamp(0.0, 1.0); + let bg_faded = bg.gamma_multiply(t); + let rect = Rect::from_min_max( + pos2(paint_rect.right() - fade_size, paint_rect.top()), + paint_rect.right_bottom(), + ); + ui.painter().add(Shape::gradient_rect( + rect, + Direction::RightToLeft, + [bg_faded, Color32::TRANSPARENT], + )); + } +} diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index c35fd254b..c97652e8d 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -1,7 +1,7 @@ use emath::GuiRounding as _; use crate::{ - Align, + Align, Direction, emath::{Align2, NumExt as _, Pos2, Rect, Vec2, pos2, vec2}, }; const INFINITY: f32 = f32::INFINITY; @@ -87,36 +87,6 @@ impl Region { // ---------------------------------------------------------------------------- -/// Layout direction, one of [`LeftToRight`](Direction::LeftToRight), [`RightToLeft`](Direction::RightToLeft), [`TopDown`](Direction::TopDown), [`BottomUp`](Direction::BottomUp). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub enum Direction { - LeftToRight, - RightToLeft, - TopDown, - BottomUp, -} - -impl Direction { - #[inline(always)] - pub fn is_horizontal(self) -> bool { - match self { - Self::LeftToRight | Self::RightToLeft => true, - Self::TopDown | Self::BottomUp => false, - } - } - - #[inline(always)] - pub fn is_vertical(self) -> bool { - match self { - Self::LeftToRight | Self::RightToLeft => false, - Self::TopDown | Self::BottomUp => true, - } - } -} - -// ---------------------------------------------------------------------------- - /// The layout of a [`Ui`][`crate::Ui`], e.g. "vertical & centered". /// /// ``` diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index e7d1fc25e..cd098eaea 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -449,7 +449,7 @@ pub use emath::{ remap_clamp, vec2, }; pub use epaint::{ - ClippedPrimitive, ColorImage, CornerRadius, ImageData, Margin, Mesh, PaintCallback, + ClippedPrimitive, ColorImage, CornerRadius, Direction, ImageData, Margin, Mesh, PaintCallback, PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 4f9749663..5549cdbc3 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -586,6 +586,8 @@ pub struct ScrollStyle { /// This is only for floating scroll bars. /// Solid scroll bars are always opaque. pub interact_handle_opacity: f32, + + pub fade: ScrollFadeStyle, } impl Default for ScrollStyle { @@ -616,6 +618,8 @@ impl ScrollStyle { dormant_handle_opacity: 0.0, active_handle_opacity: 0.6, interact_handle_opacity: 1.0, + + fade: Default::default(), } } @@ -699,6 +703,8 @@ impl ScrollStyle { dormant_handle_opacity, active_handle_opacity, interact_handle_opacity, + + fade, } = self; ui.horizontal(|ui| { @@ -772,6 +778,49 @@ impl ScrollStyle { ui.label("Inner margin"); }); } + + ui.separator(); + fade.ui(ui); + } +} + +/// Controls if and how to fade out the sides of a [`crate::ScrollArea`] +/// to indicate there is more there if you scroll. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct ScrollFadeStyle { + /// Show fade areas? + pub enabled: bool, + + /// Size of the fade-area (height for vertical scrolling, + /// width for horizontal scrolling). + pub size: f32, +} + +impl Default for ScrollFadeStyle { + fn default() -> Self { + Self { + enabled: true, + size: 20.0, + } + } +} + +impl ScrollFadeStyle { + pub fn ui(&mut self, ui: &mut Ui) { + let Self { enabled, size } = self; + + ui.horizontal(|ui| { + ui.checkbox(enabled, "Fade edges"); + }); + + if *enabled { + ui.horizontal(|ui| { + ui.add(DragValue::new(size).range(0.0..=64.0)); + ui.label("Fade size"); + }); + } } } diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 0686dc086..4debd76bb 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -256,7 +256,7 @@ impl UiStack { !self.info.frame.stroke.is_empty() } - /// The background color of this [`Ui`]. + /// The background color of this [`crate::Ui`]. /// /// This blend together all [`Frame::fill`] colors /// up to the root. diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index a0c1a52de..c90dcc918 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:b9ad01a55950f96a3ae9e48a2c026143d11ffee62bff4f83b4529cd884ce11f0 -size 169683 +oid sha256:65776e4d9e31e7163117166915a0cfaaece98b1dd69695533655dee889ac5597 +size 169277 diff --git a/crates/egui_demo_lib/src/demo/strip_demo.rs b/crates/egui_demo_lib/src/demo/strip_demo.rs index 94df7bb57..69bcce4f6 100644 --- a/crates/egui_demo_lib/src/demo/strip_demo.rs +++ b/crates/egui_demo_lib/src/demo/strip_demo.rs @@ -27,7 +27,7 @@ impl crate::Demo for StripDemo { impl crate::View for StripDemo { fn ui(&mut self, ui: &mut egui::Ui) { let dark_mode = ui.visuals().dark_mode; - let faded_color = ui.visuals().window_fill(); + let faded_color = ui.stack().bg_color(); let faded_color = |color: Color32| -> Color32 { use egui::Rgba; let t = if dark_mode { 0.95 } else { 0.8 }; 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 f73093e3e..af2bb0a0f 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:8c630df841e98043132b920338793745549116890c1bc52d8d10b0def896a1e6 -size 114409 +oid sha256:583003c59f40515a5de435ee1eea2ee6fceae409c7881456401b004c6409896a +size 114575 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 992fb471e..67e44c14e 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:627114dcbda4f3d2255d34926ed0b77c679d248ed1d822d723479b1e6652c67a -size 249258 +oid sha256:8815473873602211a95e0a077dc77560de154a0a1a2f9c1418d90746670075f7 +size 248630 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 24e42cb23..aa354bb0a 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:b5b965a7c690fd8e8646812513e2417170b687fd37e29d220c29127ba0cc200c -size 172609 +oid sha256:2881255fa694b713a3c3049f30143ee60a20b68105fb724fbba188f81d34e572 +size 171778 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 3f72922d2..f9c8f991e 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:931f38ade8373ff79801c05c5d4397f2c5fcfa27022f2e1abe9eb29d561a3aef -size 76022 +oid sha256:63fb9d15956efa69818cfd5398b4dfaa796dfbe1df3fb171b8ae413a6f641c9f +size 76049 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index 23adea69c..3441b6896 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:89986132c5d2a3ccccf0a16cb2b0be07b7c5512838cc10ec9067022e7a238515 -size 63918 +oid sha256:4ac837d03e3e8959196942d030a8511d00cdd0d5ac9bfe761d80b59d32e73581 +size 63967 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 96ca9949e..2e5e8c717 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:9c81d5a5915dac60297293b90cd3fc0d188a9335e99b318996e3e2934de7bee1 -size 483497 +oid sha256:4107ba4569b3e37ce6720595cfc4de60aec11dcfdb835720442b244e121209fc +size 482862 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png index e61dc99a0..1eda6d278 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:eb8737af84c3d3b0c054b7e2a8bcb04685243d84cb13b72a1372dc40dbfd14fb -size 7267 +oid sha256:6f6d516cc3d1439256a19f72153468125bac1647e2e211e841579545570faef7 +size 7366 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png index 0cc3d6d15..87367951c 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1651bb1b9bbaa3c65ecd07c39c57527f4beb4c607581a5b2596a49dcf4c5db3 -size 7996 +oid sha256:2e05d28e5541eb5926b4758e611f5c340311269fcd8b63055ff3a6793abbb140 +size 8279 diff --git a/crates/epaint/src/direction.rs b/crates/epaint/src/direction.rs new file mode 100644 index 000000000..b2f317c37 --- /dev/null +++ b/crates/epaint/src/direction.rs @@ -0,0 +1,27 @@ +/// A cardinal direction, one of [`LeftToRight`](Direction::LeftToRight), [`RightToLeft`](Direction::RightToLeft), [`TopDown`](Direction::TopDown), [`BottomUp`](Direction::BottomUp). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum Direction { + LeftToRight, + RightToLeft, + TopDown, + BottomUp, +} + +impl Direction { + #[inline(always)] + pub fn is_horizontal(self) -> bool { + match self { + Self::LeftToRight | Self::RightToLeft => true, + Self::TopDown | Self::BottomUp => false, + } + } + + #[inline(always)] + pub fn is_vertical(self) -> bool { + match self { + Self::LeftToRight | Self::RightToLeft => false, + Self::TopDown | Self::BottomUp => true, + } + } +} diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 0042e0964..6f574e6b4 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -27,6 +27,7 @@ mod brush; pub mod color; mod corner_radius; mod corner_radius_f32; +mod direction; pub mod image; mod margin; mod margin_f32; @@ -50,6 +51,7 @@ pub use self::{ color::ColorMode, corner_radius::CornerRadius, corner_radius_f32::CornerRadiusF32, + direction::Direction, image::{AlphaFromCoverage, ColorImage, ImageData, ImageDelta}, margin::Margin, margin_f32::*, diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index 5343fdace..d48c98bc2 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -23,6 +23,18 @@ pub struct Vertex { pub color: Color32, // 32 bit } +impl Vertex { + /// An untextured vertex + #[inline] + pub fn untextured(pos: Pos2, color: Color32) -> Self { + Self { + pos, + uv: WHITE_UV, + color, + } + } +} + #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[cfg(all(feature = "unity", not(feature = "_override_unity")))] @@ -159,11 +171,7 @@ impl Mesh { self.texture_id == TextureId::default(), "Mesh has an assigned texture" ); - self.vertices.push(Vertex { - pos, - uv: WHITE_UV, - color, - }); + self.vertices.push(Vertex::untextured(pos, color)); } /// Add a triangle. diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index fa8a3e75c..f5ca6a325 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use emath::{Align2, Pos2, Rangef, Rect, TSTransform, Vec2, pos2}; use crate::{ - Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId, + Color32, CornerRadius, Direction, Mesh, Stroke, StrokeKind, TextureId, Vertex, stroke::PathStroke, text::{FontId, FontsView, Galley}, }; @@ -297,6 +297,32 @@ impl Shape { Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind)) } + /// Paints a gradient rectangle that transitions from `color_from` to `color_to` + /// along the given `direction`. + /// + /// For example, [`Direction::TopDown`] paints `color_from` at the top edge fading + /// to `color_to` at the bottom edge. + #[inline] + pub fn gradient_rect(rect: Rect, direction: Direction, [from, to]: [Color32; 2]) -> Self { + let (left_top, right_top, left_bottom, right_bottom) = match direction { + Direction::TopDown => (from, from, to, to), + Direction::BottomUp => (to, to, from, from), + Direction::LeftToRight => (from, to, from, to), + Direction::RightToLeft => (to, from, to, from), + }; + + Self::from(Mesh { + indices: vec![0, 1, 2, 2, 1, 3], + vertices: vec![ + Vertex::untextured(rect.left_top(), left_top), + Vertex::untextured(rect.right_top(), right_top), + Vertex::untextured(rect.left_bottom(), left_bottom), + Vertex::untextured(rect.right_bottom(), right_bottom), + ], + texture_id: Default::default(), + }) + } + #[expect(clippy::needless_pass_by_value)] pub fn text( fonts: &mut FontsView<'_>, diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 9256ae16e..a732402d7 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -10,8 +10,8 @@ use emath::{GuiRounding as _, NumExt as _, Pos2, Rect, Rot2, Vec2, pos2, remap, use crate::{ CircleShape, ClippedPrimitive, ClippedShape, Color32, CornerRadiusF32, CubicBezierShape, EllipseShape, Mesh, PathShape, Primitive, QuadraticBezierShape, RectShape, Shape, Stroke, - StrokeKind, TextShape, TextureId, Vertex, WHITE_UV, color::ColorMode, emath, - stroke::PathStroke, texture_atlas::PreparedDisc, + StrokeKind, TextShape, TextureId, Vertex, color::ColorMode, emath, stroke::PathStroke, + texture_atlas::PreparedDisc, }; // ---------------------------------------------------------------------------- @@ -809,11 +809,8 @@ fn fill_closed_path(feathering: f32, path: &mut [PathPoint], fill_color: Color32 } else { out.reserve_triangles(n as usize); let idx = out.vertices.len() as u32; - out.vertices.extend(path.iter().map(|p| Vertex { - pos: p.pos, - uv: WHITE_UV, - color: fill_color, - })); + out.vertices + .extend(path.iter().map(|p| Vertex::untextured(p.pos, fill_color))); for i in 2..n { out.add_triangle(idx, idx + i - 1, idx + i); } diff --git a/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png b/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png index 1d0a5ed46..da3039325 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png +++ b/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81620caad6d420f3bd0f224e5b07a02960a42436208a98d3aa012e5db61a743a -size 1510 +oid sha256:5dfd9b576e0ab4b47a0f8c8acfc2664f92faa88cc7fb088409bff359fa1dfadd +size 1861 diff --git a/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png b/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png index bdb8d1b1b..ecba0fcc1 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png +++ b/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f915eafb6490ff456c5b0a7c74c38ef143262bdf74a0c6561b9cf6ee66a679ea -size 1501 +oid sha256:4529530bb46af68260ca910a91f888e8b296790be2b976b450cec884799f53b4 +size 1953 From d232be740ffa69ca4ae0da3d416aa118d24f0f08 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 25 Mar 2026 13:40:54 +0100 Subject: [PATCH 08/13] Fix bug in ui stack color blending (#8021) Co-authored-by: Emil Ernerfeldt --- crates/egui/src/ui_stack.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 4debd76bb..d72b3ea0b 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -265,10 +265,11 @@ impl UiStack { let mut total = Color32::TRANSPARENT; for node in self.iter() { let fill = node.frame().fill; - if fill.is_opaque() { - return fill; - } else if fill != Color32::TRANSPARENT { + if fill != Color32::TRANSPARENT { total = fill.blend(total); + if total.is_opaque() { + break; + } } } total From 02ff040b74cef45876fb8cc7534474351a48a8d6 Mon Sep 17 00:00:00 2001 From: eason <85663565+mango766@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:48:22 +0800 Subject: [PATCH 09/13] Fix: `Visuals::interact_cursor` support in `Button` (#7986) Closes #7947 ## Problem `Visuals::interact_cursor` stopped working for buttons after the `AtomLayout` refactor in commit 6eb7bb6e. Setting `interact_cursor` to e.g. `CursorIcon::PointingHand` no longer changes the cursor when hovering over a `Button`. ## Root Cause When `Button` was rewritten to use `AtomLayout` in #5830, the cursor-override block at the end of the old `Button::ui` was not carried over to the new `Button::atom_ui` method. The old code had: ```rust if let Some(cursor) = ui.visuals().interact_cursor { if response.hovered() { ui.ctx().set_cursor_icon(cursor); } } ``` This was the only place `interact_cursor` was checked, so the setting became entirely non-functional. ## Fix Re-add the same `interact_cursor` check in `Button::atom_ui`, right after painting and before `widget_info`, matching the original behavior. --------- Co-authored-by: easonysliu Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widgets/button.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 7d9dddf0d..8ef047002 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -371,6 +371,12 @@ impl<'a> Button<'a> { AtomLayoutResponse::empty(prepared.response) }; + if let Some(cursor) = ui.visuals().interact_cursor + && response.response.hovered() + { + ui.ctx().set_cursor_icon(cursor); + } + response.response.widget_info(|| { if let Some(text) = &text { WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), text) From 048f8ccd2af21a0074ac2a2f62b2d08f917bbb44 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 25 Mar 2026 15:44:41 +0100 Subject: [PATCH 10/13] Tweak `ScrollArea` fade effect (#8023) * Follow-up to https://github.com/emilk/egui/pull/8018 --- crates/egui/src/containers/scroll_area.rs | 13 ++++++------- crates/egui/src/style.rs | 15 +++++++++------ .../tests/snapshots/easymarkeditor.png | 4 ++-- .../tests/snapshots/demos/Font Book.png | 4 ++-- .../tests/snapshots/demos/Panels.png | 4 ++-- .../tests/snapshots/demos/Scrolling.png | 4 ++-- .../egui_demo_lib/tests/snapshots/demos/Table.png | 4 ++-- .../tests/snapshots/demos/Tooltips.png | 4 ++-- .../tests/snapshots/demos/Window Resize Test.png | 4 ++-- .../tests/snapshots/test_scroll_initial.png | 4 ++-- .../tests/snapshots/test_scroll_scrolled.png | 4 ++-- .../tests/snapshots/text_edit_scroll_0_focus.png | 4 ++-- .../tests/snapshots/text_edit_scroll_1_5.png | 4 ++-- 13 files changed, 37 insertions(+), 35 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 6dc264a0a..b99bcf5da 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1513,11 +1513,11 @@ impl Prepared { /// indicate that more content is available beyond the visible region. fn paint_fade_areas(ui: &Ui, scroll_output: &ScrollAreaOutput) { let crate::style::ScrollFadeStyle { - enabled, + strength, size: fade_size, } = ui.spacing().scroll.fade; - if !enabled { + if strength <= 0.0 { return; } @@ -1533,7 +1533,7 @@ fn paint_fade_areas(ui: &Ui, scroll_output: &ScrollAreaOutput) { // Top fade: animate opacity based on how far we've scrolled down. if 0.0 < offset.y { - let t = (offset.y / fade_size).clamp(0.0, 1.0); + let t = (offset.y / fade_size).clamp(0.0, 1.0) * strength; let bg_faded = bg.gamma_multiply(t); let rect = Rect::from_min_max( paint_rect.left_top(), @@ -1549,7 +1549,7 @@ fn paint_fade_areas(ui: &Ui, scroll_output: &ScrollAreaOutput) { // Bottom fade: animate opacity based on distance from the bottom. let distance_from_bottom = overflow.y - offset.y; if 0.0 < distance_from_bottom { - let t = (distance_from_bottom / fade_size).clamp(0.0, 1.0); + let t = (distance_from_bottom / fade_size).clamp(0.0, 1.0) * strength; let bg_faded = bg.gamma_multiply(t); let rect = Rect::from_min_max( pos2(paint_rect.left(), paint_rect.bottom() - fade_size), @@ -1563,9 +1563,8 @@ fn paint_fade_areas(ui: &Ui, scroll_output: &ScrollAreaOutput) { } // Left fade: animate opacity based on how far we've scrolled right. - if 0.0 < offset.x { - let t = (offset.x / fade_size).clamp(0.0, 1.0); + let t = (offset.x / fade_size).clamp(0.0, 1.0) * strength; let bg_faded = bg.gamma_multiply(t); let rect = Rect::from_min_max( paint_rect.left_top(), @@ -1581,7 +1580,7 @@ fn paint_fade_areas(ui: &Ui, scroll_output: &ScrollAreaOutput) { // Right fade: animate opacity based on distance from the right edge. let distance_from_right = overflow.x - offset.x; if 0.0 < distance_from_right { - let t = (distance_from_right / fade_size).clamp(0.0, 1.0); + let t = (distance_from_right / fade_size).clamp(0.0, 1.0) * strength; let bg_faded = bg.gamma_multiply(t); let rect = Rect::from_min_max( pos2(paint_rect.right() - fade_size, paint_rect.top()), diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 5549cdbc3..f7b889506 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -790,8 +790,10 @@ impl ScrollStyle { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ScrollFadeStyle { - /// Show fade areas? - pub enabled: bool, + /// Opacity of the fade effect at the outer edge, in 0.0-1.0. + /// + /// Set to 0.0 to disable the fade effect. + pub strength: f32, /// Size of the fade-area (height for vertical scrolling, /// width for horizontal scrolling). @@ -801,7 +803,7 @@ pub struct ScrollFadeStyle { impl Default for ScrollFadeStyle { fn default() -> Self { Self { - enabled: true, + strength: 0.5, size: 20.0, } } @@ -809,13 +811,14 @@ impl Default for ScrollFadeStyle { impl ScrollFadeStyle { pub fn ui(&mut self, ui: &mut Ui) { - let Self { enabled, size } = self; + let Self { strength, size } = self; ui.horizontal(|ui| { - ui.checkbox(enabled, "Fade edges"); + ui.add(DragValue::new(strength).speed(0.01).range(0.0..=1.0)); + ui.label("Fade strength"); }); - if *enabled { + if 0.0 < *strength { ui.horizontal(|ui| { ui.add(DragValue::new(size).range(0.0..=64.0)); ui.label("Fade size"); diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index c90dcc918..5118065c4 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:65776e4d9e31e7163117166915a0cfaaece98b1dd69695533655dee889ac5597 -size 169277 +oid sha256:84f0e72ce337d56f3767ebed1ab6a47f3d27c9fbcce4d8a19aeab358e12920f5 +size 169664 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 af2bb0a0f..ac85845f9 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:583003c59f40515a5de435ee1eea2ee6fceae409c7881456401b004c6409896a -size 114575 +oid sha256:deff441cd1d9142352f8759dff4b759f4572f0ddf93752349314da77abe4b254 +size 115028 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 67e44c14e..70e7ce90e 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:8815473873602211a95e0a077dc77560de154a0a1a2f9c1418d90746670075f7 -size 248630 +oid sha256:a46457b23b7b32694564d03b42bccac2f017a756225bc54b508bb6fe2ad8ee7b +size 249548 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index aa354bb0a..0924d3aa9 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:2881255fa694b713a3c3049f30143ee60a20b68105fb724fbba188f81d34e572 -size 171778 +oid sha256:927a497e8b6f9ce3b71dcb67086f477e19d327c163b2b8ad868af10009c2faf2 +size 172981 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index f9c8f991e..727c01434 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:63fb9d15956efa69818cfd5398b4dfaa796dfbe1df3fb171b8ae413a6f641c9f -size 76049 +oid sha256:db4c0cf1c4cdae3d416afce5c58efd1cc382be86431e547fa66bcc95a0a17ddb +size 76364 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index 3441b6896..e7ed07b04 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:4ac837d03e3e8959196942d030a8511d00cdd0d5ac9bfe761d80b59d32e73581 -size 63967 +oid sha256:57018beba5e4fb4f1e6de9c58bf898560b3a7669159d5bad91a4e2382ef57ce0 +size 64004 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 2e5e8c717..c769f61bd 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:4107ba4569b3e37ce6720595cfc4de60aec11dcfdb835720442b244e121209fc -size 482862 +oid sha256:999f9cc006302b8951d97b510a02f1209969c376ecc7909ed5d7b46da27c0637 +size 483753 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_initial.png b/crates/egui_kittest/tests/snapshots/test_scroll_initial.png index 1eda6d278..64ed153a6 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:6f6d516cc3d1439256a19f72153468125bac1647e2e211e841579545570faef7 -size 7366 +oid sha256:6154c8bb550575bcb9fa0bba06da4d47079a00dffc5754b62ef2a6e7529e2090 +size 7489 diff --git a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png index 87367951c..a87f09545 100644 --- a/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png +++ b/crates/egui_kittest/tests/snapshots/test_scroll_scrolled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e05d28e5541eb5926b4758e611f5c340311269fcd8b63055ff3a6793abbb140 -size 8279 +oid sha256:df2578c198b29950254ec62c6cc615a4b1c003e7ae3ea027da22fc868b392c74 +size 8342 diff --git a/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png b/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png index da3039325..019154ba6 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png +++ b/tests/egui_tests/tests/snapshots/text_edit_scroll_0_focus.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dfd9b576e0ab4b47a0f8c8acfc2664f92faa88cc7fb088409bff359fa1dfadd -size 1861 +oid sha256:6f2a57ad8dbdd121cb181e74d76db68d800aba8fdc980d8de4e962e1e85fe8f6 +size 1803 diff --git a/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png b/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png index ecba0fcc1..6c1ee1d81 100644 --- a/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png +++ b/tests/egui_tests/tests/snapshots/text_edit_scroll_1_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4529530bb46af68260ca910a91f888e8b296790be2b976b450cec884799f53b4 -size 1953 +oid sha256:43dc457cac18107772dbb7e5773961cf502dd685ce1cb4e94338e6b7daedaa77 +size 1861 From f1236f1c612cd4581db2425e8d8548651e19cfc7 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 25 Mar 2026 19:00:28 +0100 Subject: [PATCH 11/13] Fix missing `objc2-app-kit` features (#8025) Was missing some features --- crates/eframe/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 6219b90fd..0f08eb7f1 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -170,12 +170,16 @@ objc2-app-kit = { workspace = true, default-features = false, features = [ "std", "NSApplication", "NSBitmapImageRep", + "NSButton", + "NSControl", "NSGraphics", "NSImage", "NSImageRep", "NSMenu", "NSMenuItem", "NSResponder", + "NSView", + "NSWindow", ] } # windows: From 1c9f74b8bdec0d68795cde4d7f2827209b5857b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Sch=C3=BCtz?= Date: Thu, 26 Mar 2026 08:03:39 +0100 Subject: [PATCH 12/13] Add raw key methods to TypeIdMap (#8007) This PR fundamentally solves the same problem as https://github.com/emilk/egui/pull/5827 just implemented with a lot less ambition and api surface on my end. It contains the bare minimum amount of changes that I need in order to be able to solve my problem. My Problem: I am still suffering from the problem that the TypeIdMap blows up over a very long time when using my application. (The user generally never turns off the application, it is intended to be just kept running forever, some users also never restart their computers) My application generates a lot of content dynamically so it may for some time display widgets with a certain set of TypeId's + Id's later hiding them. Some of the elements that were hidden may turn visible again once an external event occurs, some may forever be discarded. I do know myself when which sections of the UI have to be purged because they will never become visible again, so this PR contains the minimum amount of necessary functions that allow me to implement this housekeeping logic on my end. The existing facilities are insufficient to handle this as the type T which the TypeId and the hash is derived from are sometimes pub(crate) privates of widget subcrates or even pub(crate) of egui internals itself, so its impossible to manually remove those from the TypeIdMap the only build-in method to remove them is to call "clear" on the TypeIdMap, however that gets rid of everything, even the elements that are still shown and should still be in the TypeIdMap. If the changes in this PR are agreeable to you and you want me to write unit test for the 4 functions that I have added then tell me and I will write the tests for you. If you need anything else changed please tell me. I ran cargo clippy and cargo fmt, but your check.sh does not work on my computer. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/util/id_type_map.rs | 153 ++++++++++++++++++++++------ crates/egui_extras/src/table.rs | 5 +- 2 files changed, 126 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index b1312a1da..76f0da5d7 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -4,7 +4,6 @@ // This will also allow users to pick their own serialization format per type. use std::{any::Any, sync::Arc}; - // ----------------------------------------------------------------------------------------------- /// Like [`std::any::TypeId`], but can be serialized and deserialized. @@ -182,6 +181,18 @@ impl Element { } } + pub fn is_temp(&self) -> bool { + match self { + #[cfg(feature = "persistence")] + Self::Value { serialize_fn, .. } => serialize_fn.is_none(), + + #[cfg(not(feature = "persistence"))] + Self::Value { .. } => true, + + Self::Serialized(_) => false, + } + } + #[inline] pub(crate) fn get_temp(&self) -> Option<&T> { match self { @@ -316,6 +327,41 @@ fn from_ron_str(ron: &str) -> Option { use crate::Id; +/// The key used in [`IdTypeMap`], which is a combination of an [`Id`] and a [`TypeId`]. +/// +/// This key can be used to remove or access values in the [`IdTypeMap`] without +/// knowledge of the `TypeId` `T` that is required for other accessors. +/// +/// [`RawKey`]s make no guarantees about layout or their ability to be persisted. +/// They only produce deterministic results if they are used with the map +/// they were initially obtained from. Using them on other instances of [`IdTypeMap`] +/// may produce unexpected behavior. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct RawKey(u64); + +impl nohash_hasher::IsEnabled for RawKey {} + +impl RawKey { + /// Create a new key for the given type. + /// + /// Note that two keys with the same id but different types + /// will be different keys. + /// + /// ``` + /// use egui::{Id, util::id_type_map::RawKey}; + /// assert_ne!( + /// RawKey::new::(Id::NULL), + /// RawKey::new::(Id::NULL), + /// ); + /// ``` + #[inline(always)] + pub fn new(id: Id) -> Self { + let type_id = TypeId::of::(); + Self(type_id.value() ^ id.value()) + } +} + // TODO(emilk): make IdTypeMap generic over the key (`Id`), and make a library of IdTypeMap. /// Stores values identified by an [`Id`] AND the [`std::any::TypeId`] of the value. /// @@ -358,7 +404,7 @@ use crate::Id; #[derive(Clone, Debug)] // We use `id XOR typeid` as a key, so we don't need to hash again! pub struct IdTypeMap { - map: nohash_hasher::IntMap, + map: nohash_hasher::IntMap, max_bytes_per_type: usize, } @@ -375,16 +421,23 @@ impl Default for IdTypeMap { impl IdTypeMap { /// Insert a value that will not be persisted. #[inline] - pub fn insert_temp(&mut self, id: Id, value: T) { - let hash = hash(TypeId::of::(), id); - self.map.insert(hash, Element::new_temp(value)); + pub fn insert_temp( + &mut self, + id: Id, + value: T, + ) -> RawKey { + let key = RawKey::new::(id); + self.map.insert(key, Element::new_temp(value)); + key } /// Insert a value that will be persisted next time you start the app. #[inline] pub fn insert_persisted(&mut self, id: Id, value: T) { - let hash = hash(TypeId::of::(), id); - self.map.insert(hash, Element::new_persisted(value)); + let key = RawKey::new::(id); + self.map.insert(key, Element::new_persisted(value)); + // We don't yet return the key here, because currently all our `raw` + // methods are only for temporary values. } /// Read a value without trying to deserialize a persisted value. @@ -392,8 +445,28 @@ impl IdTypeMap { /// The call clones the value (if found), so make sure it is cheap to clone! #[inline] pub fn get_temp(&self, id: Id) -> Option { - let hash = hash(TypeId::of::(), id); - self.map.get(&hash).and_then(|x| x.get_temp()).cloned() + let key = RawKey::new::(id); + self.map.get(&key).and_then(|x| x.get_temp()).cloned() + } + + /// Gets a reference to a value for a given raw key. + /// + /// Serialized values are ignored. + pub fn get_temp_raw(&self, raw: RawKey) -> Option<&(dyn Any + Send + Sync)> { + match self.map.get(&raw)? { + Element::Value { value, .. } => Some(value.as_ref()), + Element::Serialized(_) => None, + } + } + + /// Gets a mutable reference to a value for a given raw key. + /// + /// Serialized values are ignored. + pub fn get_temp_raw_mut(&mut self, raw: RawKey) -> Option<&mut (dyn Any + Send + Sync)> { + match self.map.get_mut(&raw)? { + Element::Value { value, .. } => Some(value.as_mut()), + Element::Serialized(_) => None, + } } /// Read a value, optionally deserializing it if available. @@ -404,9 +477,9 @@ impl IdTypeMap { /// The call clones the value (if found), so make sure it is cheap to clone! #[inline] pub fn get_persisted(&mut self, id: Id) -> Option { - let hash = hash(TypeId::of::(), id); + let key = RawKey::new::(id); self.map - .get_mut(&hash) + .get_mut(&key) .and_then(|x| x.get_mut_persisted()) .cloned() } @@ -443,9 +516,9 @@ impl IdTypeMap { id: Id, insert_with: impl FnOnce() -> T, ) -> &mut T { - let hash = hash(TypeId::of::(), id); + let key = RawKey::new::(id); use std::collections::hash_map::Entry; - match self.map.entry(hash) { + match self.map.entry(key) { Entry::Vacant(vacant) => { // this unwrap will never panic, because we insert correct type right now #[expect(clippy::unwrap_used)] @@ -465,9 +538,9 @@ impl IdTypeMap { id: Id, insert_with: impl FnOnce() -> T, ) -> &mut T { - let hash = hash(TypeId::of::(), id); + let key = RawKey::new::(id); use std::collections::hash_map::Entry; - match self.map.entry(hash) { + match self.map.entry(key) { Entry::Vacant(vacant) => { // this unwrap will never panic, because we insert correct type right now #[expect(clippy::unwrap_used)] @@ -486,7 +559,7 @@ impl IdTypeMap { #[cfg(feature = "persistence")] #[allow(clippy::allow_attributes, unused)] fn get_generation(&self, id: Id) -> Option { - let element = self.map.get(&hash(TypeId::of::(), id))?; + let element = self.map.get(&RawKey::new::(id))?; match element { Element::Value { .. } => Some(0), Element::Serialized(SerializedElement { generation, .. }) => Some(*generation), @@ -496,18 +569,33 @@ impl IdTypeMap { /// Remove the state of this type and id. #[inline] pub fn remove(&mut self, id: Id) { - let hash = hash(TypeId::of::(), id); - self.map.remove(&hash); + let key = RawKey::new::(id); + self.map.remove(&key); } /// Remove and fetch the state of this type and id. #[inline] pub fn remove_temp(&mut self, id: Id) -> Option { - let hash = hash(TypeId::of::(), id); - let mut element = self.map.remove(&hash)?; + let key = RawKey::new::(id); + let mut element = self.map.remove(&key)?; Some(std::mem::take(element.get_mut_temp()?)) } + /// Remove a temporary value given a raw key. + /// + /// Serialized values are ignored. + pub fn remove_temp_raw(&mut self, raw: RawKey) -> Option> { + use std::collections::hash_map::Entry; + if let Entry::Occupied(e) = self.map.entry(raw) + && e.get().is_temp() + && let Element::Value { value, .. } = e.remove() + { + Some(value) + } else { + None + } + } + /// Note all state of the given type. pub fn remove_by_type(&mut self) { let key = TypeId::of::(); @@ -532,6 +620,18 @@ impl IdTypeMap { self.map.len() } + /// Returns all [`RawKey`]s to values in this map. + /// + /// The returned keys can only be used with this map. + /// + /// Serializable values will be ignored. + pub fn temp_keys(&self) -> impl Iterator { + self.map + .iter() + .filter(|(_, v)| v.is_temp()) + .map(|(k, _)| *k) + } + /// Count how many values are stored but not yet deserialized. #[inline] pub fn count_serialized(&self) -> usize { @@ -576,11 +676,6 @@ impl IdTypeMap { } } -#[inline(always)] -fn hash(type_id: TypeId, id: Id) -> u64 { - type_id.value() ^ id.value() -} - // ---------------------------------------------------------------------------- /// How [`IdTypeMap`] is persisted. @@ -613,13 +708,13 @@ impl PersistedMap { { profiling::scope!("gather"); - for (hash, element) in &map.map { + for (key, element) in &map.map { if let Some(element) = element.to_serialize() { let stats = types_map.entry(element.type_id).or_default(); stats.num_bytes += element.ron.len(); let generation_stats = stats.generations.entry(element.generation).or_default(); generation_stats.num_bytes += element.ron.len(); - generation_stats.elements.push((*hash, element)); + generation_stats.elements.push((key.0, element)); } else { // temporary value that shouldn't be serialized } @@ -659,7 +754,7 @@ impl PersistedMap { .into_iter() .map( |( - hash, + raw, SerializedElement { type_id, ron, @@ -667,7 +762,7 @@ impl PersistedMap { }, )| { ( - hash, + RawKey(raw), Element::Serialized(SerializedElement { type_id, ron, diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 78c6e7435..25257421a 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -656,14 +656,13 @@ impl TableState { } fn store(self, ui: &egui::Ui, state_id: egui::Id) { - #![expect(clippy::needless_return)] #[cfg(feature = "serde")] { - return ui.data_mut(|d| d.insert_persisted(state_id, self)); + ui.data_mut(|d| d.insert_persisted(state_id, self)); } #[cfg(not(feature = "serde"))] { - return ui.data_mut(|d| d.insert_temp(state_id, self)); + ui.data_mut(|d| d.insert_temp(state_id, self)); } } From 82a578e58c7ad8188c53a8fdc5bf6d4bc789e620 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 26 Mar 2026 12:15:41 +0100 Subject: [PATCH 13/13] Release 0.34.0 - More `Ui`, less `Context` (#8028) Co-authored-by: Emil Ernerfeldt --- CHANGELOG.md | 122 +++++++++++++++++++++++ Cargo.lock | 32 +++--- Cargo.toml | 26 ++--- crates/ecolor/CHANGELOG.md | 4 + crates/eframe/CHANGELOG.md | 24 +++++ crates/egui-wgpu/CHANGELOG.md | 14 +++ crates/egui-winit/CHANGELOG.md | 7 ++ crates/egui_extras/CHANGELOG.md | 8 ++ crates/egui_glow/CHANGELOG.md | 4 + crates/egui_kittest/CHANGELOG.md | 7 ++ crates/emath/CHANGELOG.md | 4 + crates/epaint/CHANGELOG.md | 19 ++++ crates/epaint_default_fonts/CHANGELOG.md | 4 + 13 files changed, 246 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12010e287..7b6c63ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,128 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 + +### Highlights from this release +- Sharper text unlocked by switching font rendering crate to [`skrifa`](https://crates.io/crates/skrifa) +- Fade out edges of `ScrollArea`s +- Use `Ui` as the main entrypoint + +### Skrifa and font hinting +The font rendering backend was switched from `ab_glyph` to `skrifa` + `vello_cpu`. This enabled us support +font hinting and variations. It also paves the way for more font improvements in the future, like support for color +emojis and adding helpers for variations like `RichText::bold`. + +Font hinting makes text more clear (look at the =): + +https://github.com/user-attachments/assets/ea9151ec-869f-4c05-ab59-836114683417 + +We now support setting variable font parameters: + +https://github.com/user-attachments/assets/0febde1c-ebf6-4d85-8f96-86ec0f934ecf + +(Unfortunately there is currently a bug with variations, meaning changing them live like this won't work in practise. +There is a [draft PR](https://github.com/emilk/egui/pull/8029) to fix it, but it didn't make the release) + +* Replace ab_glyph with Skrifa + vello_cpu; enable font hinting [#7694](https://github.com/emilk/egui/pull/7694) by [@valadaptive](https://github.com/valadaptive) +* Add font variations API [#7859](https://github.com/emilk/egui/pull/7859) by [@valadaptive](https://github.com/valadaptive) + +### More `Ui`, less `Context` +egui has long had a confusing overlap in responsibilities between `Context` and `Ui`. +In particular, you could add panels to either one (or both!). +In this release, we switch from having `Context` be the main entrypoint, and instead provide whole-app `Ui`. +In egui we've replaced `Context::run` with `Context::run_ui`, and changed viewports to be given a `&mut Ui` instead of `Context`. +In `eframe` we've deprecated `App::update` replaced it with `App::ui` (which provides a `&mut Ui` instead of a `&Context`). + +In addition to this, `Ui` now derefs to `Context`, so all code like `ui.ctx().input(…)` can now be written `ui.input(…)`. +This means you are much less likely to have to use naked `Context`s. +`Context` can still be useful though, since they implement `Clone` and can be sent to other threads so you can call `.request_repaint` on them. + +* Add `Context::run_ui` [#7736](https://github.com/emilk/egui/pull/7736) by [@emilk](https://github.com/emilk) +* Add `Deref` for `Ui` [#7770](https://github.com/emilk/egui/pull/7770) by [@emilk](https://github.com/emilk) +* Replace `App::update` with `fn logic` and `fn ui` [#7775](https://github.com/emilk/egui/pull/7775) by [@emilk](https://github.com/emilk) +* Rename `Context::style` to `global_style`; avoid confusion w/ `Ui::style` [#7772](https://github.com/emilk/egui/pull/7772) by [@emilk](https://github.com/emilk) +* Rename functions in `Context` to avoid confusion [#7773](https://github.com/emilk/egui/pull/7773) by [@emilk](https://github.com/emilk) +* Viewports: give the caller a `Ui` instead of `Context` [#7779](https://github.com/emilk/egui/pull/7779) by [@emilk](https://github.com/emilk) + +### Changed panel API +As part of the above work, we have unified the panel API. +`SidePanel` and `TopBottomPanel` are deprecated, replaced by a single `Panel`. +Furthermore, it is now deprecated to use panels directly on `Context`. Use the `show_inside` functions instead, acting on `Ui`s. + +This unification and simplification will make it easier to maintain and improve panels going forward. + +* Add `Panel` to replace `SidePanel` and `TopBottomPanel` [#5659](https://github.com/emilk/egui/pull/5659) by [@sharky98](https://github.com/sharky98) +* Deprecate using `Panel` directly on a `Context` [#7781](https://github.com/emilk/egui/pull/7781) by [@emilk](https://github.com/emilk) +* Deprecate `CentralPanel::show` [#7783](https://github.com/emilk/egui/pull/7783) by [@emilk](https://github.com/emilk) +* Deprecate `Context::used_size` and `Context::available_rect` [#7788](https://github.com/emilk/egui/pull/7788) by [@emilk](https://github.com/emilk) + +### ⭐ Added +* Add `is_scrolling`/`is_smooth_scrolling` util, checking for active scroll action [#7669](https://github.com/emilk/egui/pull/7669) by [@IsseW](https://github.com/IsseW) +* Allow multiple atoms in `Button::shortcut_text` and `right_text` [#7696](https://github.com/emilk/egui/pull/7696) by [@emilk](https://github.com/emilk) +* Add `ScrollArea::content_margin` [#7722](https://github.com/emilk/egui/pull/7722) by [@emilk](https://github.com/emilk) +* Per-widget style [#7667](https://github.com/emilk/egui/pull/7667) by [@AdrienZianne](https://github.com/AdrienZianne) +* Plugin: export `TypedPluginGuard` and `TypedPluginHandle` [#7780](https://github.com/emilk/egui/pull/7780) by [@apekros](https://github.com/apekros) +* Add `ViewportInfo::occluded` and `visible` [#7948](https://github.com/emilk/egui/pull/7948) by [@emilk](https://github.com/emilk) +* Add `Atom` prefix/suffix support to `DragValue` [#7949](https://github.com/emilk/egui/pull/7949) by [@lucasmerlin](https://github.com/lucasmerlin) +* ⚠️ Atom improvements: `Atom::id`, `align`, `closure`, `max_size` [#7958](https://github.com/emilk/egui/pull/7958) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `DebugOptions::warn_if_rect_changes_id` [#7984](https://github.com/emilk/egui/pull/7984) by [@emilk](https://github.com/emilk) +* `TextEdit` `Atom` prefix/suffix [#7587](https://github.com/emilk/egui/pull/7587) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `Button::left_text` [#7955](https://github.com/emilk/egui/pull/7955) by [@rustbasic](https://github.com/rustbasic) +* Add `Response::parent_id` [#8010](https://github.com/emilk/egui/pull/8010) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add `Context::text_edit_focused` [#8014](https://github.com/emilk/egui/pull/8014) by [@emilk](https://github.com/emilk) +* Add `Context::time` [#8017](https://github.com/emilk/egui/pull/8017) by [@emilk](https://github.com/emilk) +* Add `Ui::is_tooltip` [#8016](https://github.com/emilk/egui/pull/8016) by [@emilk](https://github.com/emilk) +* Add `UiStack::bg_color` [#8020](https://github.com/emilk/egui/pull/8020) by [@emilk](https://github.com/emilk) +* Make `egui::IdSet` public [#8019](https://github.com/emilk/egui/pull/8019) by [@lucasmerlin](https://github.com/lucasmerlin) +* Add raw key methods to TypeIdMap [#8007](https://github.com/emilk/egui/pull/8007) by [@AlexanderSchuetz97](https://github.com/AlexanderSchuetz97) + +### 🔧 Changed +* Remove `accesskit` feature and always depend on `accesskit` [#7701](https://github.com/emilk/egui/pull/7701) by [@emilk](https://github.com/emilk) +* Update MSRV from 1.88 to 1.92 [#7793](https://github.com/emilk/egui/pull/7793) by [@JasperBRiedel](https://github.com/JasperBRiedel) +* Improve modifier handling when scrolling [#7678](https://github.com/emilk/egui/pull/7678) by [@emilk](https://github.com/emilk) +* Apply preferred font weight when loading variable fonts [#7790](https://github.com/emilk/egui/pull/7790) by [@pmnxis](https://github.com/pmnxis) +* Make scroll bars and resize splitters visible to accesskit [#7804](https://github.com/emilk/egui/pull/7804) by [@emilk](https://github.com/emilk) +* Allow moving existing widgets to the top of interaction stack [#7805](https://github.com/emilk/egui/pull/7805) by [@emilk](https://github.com/emilk) +* Slightly change interact behavior around thin splitters [#7806](https://github.com/emilk/egui/pull/7806) by [@emilk](https://github.com/emilk) +* Move window resize interaction to be over contents [#7807](https://github.com/emilk/egui/pull/7807) by [@emilk](https://github.com/emilk) +* Don't expand widgets on hover [#7808](https://github.com/emilk/egui/pull/7808) by [@emilk](https://github.com/emilk) +* Make `FrameCache::get` return a reference instead of cloning the cached value [#7834](https://github.com/emilk/egui/pull/7834) by [@KonaeAkira](https://github.com/KonaeAkira) +* Update selected dependencies [#7920](https://github.com/emilk/egui/pull/7920) by [@oscargus](https://github.com/oscargus) +* Make `Galley::pos_from_layout_cursor` `pub` [#7864](https://github.com/emilk/egui/pull/7864) by [@dionb](https://github.com/dionb) +* Update accesskit to 0.24.0 (and related deps) [#7850](https://github.com/emilk/egui/pull/7850) by [@delan](https://github.com/delan) +* Quit on Ctrl-Q [#7985](https://github.com/emilk/egui/pull/7985) by [@emilk](https://github.com/emilk) +* Fade out the edges of `ScrollAreas` [#8018](https://github.com/emilk/egui/pull/8018) by [@emilk](https://github.com/emilk) + +### 🔥 Removed +* Remove `CacheTrait::as_any_mut` [#7833](https://github.com/emilk/egui/pull/7833) by [@emilk](https://github.com/emilk) + +### 🐛 Fixed +* Fix: ensure `CentralPanel::show_inside` allocates space in parent [#7778](https://github.com/emilk/egui/pull/7778) by [@emilk](https://github.com/emilk) +* Heed constrain rect when auto-positioning windows [#7786](https://github.com/emilk/egui/pull/7786) by [@emilk](https://github.com/emilk) +* Fix jitter when hovering edge of scroll area close to resize splitter [#7803](https://github.com/emilk/egui/pull/7803) by [@emilk](https://github.com/emilk) +* Don't focus Areas, Windows and ScrollAreas [#7827](https://github.com/emilk/egui/pull/7827) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix backspacing leaving last character in IME prediction not removed on macOS native and Safari [#7810](https://github.com/emilk/egui/pull/7810) by [@umajho](https://github.com/umajho) +* Implemented distance threshold for double/triple clicks [#7817](https://github.com/emilk/egui/pull/7817) by [@bl4ze4447](https://github.com/bl4ze4447) +* Fix `CentralPanel::show_inside_dyn` to round `panel_rect` [#7868](https://github.com/emilk/egui/pull/7868) by [@ripopov](https://github.com/ripopov) +* Stop ctrl+arrow etc from moving focus [#7897](https://github.com/emilk/egui/pull/7897) by [@emilk](https://github.com/emilk) +* Fix scroll area not consuming scroll events [#7904](https://github.com/emilk/egui/pull/7904) by [@lucasmerlin](https://github.com/lucasmerlin) +* Pass in an explicit id in `UiBuilder`, to avoid wrapping passed in ids with Id::new() [#7925](https://github.com/emilk/egui/pull/7925) by [@lucasmerlin](https://github.com/lucasmerlin) +* Fix crash when dragging a DragValue through small floats [#7939](https://github.com/emilk/egui/pull/7939) by [@Fyrecean](https://github.com/Fyrecean) +* Fix emoji icon font [#7940](https://github.com/emilk/egui/pull/7940) by [@Jhynjhiruu](https://github.com/Jhynjhiruu) +* Fixes the overly aggressive overflow elision in `truncate()` and similar for os scaling other than 100% [#7867](https://github.com/emilk/egui/pull/7867) by [@RndUsr123](https://github.com/RndUsr123) +* Fix text color when selecting newline character [#7951](https://github.com/emilk/egui/pull/7951) by [@emilk](https://github.com/emilk) +* Fix: repaint on drag-and-drop files [#7953](https://github.com/emilk/egui/pull/7953) by [@emilk](https://github.com/emilk) +* Fix instable IDs following animated panels [#7994](https://github.com/emilk/egui/pull/7994) by [@emilk](https://github.com/emilk) +* Enables every combination of `TextEdit` and `LayoutJob` alignments [#7831](https://github.com/emilk/egui/pull/7831) by [@RndUsr123](https://github.com/RndUsr123) +* Fix `horizontal_wrapping` row height after using `text_edit_multiline` [#8000](https://github.com/emilk/egui/pull/8000) by [@optozorax](https://github.com/optozorax) +* Fix menu keyboard toggle for open submenus [#7957](https://github.com/emilk/egui/pull/7957) by [@fjkorf](https://github.com/fjkorf) +* Fix: `Visuals::interact_cursor` support in `Button` [#7986](https://github.com/emilk/egui/pull/7986) by [@mango766](https://github.com/mango766) + +### 🚀 Performance +* Shrink the byte-size of `Response` slightly [#8011](https://github.com/emilk/egui/pull/8011) by [@emilk](https://github.com/emilk) + + ## 0.33.3 - 2025-12-11 * Treat `.` as a word-splitter in text navigation [#7741](https://github.com/emilk/egui/pull/7741) by [@emilk](https://github.com/emilk) * Change text color of selected text [#7691](https://github.com/emilk/egui/pull/7691) by [@emilk](https://github.com/emilk) diff --git a/Cargo.lock b/Cargo.lock index 981e7c475..526f8dd10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1193,7 +1193,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.33.3" +version = "0.34.0" dependencies = [ "bytemuck", "cint", @@ -1205,7 +1205,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.33.3" +version = "0.34.0" dependencies = [ "ahash", "bytemuck", @@ -1244,7 +1244,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.33.3" +version = "0.34.0" dependencies = [ "accesskit", "ahash", @@ -1264,7 +1264,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.33.3" +version = "0.34.0" dependencies = [ "ahash", "bytemuck", @@ -1282,7 +1282,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.33.3" +version = "0.34.0" dependencies = [ "accesskit_winit", "arboard", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.33.3" +version = "0.34.0" dependencies = [ "accesskit", "accesskit_consumer", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.33.3" +version = "0.34.0" dependencies = [ "criterion", "document-features", @@ -1352,7 +1352,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.33.3" +version = "0.34.0" dependencies = [ "ahash", "document-features", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.33.3" +version = "0.34.0" dependencies = [ "bytemuck", "document-features", @@ -1390,7 +1390,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.33.3" +version = "0.34.0" dependencies = [ "dify", "document-features", @@ -1410,7 +1410,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.33.3" +version = "0.34.0" dependencies = [ "egui", "egui_extras", @@ -1440,7 +1440,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.33.3" +version = "0.34.0" dependencies = [ "bytemuck", "document-features", @@ -1538,7 +1538,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.33.3" +version = "0.34.0" dependencies = [ "ahash", "bytemuck", @@ -1564,7 +1564,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.33.3" +version = "0.34.0" [[package]] name = "equivalent" @@ -3432,7 +3432,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.33.3" +version = "0.34.0" dependencies = [ "eframe", "env_logger", @@ -5820,7 +5820,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.33.3" +version = "0.34.0" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index cf631eb25..460a9b667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.92" -version = "0.33.3" +version = "0.34.0" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.33.3", path = "crates/emath", default-features = false } -ecolor = { version = "0.33.3", path = "crates/ecolor", default-features = false } -epaint = { version = "0.33.3", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.33.3", path = "crates/epaint_default_fonts" } -egui = { version = "0.33.3", path = "crates/egui", default-features = false } -egui-winit = { version = "0.33.3", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.33.3", path = "crates/eframe", default-features = false } +emath = { version = "0.34.0", path = "crates/emath", default-features = false } +ecolor = { version = "0.34.0", path = "crates/ecolor", default-features = false } +epaint = { version = "0.34.0", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.34.0", path = "crates/epaint_default_fonts" } +egui = { version = "0.34.0", path = "crates/egui", default-features = false } +egui-winit = { version = "0.34.0", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.34.0", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.34.0", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.34.0", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.34.0", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.34.0", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.34.0", path = "crates/eframe", default-features = false } accesskit = "0.24.0" accesskit_consumer = "0.35.0" diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index a4fe1f3de..161a32ae8 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +Nothing new + + ## 0.33.3 - 2025-12-11 Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 74a705251..ae8f41a02 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,30 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +### ⭐ Added +* Add feature `wgpu_no_default_features` [#7700](https://github.com/emilk/egui/pull/7700) by [@emilk](https://github.com/emilk) +* Add `ViewportInfo::occluded` and `visible` [#7948](https://github.com/emilk/egui/pull/7948) by [@emilk](https://github.com/emilk) +* Add `eframe::WindowChromeMetrics` (macOS only) [#8015](https://github.com/emilk/egui/pull/8015) by [@emilk](https://github.com/emilk) + +### 🔧 Changed +* Replace `App::update` with `fn logic` and `fn ui` [#7775](https://github.com/emilk/egui/pull/7775) by [@emilk](https://github.com/emilk) +* Make `wgpu` the default renderer for `eframe` and egui.rs [#7615](https://github.com/emilk/egui/pull/7615) by [@emilk](https://github.com/emilk) +* Roll out new egui icon and logo [#7995](https://github.com/emilk/egui/pull/7995) by [@emilk](https://github.com/emilk) +* Update wasm-bindgen to 0.2.108, and ehttp to 0.7.1 [#7996](https://github.com/emilk/egui/pull/7996) by [@emilk](https://github.com/emilk) +* Update to `wgpu` 29 [#7990](https://github.com/emilk/egui/pull/7990) by [@cwfitzgerald](https://github.com/cwfitzgerald) +* Allow fallback from smithay to arboard when getting clipboard [#7976](https://github.com/emilk/egui/pull/7976) by [@wizzeh](https://github.com/wizzeh) + +### 🐛 Fixed +* Fix: update get_proc_address to use Arc for better ownership management [#7922](https://github.com/emilk/egui/pull/7922) by [@Wybxc](https://github.com/Wybxc) +* Much improved IME [#7967](https://github.com/emilk/egui/pull/7967) by [@umajho](https://github.com/umajho) +* Improve behavior of invisible windows [#7905](https://github.com/emilk/egui/pull/7905) by [@gcailly](https://github.com/gcailly) + +### 🚀 Performance +* Avoid repaints on device mouse motion outside window [#7866](https://github.com/emilk/egui/pull/7866) by [@inktomi](https://github.com/inktomi) +* Only run `App::ui` if the application is visible [#7950](https://github.com/emilk/egui/pull/7950) by [@emilk](https://github.com/emilk) + + ## 0.33.3 - 2025-12-11 Nothing new diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index cef640184..08c9d7187 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,20 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +### ⭐ Added +* Add error message when calling `.render()` without `.update_buffers()` [#8005](https://github.com/emilk/egui/pull/8005) by [@emilk](https://github.com/emilk) + +### 🔧 Changed +* Put the `capture` module behind a feature flag, make the `egui` dependency optional [#7698](https://github.com/emilk/egui/pull/7698) by [@StT191](https://github.com/StT191) +* Attach stencil buffer [#7702](https://github.com/emilk/egui/pull/7702) by [@jgraef](https://github.com/jgraef) +* Update wgpu to 28.0.0 [#7853](https://github.com/emilk/egui/pull/7853) by [@SuchAFuriousDeath](https://github.com/SuchAFuriousDeath) +* Update to wgpu 29 [#7990](https://github.com/emilk/egui/pull/7990) by [@cwfitzgerald](https://github.com/cwfitzgerald) + +### 🐛 Fixed +* Fix wgpu memory leak leading to panic when window is minimized (#7434) [#7928](https://github.com/emilk/egui/pull/7928) by [@landaire](https://github.com/landaire) + + ## 0.33.3 - 2025-12-11 Nothing new diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index f88a8c84a..b4a9186b2 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,13 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +* Add `is_scrolling`/`is_smooth_scrolling` util, checking for active scroll action [#7669](https://github.com/emilk/egui/pull/7669) by [@IsseW](https://github.com/IsseW) +* Fix backspacing leaving last character in IME prediction not removed on macOS native and Safari [#7810](https://github.com/emilk/egui/pull/7810) by [@umajho](https://github.com/umajho) +* Much improved IME [#7967](https://github.com/emilk/egui/pull/7967) by [@umajho](https://github.com/umajho) +* Allow fallback from smithay to arboard when getting clipboard [#7976](https://github.com/emilk/egui/pull/7976) by [@wizzeh](https://github.com/wizzeh) + + ## 0.33.3 - 2025-12-11 Nothing new diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 15eaf8704..4a480c366 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,14 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +* Fix media type with optional parameters [#7739](https://github.com/emilk/egui/pull/7739) by [@leungkkf](https://github.com/leungkkf) +* Fix: CodeTheme functions - ui and add builder [#7684](https://github.com/emilk/egui/pull/7684) by [@Its-Just-Nans](https://github.com/Its-Just-Nans) +* Update selected dependencies [#7920](https://github.com/emilk/egui/pull/7920) by [@oscargus](https://github.com/oscargus) +* Add `DatePickerButton::reverse_years/year_scroll_to` [#7978](https://github.com/emilk/egui/pull/7978) by [@Deuracell](https://github.com/Deuracell) +* Replace `chrono` with `jiff` [#8008](https://github.com/emilk/egui/pull/8008) by [@emilk](https://github.com/emilk) + + ## 0.33.3 - 2025-12-11 * Bump `ehttp` to 0.6.0 [#7757](https://github.com/emilk/egui/pull/7757) by [@jprochazk](https://github.com/jprochazk) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 2fccf1166..d43a4be20 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +* Turn `HarnessBuilder::with_options` into a proper builder method [#7697](https://github.com/emilk/egui/pull/7697) by [@emilk](https://github.com/emilk) +* Paint mouse cursor in kittest snapshot images [#7721](https://github.com/emilk/egui/pull/7721) by [@emilk](https://github.com/emilk) +* Add `kittest.toml` config file [#7643](https://github.com/emilk/egui/pull/7643) by [@lucasmerlin](https://github.com/lucasmerlin) +* Close debug_open_snapshot temp file before viewing it [#7841](https://github.com/emilk/egui/pull/7841) by [@yuriks](https://github.com/yuriks) + + ## 0.33.3 - 2025-12-11 * Enforce consistent snapshot updates [#7744](https://github.com/emilk/egui/pull/7744) by [@lucasmerlin](https://github.com/lucasmerlin) * `kittest`: add drag-and-drop helpers [#7690](https://github.com/emilk/egui/pull/7690) by [@emilk](https://github.com/emilk) diff --git a/crates/emath/CHANGELOG.md b/crates/emath/CHANGELOG.md index f559265c4..f82686d71 100644 --- a/crates/emath/CHANGELOG.md +++ b/crates/emath/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +Nothing new + + ## 0.33.3 - 2025-12-11 Nothing new diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index dd6b4e2a5..fdbc3704c 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,25 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +### ⭐ Added +* Replace ab_glyph with Skrifa + vello_cpu; enable font hinting [#7694](https://github.com/emilk/egui/pull/7694) by [@valadaptive](https://github.com/valadaptive) +* Add font variations API [#7859](https://github.com/emilk/egui/pull/7859) by [@valadaptive](https://github.com/valadaptive) +* Allow rotation of rectangles and ellipses [#7682](https://github.com/emilk/egui/pull/7682) by [@RyanBluth](https://github.com/RyanBluth) + +### 🔧 Changed +* Apply preferred font weight when loading variable fonts [#7790](https://github.com/emilk/egui/pull/7790) by [@pmnxis](https://github.com/pmnxis) +* Make `Galley::pos_from_layout_cursor` `pub` [#7864](https://github.com/emilk/egui/pull/7864) by [@dionb](https://github.com/dionb) + +### 🐛 Fixed +* Fixes the overly aggressive overflow elision in `truncate()` and similar for os scaling other than 100% [#7867](https://github.com/emilk/egui/pull/7867) by [@RndUsr123](https://github.com/RndUsr123) +* Fix text color when selecting newline character [#7951](https://github.com/emilk/egui/pull/7951) by [@emilk](https://github.com/emilk) +* Fix galley width calculation being off due to subpixel binning [#7972](https://github.com/emilk/egui/pull/7972) by [@lucasmerlin](https://github.com/lucasmerlin) + +### 🚀 Performance +* Avoid cloning `Row`s during `Galley::concat` [#7649](https://github.com/emilk/egui/pull/7649) by [@afishhh](https://github.com/afishhh) + + ## 0.33.3 - 2025-12-11 Nothing new diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index 58fd310f4..925f6f89c 100644 --- a/crates/epaint_default_fonts/CHANGELOG.md +++ b/crates/epaint_default_fonts/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.0 - 2026-03-26 +* Fix emoji icon font [#7940](https://github.com/emilk/egui/pull/7940) by [@Jhynjhiruu](https://github.com/Jhynjhiruu) + + ## 0.33.3 - 2025-12-11 Nothing new