Skip to main content

telar_ui_core/
scroll_page.rs

1//! A full-window scrolling page.
2
3use layout_core::{AvailableSpace, LayoutError, LayoutStyle, NodeId, SizeDimension};
4use platform_core::Event;
5use ui_tree::{Component, EventResult, RenderNode};
6
7use crate::context::{compute_layout, mark_dirty, new_container};
8use crate::layout_item::LayoutItem;
9use crate::scroll_area::LayoutScrollArea;
10
11/// A window-sized root holding a [`LayoutScrollArea`] whose viewport is recomputed on resize, so content
12/// scrolls against the current window dimensions.
13///
14/// The shape any app whose whole window is one scrolling column needs, and which two of them — the preview
15/// runner and the landing page — had written out identically, down to the field names.
16pub struct ScrollPage {
17    root: NodeId,
18    content_node: NodeId,
19    scroll_area: LayoutScrollArea,
20}
21
22impl ScrollPage {
23    pub fn new(content: Box<dyn LayoutItem>) -> Result<Self, LayoutError> {
24        let content_node = content.layout_node();
25        let scroll_area = LayoutScrollArea::new(
26            LayoutStyle::new().flex_grow(1.0).align_self_stretch(),
27            content,
28        )?;
29        // Percent sizing so a `compute_layout` against `Definite(w, h)` yields a full-window viewport for the
30        // scroll-area leaf.
31        let root = new_container(
32            LayoutStyle::new()
33                .flex_column()
34                .width(SizeDimension::Percent(1.0))
35                .height(SizeDimension::Percent(1.0)),
36            &[scroll_area.layout_node()],
37        )?;
38        Ok(Self {
39            root,
40            content_node,
41            scroll_area,
42        })
43    }
44
45    /// Lays the page out against a window of `width`×`height`, then the content against that width at its own
46    /// natural height — which is what gives the scroll area something taller than itself to scroll.
47    pub fn relayout(&mut self, width: f32, height: f32) {
48        mark_dirty(self.root).ok();
49        compute_layout(
50            self.root,
51            AvailableSpace::Definite(width),
52            AvailableSpace::Definite(height),
53        )
54        .ok();
55        compute_layout(
56            self.content_node,
57            AvailableSpace::Definite(width),
58            AvailableSpace::MaxContent,
59        )
60        .ok();
61        self.scroll_area.clamp_scroll();
62    }
63}
64
65impl Component for ScrollPage {
66    fn view(&self) -> RenderNode {
67        self.scroll_area.view()
68    }
69
70    /// A resize relays the page out; everything else goes to the scroll area.
71    fn on_event(&mut self, event: &Event) -> EventResult {
72        if let Event::WindowResized { width, height } = event {
73            self.relayout(*width as f32, *height as f32);
74            return EventResult::Handled;
75        }
76        self.scroll_area.on_event(event)
77    }
78}