Skip to main content

tilo_app/framework/
page_part.rs

1//! A part of a painted page that is ordinary retained widgets (a row of
2//! buttons, a grid of cards, a row of filter chips), embedded where the
3//! page lays it out: how an app that paints its own surface shows the
4//! framework's controls in it.
5//!
6//! The widgets own their hover, press, focus and accessibility; what they
7//! ask for comes back as the app's own action. A part is rebuilt when its
8//! key (everything it shows and does) changes.
9
10use std::cell::RefCell;
11use std::rc::Rc;
12
13use ui_core::paint::PaintCtx;
14use ui_core::presence::PresenceCtx;
15use ui_core::vello::kurbo::Rect;
16use ui_core::widget::{Column, Element, Embedded, Widget};
17use ui_core::winit::event::WindowEvent;
18
19/// Where a part's widgets leave the actions they ask for.
20pub type Asked<A> = Rc<RefCell<Vec<A>>>;
21
22/// A widget callback that asks for `action`.
23pub fn asking<A: Clone + 'static>(asked: &Asked<A>, action: A) -> impl FnMut() + 'static {
24    let asked = Rc::clone(asked);
25    move || asked.borrow_mut().push(action.clone())
26}
27
28/// A retained widget laid out and painted where the page puts it. Only a
29/// widget placed in view this frame takes input or answers for what is
30/// under the pointer.
31pub struct Placed<W: Widget> {
32    host: Embedded<W>,
33    placed: bool,
34}
35
36impl<W: Widget + 'static> Placed<W> {
37    pub fn new(widget: W) -> Self {
38        Self {
39            host: Embedded::new(widget),
40            placed: false,
41        }
42    }
43
44    /// Swap in a rebuilt widget.
45    pub fn replace(&mut self, widget: W) {
46        self.host = Embedded::new(widget);
47    }
48
49    /// Forget this frame's placement; the next [`place`](Self::place)
50    /// decides it again.
51    pub fn unplace(&mut self) {
52        self.placed = false;
53    }
54
55    pub fn widget(&self) -> &W {
56        self.host.widget()
57    }
58
59    pub fn widget_mut(&mut self) -> &mut W {
60        self.host.widget_mut()
61    }
62
63    /// The widget, while it is placed in view.
64    pub fn visible(&self) -> Option<&W> {
65        self.placed.then(|| self.host.widget())
66    }
67
68    /// The widget, while it is placed in view.
69    pub fn visible_mut(&mut self) -> Option<&mut W> {
70        self.placed.then(|| self.host.widget_mut())
71    }
72
73    pub fn layout(&mut self, rect: Rect, ctx: &mut PaintCtx<'_>) -> f64 {
74        self.host.layout(rect, ctx.theme, ctx.text)
75    }
76
77    /// Paint the laid-out widget inside `clip` (the page's viewport) and
78    /// count it as placed.
79    pub fn draw(&mut self, ctx: &mut PaintCtx<'_>, clip: Rect) {
80        let presence = PresenceCtx::default();
81        let outer = presence.push_viewport(clip);
82        self.host
83            .draw(ctx.scene, ctx.transform, ctx.text, ctx.theme, &presence);
84        presence.pop_viewport(outer);
85        self.placed = true;
86    }
87
88    /// Lay the widget out from `top` across `[left, right]` and paint it
89    /// where it meets `clip`. Returns the y below it.
90    pub fn place(
91        &mut self,
92        ctx: &mut PaintCtx<'_>,
93        (left, right): (f64, f64),
94        top: f64,
95        clip: Rect,
96    ) -> f64 {
97        let bottom = top + self.layout(Rect::new(left, top, right, top), ctx);
98        if bottom > clip.y0 && top < clip.y1 {
99            self.draw(ctx, clip);
100        }
101        bottom
102    }
103
104    /// Route one window event to the widget; one out of view takes none.
105    pub fn handle_event(&mut self, event: &WindowEvent, ctx: &mut ui_core::widget::EventCtx<'_>) {
106        if !self.placed {
107            return;
108        }
109        self.host.handle_event(
110            event,
111            ctx.scale_factor,
112            ctx.modifiers,
113            &mut ctx.state,
114            ctx.path,
115            ctx.handled,
116        );
117    }
118
119    pub fn tooltip_at(&self, point: ui_core::vello::kurbo::Point) -> Option<String> {
120        fn find(widget: &dyn Widget, point: ui_core::vello::kurbo::Point) -> Option<String> {
121            for child in widget.children() {
122                if let Some(found) = find(child, point) {
123                    return Some(found);
124                }
125            }
126            (widget.get_rect().contains(point))
127                .then(|| widget.tooltip().map(str::to_owned))
128                .flatten()
129        }
130        find(self.visible()?, point)
131    }
132}
133
134/// A part of the painted page: retained widgets the page lays out and
135/// paints where it puts them. The painting app lists the part among its
136/// children ([`Self::child`], [`Self::child_mut`]), so the window reaches its
137/// controls as any retained child's: the actions they are bound to, what
138/// they publish, and their accessibility, which the window's walk exports
139/// where they are drawn and routes through the painting app's node.
140pub struct PagePart<A> {
141    pub host: Placed<Column>,
142    key: String,
143    asked: Asked<A>,
144}
145
146impl<A> Default for PagePart<A> {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152impl<A> PagePart<A> {
153    #[must_use]
154    pub fn new() -> Self {
155        Self {
156            host: Placed::new(Column::new().with_no_padding()),
157            key: String::new(),
158            asked: Rc::default(),
159        }
160    }
161
162    /// The part's widgets while they are placed in view: one of the
163    /// painting app's children.
164    pub fn child(&self) -> Option<&dyn Widget> {
165        self.host.visible().map(|column| column as &dyn Widget)
166    }
167
168    /// [`Self::child`], to route to.
169    pub fn child_mut(&mut self) -> Option<&mut dyn Widget> {
170        self.host
171            .visible_mut()
172            .map(|column| column as &mut dyn Widget)
173    }
174
175    /// Bring the part up to date: rebuild it with `build` when `key` says
176    /// what it shows changed.
177    pub fn update(&mut self, key: String, build: impl FnOnce(&Asked<A>) -> Element) {
178        self.host.unplace();
179        if key == self.key {
180            return;
181        }
182        self.key = key;
183        let widget = build(&self.asked);
184        self.host.replace(
185            Column::new()
186                .with_no_padding()
187                .with_intrinsic_height()
188                .push(widget),
189        );
190    }
191
192    /// What the part's widgets asked for since the last call.
193    pub fn take_asked(&self) -> Vec<A> {
194        std::mem::take(&mut *self.asked.borrow_mut())
195    }
196
197    /// Where the widget whose test id is `id` stands.
198    pub fn find(&self, id: &str) -> Option<Rect> {
199        ui_core::widget::find_test_id(self.host.widget(), id).map(ui_core::widget::Widget::get_rect)
200    }
201}