maycoon_core/app/
handler.rs

1use nalgebra::Vector2;
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4use taffy::{
5    AvailableSpace, Dimension, NodeId, PrintTree, Size, Style, TaffyResult, TaffyTree,
6    TraversePartialTree,
7};
8use winit::application::ApplicationHandler;
9use winit::event::WindowEvent;
10use winit::event_loop::ActiveEventLoop;
11use winit::window::{Window, WindowAttributes, WindowId};
12
13use crate::app::context::AppContext;
14use crate::app::font_ctx::FontContext;
15use crate::app::info::AppInfo;
16use crate::app::update::{Update, UpdateManager};
17use crate::config::MayConfig;
18use crate::layout::{LayoutNode, StyleNode};
19use crate::plugin::PluginManager;
20use crate::vgi::{Scene, VectorGraphicsInterface};
21use crate::widget::Widget;
22use maycoon_theme::theme::Theme;
23
24/// The core application handler. You should use [MayApp](crate::app::MayApp) instead for running applications.
25pub struct AppHandler<T, W, S, F, V>
26where
27    T: Theme,
28    W: Widget,
29    F: Fn(AppContext, S) -> W,
30    V: VectorGraphicsInterface,
31{
32    config: MayConfig<T, V>,
33    attrs: WindowAttributes,
34    window: Option<Arc<Window>>,
35    scene: V::Scene,
36    taffy: TaffyTree,
37    window_node: NodeId,
38    builder: F,
39    state: Option<S>,
40    widget: Option<W>,
41    info: AppInfo,
42    update: UpdateManager,
43    last_update: Instant,
44    plugins: PluginManager<T, V>,
45    graphics: V,
46}
47
48impl<T, W, S, F, V> AppHandler<T, W, S, F, V>
49where
50    T: Theme,
51    W: Widget,
52    F: Fn(AppContext, S) -> W,
53    V: VectorGraphicsInterface,
54{
55    /// Create a new handler with given window attributes, config, widget and state.
56    #[inline(always)]
57    #[tracing::instrument(level = "trace", skip_all)]
58    pub fn new(
59        attrs: WindowAttributes,
60        config: MayConfig<T, V>,
61        builder: F,
62        state: S,
63        font_context: FontContext,
64        update: UpdateManager,
65        plugins: PluginManager<T, V>,
66    ) -> Self {
67        tracing::trace!("creating taffy tree");
68        let mut taffy = TaffyTree::with_capacity(16);
69
70        // gets configured on resume
71        let window_node = taffy
72            .new_leaf(Style::default())
73            .expect("Failed to create window node");
74
75        let size = config.window.size;
76
77        let graphics = config.graphics.clone();
78
79        Self {
80            attrs,
81            window: None,
82            config,
83            scene: Scene::new(),
84            taffy,
85            widget: None,
86            info: AppInfo {
87                font_context,
88                size,
89                ..Default::default()
90            },
91            window_node,
92            builder,
93            state: Some(state),
94            update,
95            last_update: Instant::now(),
96            plugins,
97            graphics: V::new(graphics).expect("Failed to create vector graphics interface"),
98        }
99    }
100
101    /// Get the application context.
102    #[inline(always)]
103    #[tracing::instrument(level = "trace", skip_all)]
104    pub fn context(&self) -> AppContext {
105        AppContext::new(self.update.clone(), self.info.diagnostics)
106    }
107
108    /// Add the parent node and its children to the layout tree.
109    #[tracing::instrument(level = "trace", skip(self, style))]
110    fn layout_widget(&mut self, parent: NodeId, style: &StyleNode) -> TaffyResult<()> {
111        let node = self.taffy.new_leaf(style.style.clone().into())?;
112
113        self.taffy.add_child(parent, node)?;
114
115        for child in &style.children {
116            self.layout_widget(node, child)?;
117        }
118
119        Ok(())
120    }
121
122    /// Compute the layout of the root node and its children.
123    #[inline(always)]
124    #[tracing::instrument(level = "trace", skip(self))]
125    fn compute_layout(&mut self) -> TaffyResult<()> {
126        self.taffy.compute_layout(
127            self.window_node,
128            Size::<AvailableSpace> {
129                width: AvailableSpace::Definite(
130                    self.window.as_ref().unwrap().inner_size().width as f32,
131                ),
132                height: AvailableSpace::Definite(
133                    self.window.as_ref().unwrap().inner_size().height as f32,
134                ),
135            },
136        )?;
137        Ok(())
138    }
139
140    /// Collect the computed layout of the given node and its children. Make sure to call [AppHandler::compute_layout] before, to not get dirty results.
141    #[inline(always)]
142    #[tracing::instrument(level = "trace", skip(self, style))]
143    fn collect_layout(&mut self, node: NodeId, style: &StyleNode) -> TaffyResult<LayoutNode> {
144        tracing::trace!("collecting node layout {node:?}");
145
146        let mut children = Vec::with_capacity(style.children.capacity());
147
148        for (i, style) in style.children.iter().enumerate() {
149            let child = self.taffy.child_at_index(node, i)?;
150
151            tracing::trace!("collecting layout of child {child:?}");
152
153            children.push(self.collect_layout(child, style)?);
154        }
155
156        Ok(LayoutNode {
157            layout: self.taffy.get_final_layout(node),
158            children,
159        })
160    }
161
162    /// Request a window redraw.
163    #[inline(always)]
164    #[tracing::instrument(level = "trace", skip(self))]
165    fn request_redraw(&self) {
166        tracing::trace!("requesting redraw");
167        self.window.as_ref().unwrap().request_redraw();
168    }
169
170    /// Render the application via the vector graphics interface.
171    #[inline(always)]
172    #[tracing::instrument(level = "trace", skip(self))]
173    fn render(
174        &mut self,
175        window: Arc<Window>,
176        event_loop: &ActiveEventLoop,
177    ) -> Result<(), V::Error> {
178        tracing::trace!("rendering via vector graphics interface");
179        self.graphics.render(
180            window,
181            event_loop,
182            &self.scene,
183            self.config.theme.window_background(),
184        )?;
185
186        Ok(())
187    }
188
189    /// Update the app and process events.
190    #[inline(always)]
191    #[tracing::instrument(level = "trace", skip_all)]
192    fn update(&mut self, event_loop: &ActiveEventLoop) {
193        // update plugins
194        tracing::trace!("updating plugins");
195        self.plugins.run(|pl| {
196            pl.on_update(
197                &mut self.config,
198                self.window.as_ref().expect("Window not initialized"),
199                &mut self.scene,
200                &mut self.taffy,
201                self.window_node,
202                &mut self.info,
203                &self.update,
204                &mut self.last_update,
205                event_loop,
206            )
207        });
208
209        // completely layout widgets if taffy is not set up yet (e.g. during first update)
210        if self.taffy.child_count(self.window_node) == 0 {
211            tracing::trace_span!("complete layout").in_scope(|| {
212                let style = self.widget.as_ref().unwrap().layout_style();
213
214                self.layout_widget(self.window_node, &style)
215                    .expect("Failed to layout window");
216
217                self.compute_layout().expect("Failed to compute layout");
218
219                self.update.insert(Update::FORCE);
220            });
221        }
222
223        let style = self.widget.as_ref().unwrap().layout_style();
224
225        let mut layout_node = self
226            .collect_layout(
227                self.taffy.child_at_index(self.window_node, 0).unwrap(),
228                &style,
229            )
230            .expect("Failed to collect layout");
231
232        // update call to check if app should re-evaluate
233        let context = self.context();
234
235        tracing::trace!("updating widget");
236        self.update.insert(
237            self.widget
238                .as_mut()
239                .unwrap()
240                .update(&layout_node, context, &self.info),
241        );
242
243        // check if app should re-evaluate layout
244        if self.update.get().intersects(Update::LAYOUT | Update::FORCE) {
245            tracing::trace_span!("layout").in_scope(|| {
246                // clear all nodes (except root window node)
247                self.taffy
248                    .set_children(self.window_node, &[])
249                    .expect("Failed to set children");
250
251                let style = self.widget.as_ref().unwrap().layout_style();
252
253                self.layout_widget(self.window_node, &style)
254                    .expect("Failed to layout window");
255
256                self.compute_layout().expect("Failed to compute layout");
257
258                layout_node = self
259                    .collect_layout(
260                        self.taffy.child_at_index(self.window_node, 0).unwrap(),
261                        &style,
262                    )
263                    .expect("Failed to collect layout");
264            });
265        }
266
267        // check if app should redraw
268        if self.update.get().intersects(Update::FORCE | Update::DRAW) {
269            tracing::trace_span!("draw").in_scope(|| {
270                // clear scene
271                tracing::trace!("resetting vector graphics interface scene");
272                self.scene.reset();
273
274                let context = self.context();
275
276                tracing::trace!("drawing root widget");
277                self.widget.as_mut().unwrap().render(
278                    &mut self.scene,
279                    &mut self.config.theme,
280                    &layout_node,
281                    &self.info,
282                    context,
283                );
284
285                let window = self.window.clone().expect("Window not initialized");
286
287                // check surface validity
288                if window.inner_size().width != 0 && window.inner_size().height != 0 {
289                    self.render(window, event_loop)
290                        .expect("Failed rendering process");
291                } else {
292                    tracing::debug!("skipping render due to invalid surface");
293                }
294            });
295        }
296
297        // check if app should re-evaluate
298        if self.update.get().intersects(Update::EVAL | Update::FORCE) {
299            tracing::trace!("re-evaluating application state");
300            self.request_redraw();
301        }
302
303        // update the app if requested
304        if self.update.get().intersects(Update::EXIT) {
305            tracing::trace!("exiting event loop");
306            event_loop.exit();
307            return;
308        }
309
310        // reset AppInfo and update states
311        tracing::trace!("resetting app info and update states");
312        self.info.reset();
313        self.update.clear();
314
315        // update diagnostics
316        tracing::trace!("updating diagnostics");
317
318        self.info.diagnostics.first_run = false;
319
320        if self.last_update.elapsed() >= Duration::from_secs(1) {
321            self.last_update = Instant::now();
322
323            // calc avg updates per sec through updates per sec NOW divided by 2
324            self.info.diagnostics.updates_per_sec =
325                (self.info.diagnostics.updates_per_sec + self.info.diagnostics.updates) / 2;
326
327            // reset current updates per seconds
328            self.info.diagnostics.updates = 0;
329        } else {
330            // increase updates per sec NOW by 1
331            self.info.diagnostics.updates += 1;
332        }
333
334        tracing::debug!("updates per sec: {}", self.info.diagnostics.updates_per_sec);
335    }
336
337    #[cold]
338    fn close(&mut self, window: Arc<Window>, event_loop: &ActiveEventLoop) {
339        tracing::trace!("close requested");
340
341        self.graphics
342            .destroy(window, event_loop)
343            .expect("Failed to destroy vector graphics interface");
344
345        if self.config.window.close_on_request {
346            tracing::info!("exiting event loop");
347            event_loop.exit();
348        }
349    }
350}
351
352impl<T, W, S, F, V> ApplicationHandler for AppHandler<T, W, S, F, V>
353where
354    T: Theme,
355    W: Widget,
356    F: Fn(AppContext, S) -> W,
357    V: VectorGraphicsInterface,
358{
359    #[inline(always)]
360    #[tracing::instrument(level = "trace", skip_all)]
361    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
362        tracing::debug!("resuming plugins");
363        self.plugins.run(|pl| {
364            pl.on_resume(
365                &mut self.config,
366                &mut self.scene,
367                &mut self.taffy,
368                self.window_node,
369                &mut self.info,
370                &self.update,
371                &mut self.last_update,
372                event_loop,
373            )
374        });
375
376        tracing::info!("creating window");
377        let window = Arc::new(
378            event_loop
379                .create_window(self.attrs.clone())
380                .expect("Failed to create window"),
381        );
382
383        tracing::info!("initializing layout");
384        let size = window.inner_size();
385
386        self.taffy
387            .set_style(
388                self.window_node,
389                Style {
390                    size: Size::<Dimension> {
391                        width: Dimension::length(size.width as f32),
392                        height: Dimension::length(size.height as f32),
393                    },
394                    ..Default::default()
395                },
396            )
397            .expect("Failed to set window node style");
398
399        tracing::info!("initializing vector graphics interface");
400        self.graphics
401            .init(window.clone(), event_loop)
402            .expect("Failed to initialize vector graphics interface");
403
404        tracing::info!("building root widget");
405        self.widget = Some((self.builder)(
406            AppContext::new(self.update.clone(), self.info.diagnostics),
407            self.state.take().unwrap(),
408        ));
409
410        self.window = Some(window);
411    }
412
413    #[inline(always)]
414    #[tracing::instrument(level = "trace", skip_all, fields(event =
415        ?event))]
416    fn window_event(
417        &mut self,
418        event_loop: &ActiveEventLoop,
419        window_id: WindowId,
420        mut event: WindowEvent,
421    ) {
422        tracing::trace!("running plugin window event");
423        self.plugins.run(|pl| {
424            pl.on_window_event(
425                &mut event,
426                &mut self.config,
427                self.window.as_ref().unwrap(),
428                &mut self.scene,
429                &mut self.taffy,
430                self.window_node,
431                &mut self.info,
432                &self.update,
433                &mut self.last_update,
434                event_loop,
435            )
436        });
437
438        if let Some(window) = &self.window
439            && window.id() == window_id
440        {
441            match event {
442                WindowEvent::Resized(new_size) => {
443                    tracing::debug!("resizing window to {new_size:?}");
444
445                    if new_size.width != 0 && new_size.height != 0 {
446                        tracing::trace!("resizing vector graphics interface");
447                        self.graphics
448                            .resize(
449                                window.clone(),
450                                event_loop,
451                                Vector2::new(new_size.width, new_size.height),
452                            )
453                            .expect("Failed to resize vector graphics interface");
454
455                        tracing::trace!("resizing root layout node");
456                        self.taffy
457                            .set_style(
458                                self.window_node,
459                                Style {
460                                    size: Size::<Dimension> {
461                                        width: Dimension::length(new_size.width as f32),
462                                        height: Dimension::length(new_size.height as f32),
463                                    },
464                                    ..Default::default()
465                                },
466                            )
467                            .expect("Failed to set window node style");
468
469                        self.info.size =
470                            Vector2::new(new_size.width as f64, new_size.height as f64);
471
472                        self.request_redraw();
473
474                        self.update.insert(Update::DRAW | Update::LAYOUT);
475                    } else {
476                        tracing::trace!("window size is 0x0, ignoring resize event");
477                    }
478                },
479
480                WindowEvent::CloseRequested => {
481                    self.close(window.clone(), event_loop);
482                },
483
484                WindowEvent::RedrawRequested => {
485                    window.request_redraw();
486                    self.update(event_loop);
487                },
488
489                WindowEvent::CursorLeft { .. } => {
490                    self.info.cursor_pos = None;
491                    self.request_redraw();
492                },
493
494                WindowEvent::CursorMoved { position, .. } => {
495                    self.info.cursor_pos = Some(Vector2::new(position.x as f32, position.y as f32));
496                    self.request_redraw();
497                },
498
499                WindowEvent::KeyboardInput {
500                    event,
501                    device_id,
502                    is_synthetic,
503                } => {
504                    if !is_synthetic {
505                        tracing::trace!("keyboard input {event:?}");
506
507                        self.info.keys.push((device_id, event));
508                        self.request_redraw();
509                    }
510                },
511
512                WindowEvent::MouseInput {
513                    device_id,
514                    button,
515                    state,
516                } => {
517                    tracing::trace!("mouse input {button:?} {state:?}");
518
519                    self.info.buttons.push((device_id, button, state));
520                    self.request_redraw();
521                },
522
523                WindowEvent::MouseWheel { delta, .. } => {
524                    tracing::trace!("mouse wheel {delta:?}");
525                    self.info.mouse_scroll_delta = Some(delta);
526                    self.request_redraw();
527                },
528
529                WindowEvent::Destroyed => tracing::info!("window destroyed"),
530
531                _ => (),
532            }
533        }
534    }
535
536    #[cold]
537    #[tracing::instrument(level = "trace", skip_all)]
538    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
539        tracing::trace!("destroying vector graphics interface");
540        let window = self.window.clone().unwrap();
541        self.graphics
542            .uninit(window, event_loop)
543            .expect("Failed to destroy vector graphics interface");
544
545        tracing::trace!("destroying window");
546        self.window = None;
547
548        tracing::trace!("running plugin suspensions");
549        self.plugins.run(|pl| {
550            pl.on_suspended(
551                &mut self.config,
552                &mut self.scene,
553                &mut self.taffy,
554                self.window_node,
555                &mut self.info,
556                &self.update,
557                &mut self.last_update,
558                event_loop,
559            )
560        });
561
562        self.info.reset();
563    }
564}