maycoon_core/app/
handler.rs

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