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 maycoon_theme::theme::Theme;
17
18use crate::app::font_ctx::FontContext;
19use crate::app::info::AppInfo;
20use crate::app::update::Update;
21use crate::config::MayConfig;
22use crate::layout::{LayoutNode, StyleNode};
23use crate::state::State;
24use crate::widget::Widget;
25
26/// The core application handler. You should use [`MayApp`](crate::app::MayApp) instead for running applications.
27pub struct AppHandler<'a, T, W, S>
28where
29    T: Theme,
30    W: Widget<S>,
31    S: State,
32{
33    config: MayConfig<T>,
34    attrs: WindowAttributes,
35    window: Option<Arc<Window>>,
36    renderer: Option<Renderer>,
37    scene: Scene,
38    surface: Option<RenderSurface<'a>>,
39    taffy: TaffyTree,
40    window_node: NodeId,
41    widget: W,
42    state: S,
43    info: AppInfo,
44    render_ctx: Option<RenderContext>,
45    update: Update,
46    last_update: Instant,
47}
48
49impl<T, W, S> AppHandler<'_, T, W, S>
50where
51    T: Theme,
52    W: Widget<S>,
53    S: State,
54{
55    /// Create a new handler with given window attributes, config, widget and state.
56    pub fn new(
57        attrs: WindowAttributes,
58        config: MayConfig<T>,
59        widget: W,
60        state: S,
61        font_context: FontContext,
62    ) -> Self {
63        let mut taffy = TaffyTree::with_capacity(16);
64
65        // gets configured on resume
66        let window_node = taffy
67            .new_leaf(Style::default())
68            .expect("Failed to create window node");
69
70        let size = config.window.size;
71
72        Self {
73            attrs,
74            window: None,
75            renderer: None,
76            config,
77            scene: Scene::new(),
78            surface: None,
79            taffy,
80            state,
81            widget,
82            info: AppInfo {
83                font_context,
84                size,
85                ..Default::default()
86            },
87            window_node,
88            render_ctx: None,
89            update: Update::empty(),
90            last_update: Instant::now(),
91        }
92    }
93
94    /// Add the parent node and its children to the layout tree.
95    fn layout_widget(&mut self, parent: NodeId, style: &StyleNode) -> TaffyResult<()> {
96        let node = self.taffy.new_leaf(style.style.clone().into())?;
97
98        self.taffy.add_child(parent, node)?;
99
100        for child in &style.children {
101            self.layout_widget(node, child)?;
102        }
103
104        Ok(())
105    }
106
107    /// Compute the layout of the root node and its children.
108    fn compute_layout(&mut self) -> TaffyResult<()> {
109        self.taffy.compute_layout(
110            self.window_node,
111            Size::<AvailableSpace> {
112                width: AvailableSpace::Definite(
113                    self.window.as_ref().unwrap().inner_size().width as f32,
114                ),
115                height: AvailableSpace::Definite(
116                    self.window.as_ref().unwrap().inner_size().height as f32,
117                ),
118            },
119        )?;
120        Ok(())
121    }
122
123    /// Collect the computed layout of the given node and its children. Make sure to call [`AppHandler::compute_layout`] before, to not get dirty results.
124    fn collect_layout(&mut self, node: NodeId, style: &StyleNode) -> TaffyResult<LayoutNode> {
125        let mut children = Vec::with_capacity(style.children.len());
126
127        for (i, child) in style.children.iter().enumerate() {
128            children.push(self.collect_layout(self.taffy.child_at_index(node, i)?, child)?);
129        }
130
131        Ok(LayoutNode {
132            layout: *self.taffy.get_final_layout(node),
133            children,
134        })
135    }
136
137    /// Request a window redraw.
138    fn request_redraw(&self) {
139        if let Some(window) = self.window.as_ref() {
140            window.request_redraw();
141        }
142    }
143
144    /// Update the app and process events.
145    fn update(&mut self, _: &ActiveEventLoop) {
146        // completely layout widgets if taffy is not set up yet (e.g. during first update)
147        if self.taffy.child_count(self.window_node) == 0 {
148            let style = self.widget.layout_style(&self.state);
149
150            self.layout_widget(self.window_node, &style)
151                .expect("Failed to layout window");
152
153            self.compute_layout().expect("Failed to compute layout");
154
155            self.update.insert(Update::FORCE);
156        }
157
158        let style = self.widget.layout_style(&self.state);
159
160        let mut layout_node = self
161            .collect_layout(
162                self.taffy.child_at_index(self.window_node, 0).unwrap(),
163                &style,
164            )
165            .expect("Failed to collect layout");
166
167        // update call to check if app should re-evaluate
168        self.update.insert(
169            self.widget
170                .update(&layout_node, &mut self.state, &self.info),
171        );
172
173        // check if app should re-evaluate layout
174        if self.update.intersects(Update::LAYOUT | Update::FORCE) {
175            // clear all nodes (except root window node)
176            self.taffy
177                .set_children(self.window_node, &[])
178                .expect("Failed to set children");
179
180            let style = self.widget.layout_style(&self.state);
181
182            self.layout_widget(self.window_node, &style)
183                .expect("Failed to layout window");
184
185            self.compute_layout().expect("Failed to compute layout");
186
187            layout_node = self
188                .collect_layout(
189                    self.taffy.child_at_index(self.window_node, 0).unwrap(),
190                    &style,
191                )
192                .expect("Failed to collect layout");
193        }
194
195        // check if app should redraw
196        if self.update.intersects(Update::FORCE | Update::DRAW) {
197            // clear scene
198            self.scene.reset();
199
200            self.widget.render(
201                &mut self.scene,
202                &mut self.config.theme,
203                &self.info,
204                &layout_node,
205                &self.state,
206            );
207
208            let renderer = self.renderer.as_mut().expect("Renderer not initialized");
209            let render_ctx = self
210                .render_ctx
211                .as_ref()
212                .expect("Render context not initialized");
213            let surface = self.surface.as_ref().expect("Surface not initialized");
214            let window = self.window.as_ref().expect("Window not initialized");
215
216            let device_handle = render_ctx.devices.first().expect("No devices available");
217
218            // check surface validity
219            if window.inner_size().width != 0 && window.inner_size().height != 0 {
220                let surface_texture = surface
221                    .surface
222                    .get_current_texture()
223                    .expect("Failed to get surface texture");
224
225                // make sure winit knows that the surface texture is ready to be presented
226                window.pre_present_notify();
227
228                // TODO: this panics if canvas didn't change (no operation was done) in debug mode
229                renderer
230                    .render_to_surface(
231                        &device_handle.device,
232                        &device_handle.queue,
233                        &self.scene,
234                        &surface_texture,
235                        &RenderParams {
236                            base_color: self.config.theme.window_background(),
237                            width: window.inner_size().width,
238                            height: window.inner_size().height,
239                            antialiasing_method: self.config.render.antialiasing,
240                        },
241                    )
242                    .expect("Failed to render to surface");
243
244                surface_texture.present();
245            }
246        }
247
248        // check if app should re-evaluate
249        if self.update.intersects(Update::EVAL | Update::FORCE) {
250            if let Some(window) = self.window.as_ref() {
251                window.request_redraw();
252            }
253        }
254
255        // reset AppInfo and update states
256        self.info.reset();
257        self.update = Update::empty();
258
259        // update diagnostics
260        if self.last_update.elapsed() >= Duration::from_secs(1) {
261            self.last_update = Instant::now();
262
263            // calc avg updates per sec through updates per sec NOW divided by 2
264            self.info.diagnostics.updates_per_sec =
265                (self.info.diagnostics.updates_per_sec + self.info.diagnostics.updates) / 2;
266
267            // reset current updates per seconds
268            self.info.diagnostics.updates = 0;
269        } else {
270            // increase updates per sec NOW by 1
271            self.info.diagnostics.updates += 1;
272        }
273    }
274}
275
276impl<T, W, S> ApplicationHandler for AppHandler<'_, T, W, S>
277where
278    T: Theme,
279    W: Widget<S>,
280    S: State,
281{
282    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
283        let mut render_ctx = RenderContext::new();
284
285        self.window = Some(Arc::new(
286            event_loop
287                .create_window(self.attrs.clone())
288                .expect("Failed to create window"),
289        ));
290
291        self.taffy
292            .set_style(
293                self.window_node,
294                Style {
295                    size: Size::<Dimension> {
296                        width: Dimension::Length(
297                            self.window.as_ref().unwrap().inner_size().width as f32,
298                        ),
299                        height: Dimension::Length(
300                            self.window.as_ref().unwrap().inner_size().height as f32,
301                        ),
302                    },
303                    ..Default::default()
304                },
305            )
306            .expect("Failed to set window node style");
307
308        self.surface = Some(
309            crate::tasks::block_on(async {
310                render_ctx
311                    .create_surface(
312                        self.window.clone().unwrap(),
313                        self.window.as_ref().unwrap().inner_size().width,
314                        self.window.as_ref().unwrap().inner_size().height,
315                        self.config.render.present_mode,
316                    )
317                    .await
318            })
319            .expect("Failed to create surface"),
320        );
321
322        // select first device available
323        // TODO: support device filters
324        let device_handle = render_ctx.devices.first().expect("Failed to select device");
325
326        self.renderer = Some(
327            Renderer::new(
328                &device_handle.device,
329                RendererOptions {
330                    surface_format: Some(self.surface.as_ref().unwrap().format),
331                    use_cpu: self.config.render.cpu,
332                    antialiasing_support: match self.config.render.antialiasing {
333                        AaConfig::Area => AaSupport::area_only(),
334
335                        AaConfig::Msaa8 => AaSupport {
336                            area: false,
337                            msaa8: true,
338                            msaa16: false,
339                        },
340
341                        AaConfig::Msaa16 => AaSupport {
342                            area: false,
343                            msaa8: false,
344                            msaa16: true,
345                        },
346                    },
347                    num_init_threads: self.config.render.init_threads,
348                },
349            )
350            .expect("Failed to create renderer"),
351        );
352
353        self.render_ctx = Some(render_ctx);
354        self.update = Update::FORCE;
355    }
356
357    fn window_event(
358        &mut self,
359        event_loop: &ActiveEventLoop,
360        window_id: WindowId,
361        event: WindowEvent,
362    ) {
363        if let Some(window) = &self.window {
364            if window.id() == window_id {
365                match event {
366                    WindowEvent::Resized(new_size) => {
367                        if new_size.width != 0 && new_size.height != 0 {
368                            if let Some(ctx) = &self.render_ctx {
369                                if let Some(surface) = &mut self.surface {
370                                    ctx.resize_surface(surface, new_size.width, new_size.height);
371                                }
372                            }
373
374                            self.taffy
375                                .set_style(
376                                    self.window_node,
377                                    Style {
378                                        size: Size::<Dimension> {
379                                            width: Dimension::Length(new_size.width as f32),
380                                            height: Dimension::Length(new_size.height as f32),
381                                        },
382                                        ..Default::default()
383                                    },
384                                )
385                                .expect("Failed to set window node style");
386
387                            self.info.size =
388                                Vector2::new(new_size.width as f64, new_size.height as f64);
389
390                            self.request_redraw();
391
392                            self.update.insert(Update::DRAW | Update::LAYOUT);
393                        }
394                    },
395
396                    WindowEvent::CloseRequested => {
397                        if let Some(render_ctx) = self.render_ctx.as_mut() {
398                            for handle in &render_ctx.devices {
399                                handle.device.destroy();
400                            }
401                        }
402
403                        if self.config.window.close_on_request {
404                            event_loop.exit();
405                        }
406                    },
407
408                    WindowEvent::RedrawRequested => {
409                        self.update(event_loop);
410                    },
411
412                    WindowEvent::CursorLeft { .. } => {
413                        self.info.cursor_pos = None;
414                        self.request_redraw();
415                    },
416
417                    WindowEvent::CursorMoved { position, .. } => {
418                        self.info.cursor_pos = Some(Vector2::new(position.x, position.y));
419                        self.request_redraw();
420                    },
421
422                    WindowEvent::KeyboardInput {
423                        event,
424                        device_id,
425                        is_synthetic,
426                    } => {
427                        if !is_synthetic {
428                            self.info.keys.push((device_id, event));
429                            self.request_redraw();
430                        }
431                    },
432
433                    WindowEvent::MouseInput {
434                        device_id,
435                        button,
436                        state,
437                    } => {
438                        self.info.buttons.push((device_id, button, state));
439                        self.request_redraw();
440                    },
441
442                    _ => (),
443                }
444            }
445        }
446    }
447
448    fn suspended(&mut self, _: &ActiveEventLoop) {
449        self.window = None;
450        self.surface = None;
451        self.render_ctx = None;
452        self.renderer = None;
453        self.info.reset();
454    }
455}