Skip to main content

egui/
plugin.rs

1use crate::{Context, FullOutput, RawInput, Ui};
2use ahash::HashMap;
3use alloc::sync::Arc;
4use epaint::mutex::{Mutex, MutexGuard};
5
6/// A plugin to extend egui.
7///
8/// Add plugins via [`Context::add_plugin`].
9///
10/// Plugins should not hold a reference to the [`Context`], since this would create a cycle
11/// (which would prevent the [`Context`] from being dropped).
12#[expect(unused_variables)]
13pub trait Plugin: Send + Sync + core::any::Any + 'static {
14    /// Plugin name.
15    ///
16    /// Used when profiling.
17    fn debug_name(&self) -> &'static str;
18
19    /// Called once, when the plugin is registered.
20    ///
21    /// Useful to e.g. register image loaders.
22    fn setup(&mut self, ctx: &Context) {}
23
24    /// Called once when the integration is shutting down.
25    ///
26    /// This is called before [`Context::memory`] is persisted and while integration resources are
27    /// still available. Plugins may use this to store state in persistent egui [`crate::Memory`].
28    fn on_exit(&mut self, ctx: &Context) {}
29
30    /// Called at the start of each pass.
31    ///
32    /// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::Panel`].
33    fn on_begin_pass(&mut self, ui: &mut Ui) {}
34
35    /// Called at the end of each pass.
36    ///
37    /// Can be used to show ui, e.g. a [`crate::Window`].
38    fn on_end_pass(&mut self, ui: &mut Ui) {}
39
40    /// Called just before the input is processed.
41    ///
42    /// Useful to inspect or modify the input.
43    /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though.
44    fn input_hook(&mut self, ctx: &Context, input: &mut RawInput) {}
45
46    /// Called just before the output is passed to the backend.
47    ///
48    /// Useful to inspect or modify the output.
49    /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though.
50    fn output_hook(&mut self, ctx: &Context, output: &mut FullOutput) {}
51
52    /// Called when a widget is created and is under the pointer.
53    ///
54    /// Useful for capturing a stack trace so that widgets can be mapped back to their source code.
55    /// Since this is called outside a pass, don't show ui here. Using `Context::debug_painter` is fine though.
56    #[cfg(debug_assertions)]
57    fn on_widget_under_pointer(&mut self, ctx: &Context, widget: &crate::WidgetRect) {}
58}
59
60pub(crate) struct PluginHandle {
61    plugin: Box<dyn Plugin>,
62}
63
64/// A typed handle to a registered [`Plugin`].
65///
66/// Use [`Self::lock`] to access the plugin.
67pub struct TypedPluginHandle<P: Plugin> {
68    handle: Arc<Mutex<PluginHandle>>,
69    _type: core::marker::PhantomData<P>,
70}
71
72impl<P: Plugin> TypedPluginHandle<P> {
73    pub(crate) fn new(handle: Arc<Mutex<PluginHandle>>) -> Self {
74        Self {
75            handle,
76            _type: core::marker::PhantomData,
77        }
78    }
79
80    /// Lock the plugin for access.
81    ///
82    /// Returns a guard that dereferences to the plugin.
83    pub fn lock(&self) -> TypedPluginGuard<'_, P> {
84        TypedPluginGuard {
85            guard: self.handle.lock(),
86            _type: core::marker::PhantomData,
87        }
88    }
89}
90
91/// A guard that provides access to a [`Plugin`].
92pub struct TypedPluginGuard<'a, P: Plugin> {
93    guard: MutexGuard<'a, PluginHandle>,
94    _type: core::marker::PhantomData<P>,
95}
96
97impl<P: Plugin> TypedPluginGuard<'_, P> {}
98
99impl<P: Plugin> core::ops::Deref for TypedPluginGuard<'_, P> {
100    type Target = P;
101
102    fn deref(&self) -> &Self::Target {
103        self.guard.typed_plugin()
104    }
105}
106
107impl<P: Plugin> core::ops::DerefMut for TypedPluginGuard<'_, P> {
108    fn deref_mut(&mut self) -> &mut Self::Target {
109        self.guard.typed_plugin_mut()
110    }
111}
112
113impl PluginHandle {
114    pub fn new<P: Plugin>(plugin: P) -> Arc<Mutex<Self>> {
115        Arc::new(Mutex::new(Self {
116            plugin: Box::new(plugin),
117        }))
118    }
119
120    fn plugin_type_id(&self) -> core::any::TypeId {
121        (*self.plugin).type_id()
122    }
123
124    pub fn dyn_plugin_mut(&mut self) -> &mut dyn Plugin {
125        &mut *self.plugin
126    }
127
128    fn typed_plugin<P: Plugin + 'static>(&self) -> &P {
129        (self.plugin.as_ref() as &dyn core::any::Any)
130            .downcast_ref::<P>()
131            .expect("PluginHandle: plugin is not of the expected type")
132    }
133
134    pub fn typed_plugin_mut<P: Plugin + 'static>(&mut self) -> &mut P {
135        (self.plugin.as_mut() as &mut dyn core::any::Any)
136            .downcast_mut::<P>()
137            .expect("PluginHandle: plugin is not of the expected type")
138    }
139}
140
141/// User-registered plugins.
142#[derive(Clone, Default)]
143pub(crate) struct Plugins {
144    plugins: HashMap<core::any::TypeId, Arc<Mutex<PluginHandle>>>,
145    plugins_ordered: PluginsOrdered,
146}
147
148#[derive(Clone, Default)]
149pub(crate) struct PluginsOrdered(Vec<Arc<Mutex<PluginHandle>>>);
150
151impl PluginsOrdered {
152    fn for_each_dyn<F>(&self, mut f: F)
153    where
154        F: FnMut(&mut dyn Plugin),
155    {
156        for plugin in &self.0 {
157            let mut plugin = plugin.lock();
158            profiling::scope!("plugin", plugin.dyn_plugin_mut().debug_name());
159            f(plugin.dyn_plugin_mut());
160        }
161    }
162
163    pub fn on_begin_pass(&self, ui: &mut Ui) {
164        profiling::scope!("plugins", "on_begin_pass");
165        self.for_each_dyn(|p| {
166            p.on_begin_pass(ui);
167        });
168    }
169
170    pub fn on_end_pass(&self, ui: &mut Ui) {
171        profiling::scope!("plugins", "on_end_pass");
172        self.for_each_dyn(|p| {
173            p.on_end_pass(ui);
174        });
175    }
176
177    pub fn on_input(&self, ctx: &Context, input: &mut RawInput) {
178        profiling::scope!("plugins", "on_input");
179        self.for_each_dyn(|plugin| {
180            plugin.input_hook(ctx, input);
181        });
182    }
183
184    pub fn on_output(&self, ctx: &Context, output: &mut FullOutput) {
185        profiling::scope!("plugins", "on_output");
186        self.for_each_dyn(|plugin| {
187            plugin.output_hook(ctx, output);
188        });
189    }
190
191    pub fn on_exit(&self, ctx: &Context) {
192        profiling::scope!("plugins", "on_exit");
193        self.for_each_dyn(|plugin| {
194            plugin.on_exit(ctx);
195        });
196    }
197
198    #[cfg(debug_assertions)]
199    pub fn on_widget_under_pointer(&self, ctx: &Context, widget: &crate::WidgetRect) {
200        profiling::scope!("plugins", "on_widget_under_pointer");
201        self.for_each_dyn(|plugin| {
202            plugin.on_widget_under_pointer(ctx, widget);
203        });
204    }
205}
206
207impl Plugins {
208    pub fn ordered_plugins(&self) -> PluginsOrdered {
209        self.plugins_ordered.clone()
210    }
211
212    /// Remember to call [`Plugin::setup`] on the plugin after adding it.
213    ///
214    /// Will not add the plugin if a plugin of the same type already exists.
215    /// Returns `false` if the plugin was not added, `true` if it was added.
216    pub fn add(&mut self, handle: Arc<Mutex<PluginHandle>>) -> bool {
217        profiling::scope!("plugins", "add");
218
219        let type_id = handle.lock().plugin_type_id();
220
221        if self.plugins.contains_key(&type_id) {
222            return false;
223        }
224
225        self.plugins.insert(type_id, Arc::clone(&handle));
226        self.plugins_ordered.0.push(handle);
227
228        true
229    }
230
231    pub fn get(&self, type_id: core::any::TypeId) -> Option<Arc<Mutex<PluginHandle>>> {
232        self.plugins.get(&type_id).cloned()
233    }
234}
235
236/// Generic event callback.
237pub type ContextCallback = Arc<dyn Fn(&mut Ui) + Send + Sync>;
238
239#[derive(Default)]
240pub(crate) struct CallbackPlugin {
241    pub on_begin_plugins: Vec<(&'static str, ContextCallback)>,
242    pub on_end_plugins: Vec<(&'static str, ContextCallback)>,
243}
244
245impl Plugin for CallbackPlugin {
246    fn debug_name(&self) -> &'static str {
247        "CallbackPlugins"
248    }
249
250    fn on_begin_pass(&mut self, ui: &mut Ui) {
251        profiling::function_scope!();
252
253        for (_debug_name, cb) in &self.on_begin_plugins {
254            profiling::scope!("on_begin_pass", *_debug_name);
255            (cb)(ui);
256        }
257    }
258
259    fn on_end_pass(&mut self, ui: &mut Ui) {
260        profiling::function_scope!();
261
262        for (_debug_name, cb) in &self.on_end_plugins {
263            profiling::scope!("on_end_pass", *_debug_name);
264            (cb)(ui);
265        }
266    }
267}