Skip to main content

egui/
plugin.rs

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