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