presenter.rs

  1use crate::{
  2    app::{AppContext, MutableAppContext, WindowInvalidation},
  3    elements::Element,
  4    font_cache::FontCache,
  5    json::{self, ToJson},
  6    platform::Event,
  7    text_layout::TextLayoutCache,
  8    AssetCache, ElementBox, Scene,
  9};
 10use pathfinder_geometry::vector::{vec2f, Vector2F};
 11use serde_json::json;
 12use std::{
 13    any::Any,
 14    collections::{HashMap, HashSet},
 15    sync::Arc,
 16};
 17
 18pub struct Presenter {
 19    window_id: usize,
 20    rendered_views: HashMap<usize, ElementBox>,
 21    parents: HashMap<usize, usize>,
 22    font_cache: Arc<FontCache>,
 23    text_layout_cache: TextLayoutCache,
 24    asset_cache: Arc<AssetCache>,
 25    last_mouse_moved_event: Option<Event>,
 26}
 27
 28impl Presenter {
 29    pub fn new(
 30        window_id: usize,
 31        titlebar_height: f32,
 32        font_cache: Arc<FontCache>,
 33        text_layout_cache: TextLayoutCache,
 34        asset_cache: Arc<AssetCache>,
 35        cx: &MutableAppContext,
 36    ) -> Self {
 37        Self {
 38            window_id,
 39            rendered_views: cx.render_views(window_id, titlebar_height),
 40            parents: HashMap::new(),
 41            font_cache,
 42            text_layout_cache,
 43            asset_cache,
 44            last_mouse_moved_event: None,
 45        }
 46    }
 47
 48    pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
 49        let mut view_id = app.focused_view_id(self.window_id).unwrap();
 50        let mut path = vec![view_id];
 51        while let Some(parent_id) = self.parents.get(&view_id).copied() {
 52            path.push(parent_id);
 53            view_id = parent_id;
 54        }
 55        path.reverse();
 56        path
 57    }
 58
 59    pub fn invalidate(
 60        &mut self,
 61        mut invalidation: WindowInvalidation,
 62        titlebar_height: f32,
 63        cx: &AppContext,
 64    ) {
 65        for view_id in invalidation.removed {
 66            invalidation.updated.remove(&view_id);
 67            self.rendered_views.remove(&view_id);
 68            self.parents.remove(&view_id);
 69        }
 70        for view_id in invalidation.updated {
 71            self.rendered_views.insert(
 72                view_id,
 73                cx.render_view(self.window_id, view_id, titlebar_height)
 74                    .unwrap(),
 75            );
 76        }
 77    }
 78
 79    pub fn build_scene(
 80        &mut self,
 81        window_size: Vector2F,
 82        titlebar_height: f32,
 83        scale_factor: f32,
 84        cx: &mut MutableAppContext,
 85    ) -> Scene {
 86        let mut scene = Scene::new(scale_factor);
 87
 88        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
 89            self.layout(window_size, titlebar_height, cx);
 90            self.after_layout(cx);
 91            let mut paint_cx = PaintContext {
 92                scene: &mut scene,
 93                font_cache: &self.font_cache,
 94                text_layout_cache: &self.text_layout_cache,
 95                rendered_views: &mut self.rendered_views,
 96                app: cx.as_ref(),
 97            };
 98            paint_cx.paint(root_view_id, Vector2F::zero());
 99            self.text_layout_cache.finish_frame();
100
101            if let Some(event) = self.last_mouse_moved_event.clone() {
102                self.dispatch_event(event, cx)
103            }
104        } else {
105            log::error!("could not find root_view_id for window {}", self.window_id);
106        }
107
108        scene
109    }
110
111    fn layout(&mut self, size: Vector2F, titlebar_height: f32, cx: &mut MutableAppContext) {
112        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
113            let mut layout_ctx = LayoutContext {
114                rendered_views: &mut self.rendered_views,
115                parents: &mut self.parents,
116                font_cache: &self.font_cache,
117                text_layout_cache: &self.text_layout_cache,
118                asset_cache: &self.asset_cache,
119                view_stack: Vec::new(),
120                app: cx,
121                titlebar_height,
122            };
123            layout_ctx.layout(root_view_id, SizeConstraint::strict(size));
124        }
125    }
126
127    fn after_layout(&mut self, cx: &mut MutableAppContext) {
128        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
129            let mut layout_cx = AfterLayoutContext {
130                rendered_views: &mut self.rendered_views,
131                font_cache: &self.font_cache,
132                text_layout_cache: &self.text_layout_cache,
133                app: cx,
134            };
135            layout_cx.after_layout(root_view_id);
136        }
137    }
138
139    pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
140        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
141            if matches!(event, Event::MouseMoved { .. }) {
142                self.last_mouse_moved_event = Some(event.clone());
143            }
144
145            let mut event_cx = EventContext {
146                rendered_views: &mut self.rendered_views,
147                actions: Default::default(),
148                font_cache: &self.font_cache,
149                text_layout_cache: &self.text_layout_cache,
150                view_stack: Default::default(),
151                invalidated_views: Default::default(),
152                app: cx,
153            };
154            event_cx.dispatch_event(root_view_id, &event);
155
156            let invalidated_views = event_cx.invalidated_views;
157            let actions = event_cx.actions;
158
159            for view_id in invalidated_views {
160                cx.notify_view(self.window_id, view_id);
161            }
162            for action in actions {
163                cx.dispatch_action_any(
164                    self.window_id,
165                    &action.path,
166                    action.name,
167                    action.arg.as_ref(),
168                );
169            }
170        }
171    }
172
173    pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
174        cx.root_view_id(self.window_id)
175            .and_then(|root_view_id| self.rendered_views.get(&root_view_id))
176            .map(|root_element| {
177                root_element.debug(&DebugContext {
178                    rendered_views: &self.rendered_views,
179                    font_cache: &self.font_cache,
180                    app: cx,
181                })
182            })
183    }
184}
185
186pub struct ActionToDispatch {
187    pub path: Vec<usize>,
188    pub name: &'static str,
189    pub arg: Box<dyn Any>,
190}
191
192pub struct LayoutContext<'a> {
193    rendered_views: &'a mut HashMap<usize, ElementBox>,
194    parents: &'a mut HashMap<usize, usize>,
195    pub font_cache: &'a FontCache,
196    pub text_layout_cache: &'a TextLayoutCache,
197    pub asset_cache: &'a AssetCache,
198    pub app: &'a mut MutableAppContext,
199    view_stack: Vec<usize>,
200    pub titlebar_height: f32,
201}
202
203impl<'a> LayoutContext<'a> {
204    fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
205        if let Some(parent_id) = self.view_stack.last() {
206            self.parents.insert(view_id, *parent_id);
207        }
208        self.view_stack.push(view_id);
209        let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
210        let size = rendered_view.layout(constraint, self);
211        self.rendered_views.insert(view_id, rendered_view);
212        self.view_stack.pop();
213        size
214    }
215}
216
217pub struct AfterLayoutContext<'a> {
218    rendered_views: &'a mut HashMap<usize, ElementBox>,
219    pub font_cache: &'a FontCache,
220    pub text_layout_cache: &'a TextLayoutCache,
221    pub app: &'a mut MutableAppContext,
222}
223
224impl<'a> AfterLayoutContext<'a> {
225    fn after_layout(&mut self, view_id: usize) {
226        if let Some(mut view) = self.rendered_views.remove(&view_id) {
227            view.after_layout(self);
228            self.rendered_views.insert(view_id, view);
229        }
230    }
231}
232
233pub struct PaintContext<'a> {
234    rendered_views: &'a mut HashMap<usize, ElementBox>,
235    pub scene: &'a mut Scene,
236    pub font_cache: &'a FontCache,
237    pub text_layout_cache: &'a TextLayoutCache,
238    pub app: &'a AppContext,
239}
240
241impl<'a> PaintContext<'a> {
242    fn paint(&mut self, view_id: usize, origin: Vector2F) {
243        if let Some(mut tree) = self.rendered_views.remove(&view_id) {
244            tree.paint(origin, self);
245            self.rendered_views.insert(view_id, tree);
246        }
247    }
248}
249
250pub struct EventContext<'a> {
251    rendered_views: &'a mut HashMap<usize, ElementBox>,
252    actions: Vec<ActionToDispatch>,
253    pub font_cache: &'a FontCache,
254    pub text_layout_cache: &'a TextLayoutCache,
255    pub app: &'a mut MutableAppContext,
256    view_stack: Vec<usize>,
257    invalidated_views: HashSet<usize>,
258}
259
260impl<'a> EventContext<'a> {
261    fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
262        if let Some(mut element) = self.rendered_views.remove(&view_id) {
263            self.view_stack.push(view_id);
264            let result = element.dispatch_event(event, self);
265            self.view_stack.pop();
266            self.rendered_views.insert(view_id, element);
267            result
268        } else {
269            false
270        }
271    }
272
273    pub fn dispatch_action<A: 'static + Any>(&mut self, name: &'static str, arg: A) {
274        self.actions.push(ActionToDispatch {
275            path: self.view_stack.clone(),
276            name,
277            arg: Box::new(arg),
278        });
279    }
280
281    pub fn notify(&mut self) {
282        self.invalidated_views
283            .insert(*self.view_stack.last().unwrap());
284    }
285}
286
287pub struct DebugContext<'a> {
288    rendered_views: &'a HashMap<usize, ElementBox>,
289    pub font_cache: &'a FontCache,
290    pub app: &'a AppContext,
291}
292
293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
294pub enum Axis {
295    Horizontal,
296    Vertical,
297}
298
299impl Axis {
300    pub fn invert(self) -> Self {
301        match self {
302            Self::Horizontal => Self::Vertical,
303            Self::Vertical => Self::Horizontal,
304        }
305    }
306}
307
308impl ToJson for Axis {
309    fn to_json(&self) -> serde_json::Value {
310        match self {
311            Axis::Horizontal => json!("horizontal"),
312            Axis::Vertical => json!("vertical"),
313        }
314    }
315}
316
317pub trait Vector2FExt {
318    fn along(self, axis: Axis) -> f32;
319}
320
321impl Vector2FExt for Vector2F {
322    fn along(self, axis: Axis) -> f32 {
323        match axis {
324            Axis::Horizontal => self.x(),
325            Axis::Vertical => self.y(),
326        }
327    }
328}
329
330#[derive(Copy, Clone, Debug)]
331pub struct SizeConstraint {
332    pub min: Vector2F,
333    pub max: Vector2F,
334}
335
336impl SizeConstraint {
337    pub fn new(min: Vector2F, max: Vector2F) -> Self {
338        Self { min, max }
339    }
340
341    pub fn strict(size: Vector2F) -> Self {
342        Self {
343            min: size,
344            max: size,
345        }
346    }
347
348    pub fn strict_along(axis: Axis, max: f32) -> Self {
349        match axis {
350            Axis::Horizontal => Self {
351                min: vec2f(max, 0.0),
352                max: vec2f(max, f32::INFINITY),
353            },
354            Axis::Vertical => Self {
355                min: vec2f(0.0, max),
356                max: vec2f(f32::INFINITY, max),
357            },
358        }
359    }
360
361    pub fn max_along(&self, axis: Axis) -> f32 {
362        match axis {
363            Axis::Horizontal => self.max.x(),
364            Axis::Vertical => self.max.y(),
365        }
366    }
367}
368
369impl ToJson for SizeConstraint {
370    fn to_json(&self) -> serde_json::Value {
371        json!({
372            "min": self.min.to_json(),
373            "max": self.max.to_json(),
374        })
375    }
376}
377
378pub struct ChildView {
379    view_id: usize,
380}
381
382impl ChildView {
383    pub fn new(view_id: usize) -> Self {
384        Self { view_id }
385    }
386}
387
388impl Element for ChildView {
389    type LayoutState = ();
390    type PaintState = ();
391
392    fn layout(
393        &mut self,
394        constraint: SizeConstraint,
395        cx: &mut LayoutContext,
396    ) -> (Vector2F, Self::LayoutState) {
397        let size = cx.layout(self.view_id, constraint);
398        (size, ())
399    }
400
401    fn after_layout(
402        &mut self,
403        _: Vector2F,
404        _: &mut Self::LayoutState,
405        cx: &mut AfterLayoutContext,
406    ) {
407        cx.after_layout(self.view_id);
408    }
409
410    fn paint(
411        &mut self,
412        bounds: pathfinder_geometry::rect::RectF,
413        _: &mut Self::LayoutState,
414        cx: &mut PaintContext,
415    ) -> Self::PaintState {
416        cx.paint(self.view_id, bounds.origin());
417    }
418
419    fn dispatch_event(
420        &mut self,
421        event: &Event,
422        _: pathfinder_geometry::rect::RectF,
423        _: &mut Self::LayoutState,
424        _: &mut Self::PaintState,
425        cx: &mut EventContext,
426    ) -> bool {
427        cx.dispatch_event(self.view_id, event)
428    }
429
430    fn debug(
431        &self,
432        bounds: pathfinder_geometry::rect::RectF,
433        _: &Self::LayoutState,
434        _: &Self::PaintState,
435        cx: &DebugContext,
436    ) -> serde_json::Value {
437        json!({
438            "type": "ChildView",
439            "view_id": self.view_id,
440            "bounds": bounds.to_json(),
441            "child": if let Some(view) = cx.rendered_views.get(&self.view_id) {
442                view.debug(cx)
443            } else {
444                json!(null)
445            }
446        })
447    }
448}
449
450#[cfg(test)]
451mod tests {
452    // #[test]
453    // fn test_responder_chain() {
454    //     let settings = settings_rx(None);
455    //     let mut app = App::new().unwrap();
456    //     let workspace = app.add_model(|cx| Workspace::new(Vec::new(), cx));
457    //     let (window_id, workspace_view) =
458    //         app.add_window(|cx| WorkspaceView::new(workspace.clone(), settings, cx));
459
460    //     let invalidations = Rc::new(RefCell::new(Vec::new()));
461    //     let invalidations_ = invalidations.clone();
462    //     app.on_window_invalidated(window_id, move |invalidation, _| {
463    //         invalidations_.borrow_mut().push(invalidation)
464    //     });
465
466    //     let active_pane_id = workspace_view.update(&mut app, |view, cx| {
467    //         cx.focus(view.active_pane());
468    //         view.active_pane().id()
469    //     });
470
471    //     app.update(|app| {
472    //         let mut presenter = Presenter::new(
473    //             window_id,
474    //             Rc::new(FontCache::new()),
475    //             Rc::new(AssetCache::new()),
476    //             app,
477    //         );
478    //         for invalidation in invalidations.borrow().iter().cloned() {
479    //             presenter.update(vec2f(1024.0, 768.0), 2.0, Some(invalidation), app);
480    //         }
481
482    //         assert_eq!(
483    //             presenter.responder_chain(app.cx()).unwrap(),
484    //             vec![workspace_view.id(), active_pane_id]
485    //         );
486    //     });
487    // }
488}