presenter.rs

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