presenter.rs

  1use crate::{
  2    app::{AppContext, MutableAppContext, WindowInvalidation},
  3    elements::Element,
  4    font_cache::FontCache,
  5    geometry::rect::RectF,
  6    json::{self, ToJson},
  7    platform::{CursorStyle, Event},
  8    scene::CursorRegion,
  9    text_layout::TextLayoutCache,
 10    Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
 11    FontSystem, ModelHandle, MouseRegion, MouseRegionId, ReadModel, ReadView, RenderContext,
 12    RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle,
 13    WeakViewHandle,
 14};
 15use pathfinder_geometry::vector::{vec2f, Vector2F};
 16use serde_json::json;
 17use std::{
 18    collections::{HashMap, HashSet},
 19    marker::PhantomData,
 20    ops::{Deref, DerefMut},
 21    sync::Arc,
 22};
 23
 24pub struct Presenter {
 25    window_id: usize,
 26    pub(crate) rendered_views: HashMap<usize, ElementBox>,
 27    parents: HashMap<usize, usize>,
 28    cursor_regions: Vec<CursorRegion>,
 29    mouse_regions: Vec<MouseRegion>,
 30    font_cache: Arc<FontCache>,
 31    text_layout_cache: TextLayoutCache,
 32    asset_cache: Arc<AssetCache>,
 33    last_mouse_moved_event: Option<Event>,
 34    hovered_region_ids: HashSet<MouseRegionId>,
 35    clicked_region: Option<MouseRegion>,
 36    prev_drag_position: Option<Vector2F>,
 37    titlebar_height: f32,
 38}
 39
 40impl Presenter {
 41    pub fn new(
 42        window_id: usize,
 43        titlebar_height: f32,
 44        font_cache: Arc<FontCache>,
 45        text_layout_cache: TextLayoutCache,
 46        asset_cache: Arc<AssetCache>,
 47        cx: &mut MutableAppContext,
 48    ) -> Self {
 49        Self {
 50            window_id,
 51            rendered_views: cx.render_views(window_id, titlebar_height),
 52            parents: HashMap::new(),
 53            cursor_regions: Default::default(),
 54            mouse_regions: Default::default(),
 55            font_cache,
 56            text_layout_cache,
 57            asset_cache,
 58            last_mouse_moved_event: None,
 59            hovered_region_ids: Default::default(),
 60            clicked_region: None,
 61            prev_drag_position: None,
 62            titlebar_height,
 63        }
 64    }
 65
 66    pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
 67        let mut path = Vec::new();
 68        if let Some(view_id) = app.focused_view_id(self.window_id) {
 69            self.compute_dispatch_path_from(view_id, &mut path)
 70        }
 71        path
 72    }
 73
 74    pub(crate) fn compute_dispatch_path_from(&self, mut view_id: usize, path: &mut Vec<usize>) {
 75        path.push(view_id);
 76        while let Some(parent_id) = self.parents.get(&view_id).copied() {
 77            path.push(parent_id);
 78            view_id = parent_id;
 79        }
 80        path.reverse();
 81    }
 82
 83    pub fn invalidate(
 84        &mut self,
 85        invalidation: &mut WindowInvalidation,
 86        cx: &mut MutableAppContext,
 87    ) {
 88        cx.start_frame();
 89        for view_id in &invalidation.removed {
 90            invalidation.updated.remove(&view_id);
 91            self.rendered_views.remove(&view_id);
 92            self.parents.remove(&view_id);
 93        }
 94        for view_id in &invalidation.updated {
 95            self.rendered_views.insert(
 96                *view_id,
 97                cx.render_view(RenderParams {
 98                    window_id: self.window_id,
 99                    view_id: *view_id,
100                    titlebar_height: self.titlebar_height,
101                    hovered_region_ids: self.hovered_region_ids.clone(),
102                    clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
103                    refreshing: false,
104                })
105                .unwrap(),
106            );
107        }
108    }
109
110    pub fn refresh(&mut self, invalidation: &mut WindowInvalidation, cx: &mut MutableAppContext) {
111        self.invalidate(invalidation, cx);
112        for (view_id, view) in &mut self.rendered_views {
113            if !invalidation.updated.contains(view_id) {
114                *view = cx
115                    .render_view(RenderParams {
116                        window_id: self.window_id,
117                        view_id: *view_id,
118                        titlebar_height: self.titlebar_height,
119                        hovered_region_ids: self.hovered_region_ids.clone(),
120                        clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
121                        refreshing: true,
122                    })
123                    .unwrap();
124            }
125        }
126    }
127
128    pub fn build_scene(
129        &mut self,
130        window_size: Vector2F,
131        scale_factor: f32,
132        refreshing: bool,
133        cx: &mut MutableAppContext,
134    ) -> Scene {
135        let mut scene = Scene::new(scale_factor);
136
137        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
138            self.layout(window_size, refreshing, cx);
139            let mut paint_cx = self.build_paint_context(&mut scene, cx);
140            paint_cx.paint(
141                root_view_id,
142                Vector2F::zero(),
143                RectF::new(Vector2F::zero(), window_size),
144            );
145            self.text_layout_cache.finish_frame();
146            self.cursor_regions = scene.cursor_regions();
147            self.mouse_regions = scene.mouse_regions();
148
149            if cx.window_is_active(self.window_id) {
150                if let Some(event) = self.last_mouse_moved_event.clone() {
151                    self.dispatch_event(event, cx)
152                }
153            }
154        } else {
155            log::error!("could not find root_view_id for window {}", self.window_id);
156        }
157
158        scene
159    }
160
161    fn layout(&mut self, size: Vector2F, refreshing: bool, cx: &mut MutableAppContext) {
162        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
163            self.build_layout_context(refreshing, cx)
164                .layout(root_view_id, SizeConstraint::strict(size));
165        }
166    }
167
168    pub fn build_layout_context<'a>(
169        &'a mut self,
170        refreshing: bool,
171        cx: &'a mut MutableAppContext,
172    ) -> LayoutContext<'a> {
173        LayoutContext {
174            rendered_views: &mut self.rendered_views,
175            parents: &mut self.parents,
176            font_cache: &self.font_cache,
177            font_system: cx.platform().fonts(),
178            text_layout_cache: &self.text_layout_cache,
179            asset_cache: &self.asset_cache,
180            view_stack: Vec::new(),
181            refreshing,
182            hovered_region_ids: self.hovered_region_ids.clone(),
183            clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
184            titlebar_height: self.titlebar_height,
185            app: cx,
186        }
187    }
188
189    pub fn build_paint_context<'a>(
190        &'a mut self,
191        scene: &'a mut Scene,
192        cx: &'a mut MutableAppContext,
193    ) -> PaintContext {
194        PaintContext {
195            scene,
196            font_cache: &self.font_cache,
197            text_layout_cache: &self.text_layout_cache,
198            rendered_views: &mut self.rendered_views,
199            view_stack: Vec::new(),
200            app: cx,
201        }
202    }
203
204    pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
205        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
206            let mut invalidated_views = Vec::new();
207            let mut hovered_regions = Vec::new();
208            let mut unhovered_regions = Vec::new();
209            let mut clicked_region = None;
210            let mut dragged_region = None;
211
212            match event {
213                Event::LeftMouseDown { position, .. } => {
214                    for region in self.mouse_regions.iter().rev() {
215                        if region.bounds.contains_point(position) {
216                            invalidated_views.push(region.view_id);
217                            self.clicked_region = Some(region.clone());
218                            self.prev_drag_position = Some(position);
219                            break;
220                        }
221                    }
222                }
223                Event::LeftMouseUp {
224                    position,
225                    click_count,
226                    ..
227                } => {
228                    self.prev_drag_position.take();
229                    if let Some(region) = self.clicked_region.take() {
230                        invalidated_views.push(region.view_id);
231                        if region.bounds.contains_point(position) {
232                            clicked_region = Some((region, position, click_count));
233                        }
234                    }
235                }
236                Event::MouseMoved {
237                    position,
238                    left_mouse_down,
239                } => {
240                    self.last_mouse_moved_event = Some(event.clone());
241
242                    if !left_mouse_down {
243                        let mut style_to_assign = CursorStyle::Arrow;
244                        for region in self.cursor_regions.iter().rev() {
245                            if region.bounds.contains_point(position) {
246                                style_to_assign = region.style;
247                                break;
248                            }
249                        }
250                        cx.platform().set_cursor_style(style_to_assign);
251
252                        for region in self.mouse_regions.iter().rev() {
253                            let region_id = region.id();
254                            if region.bounds.contains_point(position) {
255                                if !self.hovered_region_ids.contains(&region_id) {
256                                    invalidated_views.push(region.view_id);
257                                    hovered_regions.push(region.clone());
258                                    self.hovered_region_ids.insert(region_id);
259                                }
260                            } else {
261                                if self.hovered_region_ids.contains(&region_id) {
262                                    invalidated_views.push(region.view_id);
263                                    unhovered_regions.push(region.clone());
264                                    self.hovered_region_ids.remove(&region_id);
265                                }
266                            }
267                        }
268                    }
269                }
270                Event::LeftMouseDragged { position } => {
271                    if let Some((clicked_region, prev_drag_position)) = self
272                        .clicked_region
273                        .as_ref()
274                        .zip(self.prev_drag_position.as_mut())
275                    {
276                        dragged_region =
277                            Some((clicked_region.clone(), position - *prev_drag_position));
278                        *prev_drag_position = position;
279                    }
280
281                    self.last_mouse_moved_event = Some(Event::MouseMoved {
282                        position,
283                        left_mouse_down: true,
284                    });
285                }
286                _ => {}
287            }
288
289            let mut event_cx = self.build_event_context(cx);
290            for unhovered_region in unhovered_regions {
291                if let Some(hover_callback) = unhovered_region.hover {
292                    event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
293                        hover_callback(false, event_cx);
294                    })
295                }
296            }
297
298            for hovered_region in hovered_regions {
299                if let Some(hover_callback) = hovered_region.hover {
300                    event_cx.with_current_view(hovered_region.view_id, |event_cx| {
301                        hover_callback(true, event_cx);
302                    })
303                }
304            }
305
306            if let Some((clicked_region, position, click_count)) = clicked_region {
307                if let Some(click_callback) = clicked_region.click {
308                    event_cx.with_current_view(clicked_region.view_id, |event_cx| {
309                        click_callback(position, click_count, event_cx);
310                    })
311                }
312            }
313
314            if let Some((dragged_region, delta)) = dragged_region {
315                if let Some(drag_callback) = dragged_region.drag {
316                    event_cx.with_current_view(dragged_region.view_id, |event_cx| {
317                        drag_callback(delta, event_cx);
318                    })
319                }
320            }
321
322            event_cx.dispatch_event(root_view_id, &event);
323
324            invalidated_views.extend(event_cx.invalidated_views);
325            let dispatch_directives = event_cx.dispatched_actions;
326
327            for view_id in invalidated_views {
328                cx.notify_view(self.window_id, view_id);
329            }
330
331            let mut dispatch_path = Vec::new();
332            for directive in dispatch_directives {
333                dispatch_path.clear();
334                if let Some(view_id) = directive.dispatcher_view_id {
335                    self.compute_dispatch_path_from(view_id, &mut dispatch_path);
336                }
337                cx.dispatch_action_any(self.window_id, &dispatch_path, directive.action.as_ref());
338            }
339        }
340    }
341
342    pub fn build_event_context<'a>(
343        &'a mut self,
344        cx: &'a mut MutableAppContext,
345    ) -> EventContext<'a> {
346        EventContext {
347            rendered_views: &mut self.rendered_views,
348            dispatched_actions: Default::default(),
349            font_cache: &self.font_cache,
350            text_layout_cache: &self.text_layout_cache,
351            view_stack: Default::default(),
352            invalidated_views: Default::default(),
353            notify_count: 0,
354            app: cx,
355        }
356    }
357
358    pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
359        let view = cx.root_view(self.window_id)?;
360        Some(json!({
361            "root_view": view.debug_json(cx),
362            "root_element": self.rendered_views.get(&view.id())
363                .map(|root_element| {
364                    root_element.debug(&DebugContext {
365                        rendered_views: &self.rendered_views,
366                        font_cache: &self.font_cache,
367                        app: cx,
368                    })
369                })
370        }))
371    }
372}
373
374pub struct DispatchDirective {
375    pub dispatcher_view_id: Option<usize>,
376    pub action: Box<dyn Action>,
377}
378
379pub struct LayoutContext<'a> {
380    rendered_views: &'a mut HashMap<usize, ElementBox>,
381    parents: &'a mut HashMap<usize, usize>,
382    view_stack: Vec<usize>,
383    pub font_cache: &'a Arc<FontCache>,
384    pub font_system: Arc<dyn FontSystem>,
385    pub text_layout_cache: &'a TextLayoutCache,
386    pub asset_cache: &'a AssetCache,
387    pub app: &'a mut MutableAppContext,
388    pub refreshing: bool,
389    titlebar_height: f32,
390    hovered_region_ids: HashSet<MouseRegionId>,
391    clicked_region_id: Option<MouseRegionId>,
392}
393
394impl<'a> LayoutContext<'a> {
395    fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
396        if let Some(parent_id) = self.view_stack.last() {
397            self.parents.insert(view_id, *parent_id);
398        }
399        self.view_stack.push(view_id);
400        let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
401        let size = rendered_view.layout(constraint, self);
402        self.rendered_views.insert(view_id, rendered_view);
403        self.view_stack.pop();
404        size
405    }
406
407    pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
408    where
409        F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
410        V: View,
411    {
412        handle.update(self.app, |view, cx| {
413            let mut render_cx = RenderContext {
414                app: cx,
415                window_id: handle.window_id(),
416                view_id: handle.id(),
417                view_type: PhantomData,
418                titlebar_height: self.titlebar_height,
419                hovered_region_ids: self.hovered_region_ids.clone(),
420                clicked_region_id: self.clicked_region_id,
421                refreshing: self.refreshing,
422            };
423            f(view, &mut render_cx)
424        })
425    }
426}
427
428impl<'a> Deref for LayoutContext<'a> {
429    type Target = MutableAppContext;
430
431    fn deref(&self) -> &Self::Target {
432        self.app
433    }
434}
435
436impl<'a> DerefMut for LayoutContext<'a> {
437    fn deref_mut(&mut self) -> &mut Self::Target {
438        self.app
439    }
440}
441
442impl<'a> ReadView for LayoutContext<'a> {
443    fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
444        self.app.read_view(handle)
445    }
446}
447
448impl<'a> ReadModel for LayoutContext<'a> {
449    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
450        self.app.read_model(handle)
451    }
452}
453
454impl<'a> UpgradeModelHandle for LayoutContext<'a> {
455    fn upgrade_model_handle<T: Entity>(
456        &self,
457        handle: &WeakModelHandle<T>,
458    ) -> Option<ModelHandle<T>> {
459        self.app.upgrade_model_handle(handle)
460    }
461
462    fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
463        self.app.model_handle_is_upgradable(handle)
464    }
465
466    fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
467        self.app.upgrade_any_model_handle(handle)
468    }
469}
470
471impl<'a> UpgradeViewHandle for LayoutContext<'a> {
472    fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
473        self.app.upgrade_view_handle(handle)
474    }
475
476    fn upgrade_any_view_handle(&self, handle: &crate::AnyWeakViewHandle) -> Option<AnyViewHandle> {
477        self.app.upgrade_any_view_handle(handle)
478    }
479}
480
481pub struct PaintContext<'a> {
482    rendered_views: &'a mut HashMap<usize, ElementBox>,
483    view_stack: Vec<usize>,
484    pub scene: &'a mut Scene,
485    pub font_cache: &'a FontCache,
486    pub text_layout_cache: &'a TextLayoutCache,
487    pub app: &'a AppContext,
488}
489
490impl<'a> PaintContext<'a> {
491    fn paint(&mut self, view_id: usize, origin: Vector2F, visible_bounds: RectF) {
492        if let Some(mut tree) = self.rendered_views.remove(&view_id) {
493            self.view_stack.push(view_id);
494            tree.paint(origin, visible_bounds, self);
495            self.rendered_views.insert(view_id, tree);
496            self.view_stack.pop();
497        }
498    }
499
500    pub fn current_view_id(&self) -> usize {
501        *self.view_stack.last().unwrap()
502    }
503}
504
505impl<'a> Deref for PaintContext<'a> {
506    type Target = AppContext;
507
508    fn deref(&self) -> &Self::Target {
509        self.app
510    }
511}
512
513pub struct EventContext<'a> {
514    rendered_views: &'a mut HashMap<usize, ElementBox>,
515    dispatched_actions: Vec<DispatchDirective>,
516    pub font_cache: &'a FontCache,
517    pub text_layout_cache: &'a TextLayoutCache,
518    pub app: &'a mut MutableAppContext,
519    pub notify_count: usize,
520    view_stack: Vec<usize>,
521    invalidated_views: HashSet<usize>,
522}
523
524impl<'a> EventContext<'a> {
525    fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
526        if let Some(mut element) = self.rendered_views.remove(&view_id) {
527            let result =
528                self.with_current_view(view_id, |this| element.dispatch_event(event, this));
529            self.rendered_views.insert(view_id, element);
530            result
531        } else {
532            false
533        }
534    }
535
536    fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
537    where
538        F: FnOnce(&mut Self) -> T,
539    {
540        self.view_stack.push(view_id);
541        let result = f(self);
542        self.view_stack.pop();
543        result
544    }
545
546    pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
547        self.dispatched_actions.push(DispatchDirective {
548            dispatcher_view_id: self.view_stack.last().copied(),
549            action,
550        });
551    }
552
553    pub fn dispatch_action<A: Action>(&mut self, action: A) {
554        self.dispatch_any_action(Box::new(action));
555    }
556
557    pub fn notify(&mut self) {
558        self.notify_count += 1;
559        if let Some(view_id) = self.view_stack.last() {
560            self.invalidated_views.insert(*view_id);
561        }
562    }
563
564    pub fn notify_count(&self) -> usize {
565        self.notify_count
566    }
567}
568
569impl<'a> Deref for EventContext<'a> {
570    type Target = MutableAppContext;
571
572    fn deref(&self) -> &Self::Target {
573        self.app
574    }
575}
576
577impl<'a> DerefMut for EventContext<'a> {
578    fn deref_mut(&mut self) -> &mut Self::Target {
579        self.app
580    }
581}
582
583pub struct DebugContext<'a> {
584    rendered_views: &'a HashMap<usize, ElementBox>,
585    pub font_cache: &'a FontCache,
586    pub app: &'a AppContext,
587}
588
589#[derive(Clone, Copy, Debug, Eq, PartialEq)]
590pub enum Axis {
591    Horizontal,
592    Vertical,
593}
594
595impl Axis {
596    pub fn invert(self) -> Self {
597        match self {
598            Self::Horizontal => Self::Vertical,
599            Self::Vertical => Self::Horizontal,
600        }
601    }
602}
603
604impl ToJson for Axis {
605    fn to_json(&self) -> serde_json::Value {
606        match self {
607            Axis::Horizontal => json!("horizontal"),
608            Axis::Vertical => json!("vertical"),
609        }
610    }
611}
612
613pub trait Vector2FExt {
614    fn along(self, axis: Axis) -> f32;
615}
616
617impl Vector2FExt for Vector2F {
618    fn along(self, axis: Axis) -> f32 {
619        match axis {
620            Axis::Horizontal => self.x(),
621            Axis::Vertical => self.y(),
622        }
623    }
624}
625
626#[derive(Copy, Clone, Debug)]
627pub struct SizeConstraint {
628    pub min: Vector2F,
629    pub max: Vector2F,
630}
631
632impl SizeConstraint {
633    pub fn new(min: Vector2F, max: Vector2F) -> Self {
634        Self { min, max }
635    }
636
637    pub fn strict(size: Vector2F) -> Self {
638        Self {
639            min: size,
640            max: size,
641        }
642    }
643
644    pub fn strict_along(axis: Axis, max: f32) -> Self {
645        match axis {
646            Axis::Horizontal => Self {
647                min: vec2f(max, 0.0),
648                max: vec2f(max, f32::INFINITY),
649            },
650            Axis::Vertical => Self {
651                min: vec2f(0.0, max),
652                max: vec2f(f32::INFINITY, max),
653            },
654        }
655    }
656
657    pub fn max_along(&self, axis: Axis) -> f32 {
658        match axis {
659            Axis::Horizontal => self.max.x(),
660            Axis::Vertical => self.max.y(),
661        }
662    }
663
664    pub fn min_along(&self, axis: Axis) -> f32 {
665        match axis {
666            Axis::Horizontal => self.min.x(),
667            Axis::Vertical => self.min.y(),
668        }
669    }
670
671    pub fn constrain(&self, size: Vector2F) -> Vector2F {
672        vec2f(
673            size.x().min(self.max.x()).max(self.min.x()),
674            size.y().min(self.max.y()).max(self.min.y()),
675        )
676    }
677}
678
679impl ToJson for SizeConstraint {
680    fn to_json(&self) -> serde_json::Value {
681        json!({
682            "min": self.min.to_json(),
683            "max": self.max.to_json(),
684        })
685    }
686}
687
688pub struct ChildView {
689    view: AnyViewHandle,
690}
691
692impl ChildView {
693    pub fn new(view: impl Into<AnyViewHandle>) -> Self {
694        Self { view: view.into() }
695    }
696}
697
698impl Element for ChildView {
699    type LayoutState = ();
700    type PaintState = ();
701
702    fn layout(
703        &mut self,
704        constraint: SizeConstraint,
705        cx: &mut LayoutContext,
706    ) -> (Vector2F, Self::LayoutState) {
707        let size = cx.layout(self.view.id(), constraint);
708        (size, ())
709    }
710
711    fn paint(
712        &mut self,
713        bounds: RectF,
714        visible_bounds: RectF,
715        _: &mut Self::LayoutState,
716        cx: &mut PaintContext,
717    ) -> Self::PaintState {
718        cx.paint(self.view.id(), bounds.origin(), visible_bounds);
719    }
720
721    fn dispatch_event(
722        &mut self,
723        event: &Event,
724        _: RectF,
725        _: RectF,
726        _: &mut Self::LayoutState,
727        _: &mut Self::PaintState,
728        cx: &mut EventContext,
729    ) -> bool {
730        cx.dispatch_event(self.view.id(), event)
731    }
732
733    fn debug(
734        &self,
735        bounds: RectF,
736        _: &Self::LayoutState,
737        _: &Self::PaintState,
738        cx: &DebugContext,
739    ) -> serde_json::Value {
740        json!({
741            "type": "ChildView",
742            "view_id": self.view.id(),
743            "bounds": bounds.to_json(),
744            "view": self.view.debug_json(cx.app),
745            "child": if let Some(view) = cx.rendered_views.get(&self.view.id()) {
746                view.debug(cx)
747            } else {
748                json!(null)
749            }
750        })
751    }
752}