From d7c45729a5c9b5cef281886f5c5e41ae6b63a471 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Apr 2026 09:52:28 +0200 Subject: [PATCH] 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 a8751bffc..51645e9dd 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()); + }); }) } @@ -2848,13 +2849,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(); } @@ -2881,18 +2890,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))); + } }); } }