Skip to main content

workflow_egui/module/
module.rs

1use crate::imports::*;
2
3/// Layout style with which a module's UI should be rendered, selecting the set
4/// of text styles applied before rendering.
5pub enum ModuleStyle {
6    /// Render using the application's mobile text styles.
7    Mobile,
8    /// Render using the application's default (desktop) text styles.
9    Default,
10}
11
12/// Capabilities of a module. Defines whether the module
13/// should be available on the Desktop, Mobile, WebApp or
14/// in a browser Extension.
15pub enum ModuleCaps {
16    /// The module is available on the desktop application.
17    Desktop,
18    /// The module is available on the mobile application.
19    Mobile,
20    /// The module is available in the web application.
21    WebApp,
22    /// The module is available in the browser extension.
23    Extension,
24}
25
26/// Trait implemented by a UI module: a self-contained, downcastable unit with
27/// lifecycle hooks (activate/deactivate/main/render/shutdown) operating over an
28/// associated application context.
29pub trait ModuleT: Downcast {
30    /// The application context type passed to the module's lifecycle hooks.
31    type Context;
32
33    /// Returns an explicit display name for the module, or `None` to use the
34    /// name derived from its type.
35    fn name(&self) -> Option<&'static str> {
36        None
37    }
38
39    /// Returns whether the module should be presented modally. Defaults to
40    /// `false`.
41    fn modal(&self) -> bool {
42        false
43    }
44
45    /// Returns whether the module handles sensitive data and should be treated
46    /// as secure. Defaults to `false`.
47    fn secure(&self) -> bool {
48        false
49    }
50
51    /// Returns the layout style the module should be rendered with. Defaults to
52    /// [`ModuleStyle::Default`].
53    fn style(&self) -> ModuleStyle {
54        ModuleStyle::Default
55    }
56
57    /// Called when the module becomes the active/focused module.
58    fn activate(&mut self, _core: &mut Self::Context) {}
59
60    /// Called when the module loses focus or is switched away from.
61    fn deactivate(&mut self, _core: &mut Self::Context) {}
62
63    /// Runs the module's main update step, called each frame while active.
64    fn main(&mut self, _core: &mut Self::Context) {}
65
66    /// Renders the module's UI for the current frame.
67    fn render(
68        &mut self,
69        core: &mut Self::Context,
70        ctx: &egui::Context,
71        frame: &mut eframe::Frame,
72        ui: &mut egui::Ui,
73    );
74
75    /// Called when the module is being torn down, allowing it to release
76    /// resources.
77    fn shutdown(&mut self) {}
78}
79
80impl_downcast!(ModuleT assoc Context);
81
82/// Shared inner state of a [`Module`], holding the module instance alongside
83/// its identifying metadata.
84pub struct Inner<T> {
85    /// The module's display name (derived from its type name).
86    pub name: String,
87    /// The fully-qualified type name of the concrete module.
88    pub type_name: String,
89    /// The [`TypeId`] of the concrete module type, used as a registry key.
90    pub type_id: TypeId,
91    /// The interior-mutable, type-erased module instance.
92    pub module: Rc<RefCell<dyn ModuleT<Context = T>>>,
93}
94
95/// A cheaply-clonable, reference-counted handle to a registered module.
96pub struct Module<T> {
97    /// Shared inner state holding the module and its metadata.
98    pub inner: Rc<Inner<T>>,
99}
100
101impl<T> Clone for Module<T> {
102    fn clone(&self) -> Self {
103        Self {
104            inner: Rc::clone(&self.inner),
105        }
106    }
107}
108
109impl<T> Module<T>
110where
111    T: App + 'static,
112{
113    /// Returns a cloned reference-counted handle to the inner type-erased
114    /// module.
115    pub fn any(&self) -> Rc<RefCell<dyn ModuleT<Context = T>>> {
116        Rc::clone(&self.inner.module)
117    }
118
119    /// Runs the module's main update step, invoking its [`ModuleT::main`] hook.
120    pub fn main(&self, core: &mut T) {
121        self.inner.module.borrow_mut().main(core)
122    }
123
124    /// Activates the module, invoking its [`ModuleT::activate`] hook.
125    pub fn activate(&self, core: &mut T) {
126        self.inner.module.borrow_mut().activate(core)
127    }
128
129    /// Deactivates the module, invoking its [`ModuleT::deactivate`] hook.
130    pub fn deactivate(&self, core: &mut T) {
131        self.inner.module.borrow_mut().deactivate(core)
132    }
133
134    /// Applies the module's style (mobile or default text styles) and then
135    /// renders the module's UI.
136    pub fn render(
137        &self,
138        core: &mut T,
139        ctx: &egui::Context,
140        frame: &mut eframe::Frame,
141        ui: &mut egui::Ui,
142    ) {
143        let mut module = self.inner.module.borrow_mut();
144
145        match module.style() {
146            ModuleStyle::Mobile => {
147                if let Some(text_styles) = core.mobile_text_styles() {
148                    ui.style_mut().text_styles = text_styles;
149                }
150            }
151            ModuleStyle::Default => {
152                if let Some(text_styles) = core.default_text_styles() {
153                    ui.style_mut().text_styles = text_styles;
154                }
155            }
156        }
157
158        module.render(core, ctx, frame, ui)
159    }
160
161    /// Returns the module's display name, falling back to the derived type name
162    /// when the module does not provide its own.
163    pub fn name(&self) -> &str {
164        self.inner
165            .module
166            .borrow_mut()
167            .name()
168            .unwrap_or_else(|| self.inner.name.as_str())
169    }
170
171    /// Returns whether the module should be presented modally.
172    pub fn modal(&self) -> bool {
173        self.inner.module.borrow_mut().modal()
174    }
175
176    /// Returns whether the module is marked as secure.
177    pub fn secure(&self) -> bool {
178        self.inner.module.borrow_mut().secure()
179    }
180
181    /// Returns the [`TypeId`] of the concrete module type.
182    pub fn type_id(&self) -> TypeId {
183        self.inner.type_id
184    }
185
186    /// Borrows the inner module downcast to the concrete type `M`, panicking if
187    /// the stored module is not of that type.
188    pub fn as_ref<M>(&self) -> Ref<'_, M>
189    where
190        M: ModuleT + 'static,
191    {
192        Ref::map(self.inner.module.borrow(), |r| {
193            (r).as_any()
194                .downcast_ref::<M>()
195                .expect("unable to downcast section")
196        })
197    }
198
199    /// Mutably borrows the inner module downcast to the concrete type `M`,
200    /// panicking if the stored module is not of that type.
201    pub fn as_mut<M>(&mut self) -> RefMut<'_, M>
202    where
203        M: ModuleT + 'static,
204    {
205        RefMut::map(self.inner.module.borrow_mut(), |r| {
206            (r).as_any_mut()
207                .downcast_mut::<M>()
208                .expect("unable to downcast_mut module")
209        })
210    }
211}
212
213impl<T> std::fmt::Debug for Module<T> {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        write!(f, "{}", self.inner.name)
216    }
217}
218
219impl<T> Eq for Module<T> {}
220
221impl<T> PartialEq for Module<T> {
222    fn eq(&self, other: &Self) -> bool {
223        self.inner.type_id == other.inner.type_id
224    }
225}
226
227impl<T, M> From<M> for Module<T>
228where
229    M: ModuleT<Context = T> + 'static,
230    T: App,
231{
232    fn from(section: M) -> Self {
233        let type_name = type_name::<M>().to_string();
234        let name = type_name.split("::").last().unwrap().to_string();
235        let type_id = TypeId::of::<M>();
236        Self {
237            inner: Rc::new(Inner {
238                name,
239                type_name,
240                type_id,
241                module: Rc::new(RefCell::new(section)),
242            }),
243        }
244    }
245}
246
247/// Extension trait for inserting a module into a map keyed by its [`TypeId`].
248pub trait HashMapModuleExtension<T, M> {
249    /// Inserts the module into the map, using the module type's [`TypeId`] as
250    /// the key.
251    fn insert_typeid(&mut self, value: M)
252    where
253        M: ModuleT<Context = T> + 'static,
254        T: App;
255}
256
257impl<T, M> HashMapModuleExtension<T, M> for AHashMap<TypeId, Module<T>>
258where
259    M: ModuleT<Context = T> + 'static,
260    T: App,
261{
262    fn insert_typeid(&mut self, section: M) {
263        self.insert(TypeId::of::<M>(), Module::<T>::from(section));
264    }
265}