From e0561e1820f8730b29fe8ad0aca55b28b8918e9e Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Mon, 27 Oct 2025 09:52:42 -0400 Subject: [PATCH] Add `Plugin::on_widget_under_pointer` to support widget inspector (#7652) This PR adds `Plugin::on_widget_under_pointer` which gets called whenever a widget is created whose rect contains the pointer. The point of the hook is to capture a stack trace which can be used to map widgets to their corresponding source code so it must be called while the widget is being created. The obvious concern is performance impact. However, since it's only called for rects under the cursor, the effect seems negligible afaict. It's under `debug_assertions` just in case. This change is needed so we can publish the widget inspector we've been working on. Basically a plugin that allows us to jump from any widget back to their corresponding source code. This video shows the plugin configured to open the corresponding code in github, but normally it would open your local editor. Update: [Live demo](https://membrane-io.github.io/egui/) (Firefox/Safari not yet supported. `Cmd-I` to inspect. `Tab` to cycle filters. `Click` to open). It will try to open a file under `/home/runner/work/egui/egui/` so it won't work, but you get the idea. https://github.com/user-attachments/assets/afe4d6af-7f67-44b5-be25-44f7564d9a3a ## What's next After this gets merged I plan to publish the above plugin as its own crate, that way we can iterate and release quickly while things are still changing. I agree it would make sense to eventually merge it into the main egui repo (like @emilk suggested in #4650). * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/context.rs | 6 ++++++ crates/egui/src/plugin.rs | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index aab81ab2..5a868dd7 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1198,6 +1198,12 @@ impl Context { #[allow(clippy::let_and_return, clippy::allow_attributes)] let res = self.get_response(w); + #[cfg(debug_assertions)] + if res.contains_pointer() { + let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); + plugins.on_widget_under_pointer(self, &w); + } + #[cfg(feature = "accesskit")] if allow_focus && w.sense.is_focusable() { // Make sure anything that can receive focus has an AccessKit node. diff --git a/crates/egui/src/plugin.rs b/crates/egui/src/plugin.rs index bebcf892..3e078d21 100644 --- a/crates/egui/src/plugin.rs +++ b/crates/egui/src/plugin.rs @@ -34,14 +34,21 @@ pub trait Plugin: Send + Sync + std::any::Any + 'static { /// Called just before the input is processed. /// /// Useful to inspect or modify the input. - /// Since this is called outside a pass, don't show ui here. + /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though. fn input_hook(&mut self, input: &mut RawInput) {} /// Called just before the output is passed to the backend. /// /// Useful to inspect or modify the output. - /// Since this is called outside a pass, don't show ui here. + /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though. fn output_hook(&mut self, output: &mut FullOutput) {} + + /// Called when a widget is created and is under the pointer. + /// + /// Useful for capturing a stack trace so that widgets can be mapped back to their source code. + /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though. + #[cfg(debug_assertions)] + fn on_widget_under_pointer(&mut self, ctx: &Context, widget: &crate::WidgetRect) {} } pub(crate) struct PluginHandle { @@ -167,6 +174,14 @@ impl PluginsOrdered { plugin.output_hook(output); }); } + + #[cfg(debug_assertions)] + pub fn on_widget_under_pointer(&self, ctx: &Context, widget: &crate::WidgetRect) { + profiling::scope!("plugins", "on_widget_under_pointer"); + self.for_each_dyn(|plugin| { + plugin.on_widget_under_pointer(ctx, widget); + }); + } } impl Plugins {