Skip to main content

qframe/widget/
view.rs

1//! Building the view tree.
2
3use std::any::Any;
4use std::time::Duration;
5
6use super::flex::{Axis, Flex};
7use super::idle::{IdleScope, IdleWatch};
8use super::mapped::Mapped;
9use super::place::Placed;
10use super::{Align, Container, FocusAction, Key, Length, Node, Widget};
11use crate::env::Env;
12use crate::geometry::{Padding, Rect, Size};
13use crate::keymap::Scope;
14
15/// Collects the nodes of one container while an application's `view` runs.
16pub struct View<'a, Msg> {
17    nodes: &'a mut Vec<Node<Msg>>,
18    env: &'a Env,
19    size: Size,
20    idle: &'a IdleScope<Msg>,
21}
22
23impl<'a, Msg: 'static> View<'a, Msg> {
24    pub(crate) fn new(nodes: &'a mut Vec<Node<Msg>>, env: &'a Env, size: Size, idle: &'a IdleScope<Msg>) -> Self {
25        Self { nodes, env, size, idle }
26    }
27
28    /// A builder for the children of a container inside this one: the same environment and size.
29    /// The idleness this view reads and declares watches in, for builders that make views of
30    /// their own.
31    pub(crate) fn idle_scope(&self) -> &'a IdleScope<Msg> {
32        self.idle
33    }
34
35    pub(crate) fn nested<'b>(&self, nodes: &'b mut Vec<Node<Msg>>) -> View<'b, Msg>
36    where
37        'a: 'b,
38    {
39        View::new(nodes, self.env, self.size, self.idle)
40    }
41
42    /// The environment: theme, icons, language and keymap.
43    #[must_use]
44    pub fn env(&self) -> &Env {
45        self.env
46    }
47
48    /// The room the application is drawing into: the whole terminal, in columns and rows.
49    ///
50    /// This is the value for an application's own layout decision, such as "below 48 columns,
51    /// fold the three columns into one": `if ui.size().width < 48 { .. } else { .. }` in `view`.
52    ///
53    /// The application's view fills the screen, so at the top of `view` this is exactly the
54    /// area it lays out. Every nested builder reports the same value: the children of `column`,
55    /// `row`, `stack`, `page` and `add_with`, the parts of an `AppShell`, `SidePanel`,
56    /// `Splitter` or `Popover`, the content of a `Modal` or other layer. The view is built
57    /// before layout divides the screen, so a container's own share is not known yet while its
58    /// children are being built; the number never pretends to be that share. A widget that
59    /// adapts to its own rectangle (a column that shortens its labels) does so in `measure` and
60    /// `paint`, which receive it.
61    ///
62    /// Reading it performs no I/O: it is the size of the frame the framework is about to draw,
63    /// which it already holds. After a terminal resize the next frame reports the new size, and
64    /// [`Harness::resize`](crate::runtime::Harness::resize) does the same in tests.
65    #[must_use]
66    pub fn size(&self) -> Size {
67        self.size
68    }
69
70    /// How long no input has reached this terminal: the time since the last key, mouse event or
71    /// paste the runtime received, or since the application started when none came yet.
72    ///
73    /// Everything the user does in this terminal counts: a key going down, repeating or coming
74    /// up, a mouse button, the wheel, the pointer moving over the window, a paste, and the end of
75    /// a [`Handoff`](crate::runtime::Handoff), because the program that had the terminal was
76    /// being used meanwhile. A terminal resize does not count: a window manager or a monitor
77    /// change resizes a window nobody is sitting at. Messages, background work and timers do not
78    /// count either; they are the application, not the user. Other programs and other terminals
79    /// are out of reach: this is idleness *here*, not idleness of the machine.
80    ///
81    /// Reading the value keeps it current on screen: while `view` reads it, the runtime draws
82    /// again each time it passes a whole second, and stops once `view` no longer reads it. A
83    /// view that shows minutes therefore redraws once a second while it shows them; one that
84    /// only needs to act after a silence uses [`View::on_idle`], which wakes the application
85    /// once, at that moment, without drawing in between.
86    ///
87    /// [`Harness::advance`](crate::runtime::Harness::advance) moves it forward in tests, and
88    /// every simulated input starts it again from zero.
89    #[must_use]
90    pub fn idle_for(&self) -> Duration {
91        self.idle.read.set(true);
92        self.idle.silent
93    }
94
95    /// Tells the application when no input has arrived for `after`, and when input comes back.
96    ///
97    /// `message(true)` is delivered once, at the moment the silence reaches `after`: the runtime
98    /// wakes for it even when nothing else happens, and does not draw in between. The first
99    /// input afterwards delivers `message(false)`, before that input reaches any widget, and
100    /// starts the next wait. What counts as input is listed at [`View::idle_for`].
101    ///
102    /// ```
103    /// use std::time::Duration;
104    ///
105    /// use qframe::prelude::*;
106    ///
107    /// #[derive(Default)]
108    /// struct Focus {
109    ///     away: bool,
110    /// }
111    ///
112    /// impl App for Focus {
113    ///     type Msg = bool;
114    ///     fn update(&mut self, away: bool) -> Command<bool> {
115    ///         self.away = away;
116    ///         Command::none()
117    ///     }
118    ///     fn view(&self, ui: &mut View<'_, bool>) {
119    ///         ui.on_idle(Duration::from_secs(300), |away| away);
120    ///         ui.add(Text::new(if self.away { "away" } else { "working" }));
121    ///     }
122    /// }
123    ///
124    /// let mut app = Harness::new(Focus::default(), 20, 1);
125    /// app.advance(Duration::from_secs(299));
126    /// assert!(app.screen().contains("working"));
127    /// app.advance(Duration::from_secs(1));
128    /// assert!(app.screen().contains("away"));
129    /// app.press("x");
130    /// assert!(app.screen().contains("working"));
131    /// ```
132    ///
133    /// Declare the watch in every frame it should stay active, like a widget: the runtime
134    /// answers the watches of the latest frame. One that is no longer declared is not told the
135    /// silence ended. A watch declared when the silence has already lasted `after` is told at
136    /// once. Watches with different `after` are independent, so an application can dim the
137    /// screen after one minute and pause a timer after five.
138    pub fn on_idle(&mut self, after: Duration, message: impl Fn(bool) -> Msg + 'static) {
139        self.idle.watches.borrow_mut().push(IdleWatch { after, message: Box::new(message) });
140    }
141
142    /// Adds a widget.
143    pub fn add<W: Widget<Msg>>(&mut self, widget: W) -> NodeMut<'_, Msg> {
144        let index = self.nodes.len();
145        self.nodes.push(Node::new(widget, index));
146        NodeMut { node: self.nodes.last_mut().expect("a node was just pushed") }
147    }
148
149    /// Adds a widget that contains other widgets, built by `build`.
150    pub fn add_with<W: Container<Msg>>(
151        &mut self,
152        mut widget: W,
153        build: impl FnOnce(&mut View<'_, Msg>),
154    ) -> NodeMut<'_, Msg> {
155        let mut children = Vec::new();
156        build(&mut self.nested(&mut children));
157        widget.set_children(children);
158        self.add(widget)
159    }
160
161    /// Adds a column whose children are built by `build`.
162    pub fn column(&mut self, build: impl FnOnce(&mut View<'_, Msg>)) -> NodeMut<'_, Msg> {
163        self.container(Axis::Column, build)
164    }
165
166    /// Adds a row whose children are built by `build`.
167    pub fn row(&mut self, build: impl FnOnce(&mut View<'_, Msg>)) -> NodeMut<'_, Msg> {
168        self.container(Axis::Row, build)
169    }
170
171    /// Adds a stack: children are drawn on top of each other in the same area, later ones on top.
172    pub fn stack(&mut self, build: impl FnOnce(&mut View<'_, Msg>)) -> NodeMut<'_, Msg> {
173        self.container(Axis::Stack, build)
174    }
175
176    /// Adds children at `rect`, for a stack whose children sit where the application says, such
177    /// as windows on a desktop.
178    ///
179    /// Inside a [`stack`](Self::stack), `rect` counts from the stack's top left corner, whatever
180    /// the stack's alignment: the children fill it, drawn on top of each other. A rectangle may
181    /// reach past the stack on any side, also to negative coordinates; what lies outside is not
182    /// drawn and takes no pointer. Children added later are drawn on top and get the pointer
183    /// first where they overlap, so the order of the calls is the stacking order. A placed child
184    /// may draw one cell past its right and bottom edges, where a window drops its shadow; that
185    /// cell never takes the pointer.
186    /// Outside a stack only the size of `rect` counts.
187    ///
188    /// Name every placed child whose position in the stack can change, as when a clicked window
189    /// comes to the front: `ui.place(rect, ..).id("htop")`. Its state, and a drag it is in the
190    /// middle of, follow the name.
191    ///
192    /// ```
193    /// use qframe::prelude::*;
194    ///
195    /// struct Desk;
196    ///
197    /// impl App for Desk {
198    ///     type Msg = ();
199    ///     fn update(&mut self, (): ()) -> Command<()> {
200    ///         Command::none()
201    ///     }
202    ///     fn view(&self, ui: &mut View<'_, ()>) {
203    ///         ui.stack(|ui| {
204    ///             ui.place(Rect::new(2, 1, 6, 1), |ui| {
205    ///                 ui.add(Text::new("below"));
206    ///             })
207    ///             .id("first");
208    ///             ui.place(Rect::new(6, 1, 5, 1), |ui| {
209    ///                 ui.add(Text::new("above"));
210    ///             })
211    ///             .id("second");
212    ///         })
213    ///         .fill();
214    ///     }
215    /// }
216    ///
217    /// let app = Harness::new(Desk, 12, 2);
218    /// assert_eq!(app.screen(), "\n  beloabove\n");
219    /// ```
220    pub fn place(&mut self, rect: Rect, build: impl FnOnce(&mut View<'_, Msg>)) -> NodeMut<'_, Msg> {
221        let mut children = Vec::new();
222        build(&mut self.nested(&mut children));
223        self.add(Placed::new(rect, children)).width(Length::Cells(rect.width)).height(Length::Cells(rect.height))
224    }
225
226    /// Adds a column that remembers its widgets' state (focus, scroll, cursors) while it is not
227    /// shown. Give every page of a router its own `key`.
228    pub fn page(&mut self, key: impl Into<String>, build: impl FnOnce(&mut View<'_, Msg>)) -> NodeMut<'_, Msg> {
229        let node = self.container(Axis::Column, build);
230        node.node.persistent = true;
231        node.node.key = Key::Named(key.into());
232        node.fill()
233    }
234
235    /// Adds a column whose children are built by `build` with messages of their own type
236    /// `Inner`, each converted by `map` on its way to the application. A screen with its own
237    /// messages writes its view for them, and the application places it in one line:
238    ///
239    /// ```
240    /// use qframe::prelude::*;
241    ///
242    /// mod search {
243    ///     use qframe::prelude::*;
244    ///
245    ///     #[derive(Clone)]
246    ///     pub enum Msg {
247    ///         Run,
248    ///     }
249    ///
250    ///     pub fn view(ui: &mut View<'_, Msg>) {
251    ///         ui.add(Button::new("Search").on_press(Msg::Run));
252    ///     }
253    /// }
254    ///
255    /// enum Msg {
256    ///     Search(search::Msg),
257    /// }
258    ///
259    /// fn view(ui: &mut View<'_, Msg>) {
260    ///     ui.map(Msg::Search, search::view).fill();
261    /// }
262    /// ```
263    ///
264    /// Everything the screen does inside arrives converted: the messages of its widgets and
265    /// handlers, the children of [`add_with`](Self::add_with) and nested containers, layers such
266    /// as a `Modal` and the widgets in them, overlays such as an open dropdown. Focus, memory and
267    /// ids work as for any column; [`Command::map`](crate::runtime::Command::map) converts the
268    /// commands the screen's `update` returns the same way.
269    pub fn map<Inner: 'static>(
270        &mut self,
271        map: impl Fn(Inner) -> Msg + 'static,
272        build: impl FnOnce(&mut View<'_, Inner>),
273    ) -> NodeMut<'_, Msg> {
274        let map = std::rc::Rc::new(map);
275        let mut children = Vec::new();
276        // The screen reads and watches the same silence as the application; what it read and the
277        // watches it declared are handed up, their messages converted like any other.
278        let idle = IdleScope::new(self.idle.silent);
279        build(&mut View::new(&mut children, self.env, self.size, &idle));
280        if idle.read.get() {
281            self.idle.read.set(true);
282        }
283        for watch in idle.watches.into_inner() {
284            let map = std::rc::Rc::clone(&map);
285            let message = watch.message;
286            self.idle
287                .watches
288                .borrow_mut()
289                .push(IdleWatch { after: watch.after, message: Box::new(move |away| map(message(away))) });
290        }
291        self.add(Mapped::new(children, move |inner| map(inner)))
292    }
293
294    /// Adds empty space that takes the room left in a row or column.
295    pub fn spacer(&mut self) -> NodeMut<'_, Msg> {
296        self.container(Axis::Stack, |_| {}).fill()
297    }
298
299    fn container(&mut self, axis: Axis, build: impl FnOnce(&mut View<'_, Msg>)) -> NodeMut<'_, Msg> {
300        let mut children = Vec::new();
301        build(&mut self.nested(&mut children));
302        self.add(Flex::new(axis, children))
303    }
304}
305
306/// Adjusts the node just added. Every method changes the node in place, so the result can be
307/// ignored or chained.
308pub struct NodeMut<'a, Msg> {
309    node: &'a mut Node<Msg>,
310}
311
312impl<'a, Msg> NodeMut<'a, Msg> {
313    /// Names the node. Name widgets whose position among their siblings can change (list rows,
314    /// optional widgets) so their state and focus follow them.
315    pub fn id(self, name: impl Into<String>) -> Self {
316        self.node.key = Key::Named(name.into());
317        self
318    }
319
320    /// Sets the width.
321    pub fn width(self, width: Length) -> Self {
322        self.node.layout.width = width;
323        self
324    }
325
326    /// Sets the height.
327    pub fn height(self, height: Length) -> Self {
328        self.node.layout.height = height;
329        self
330    }
331
332    /// Takes all space left in both directions.
333    pub fn fill(self) -> Self {
334        self.width(Length::Fill(1)).height(Length::Fill(1))
335    }
336
337    /// Takes all width left.
338    pub fn fill_width(self) -> Self {
339        self.width(Length::Fill(1))
340    }
341
342    /// Takes all height left.
343    pub fn fill_height(self) -> Self {
344        self.height(Length::Fill(1))
345    }
346
347    /// Keeps `padding` free inside the node.
348    pub fn padding(self, padding: Padding) -> Self {
349        self.node.layout.padding = padding;
350        self
351    }
352
353    /// Leaves `cells` between the children of a row or column.
354    pub fn gap(self, cells: u16) -> Self {
355        self.node.layout.gap = cells;
356        self
357    }
358
359    /// Places children along the main axis of a row or column (both axes of a stack).
360    pub fn justify(self, align: Align) -> Self {
361        self.node.layout.justify = align;
362        self
363    }
364
365    /// Whether a mouse drag may select text in this node. Nothing is selectable unless asked:
366    /// `true` makes the node a selection region, so a drag that starts inside it selects text
367    /// within the node only (widgets such as `CodeView` and `Markdown` are regions by
368    /// themselves). `false` keeps selection out of the node and everything inside it, also out
369    /// of regions within it, e.g. for a secret shown inside a selectable log.
370    pub fn selectable(self, selectable: bool) -> Self {
371        self.node.selectable = Some(selectable);
372        self
373    }
374
375    /// Places children across the main axis of a row or column.
376    pub fn align(self, align: Align) -> Self {
377        self.node.layout.align = align;
378        self
379    }
380}
381
382impl<Msg: Clone + 'static> NodeMut<'_, Msg> {
383    /// While keyboard focus is on this node or inside it, a key bound to the keymap action
384    /// `action` of `scope` sends `message` instead of reaching [`App::action`](crate::runtime::App::action).
385    ///
386    /// This is how an application tells where a shortcut was pressed. With focus elsewhere the
387    /// same key reaches `App::action` as usual, so one key can mean two things: leave a
388    /// terminal while inside it, go back into it from outside. The focus in force when the key
389    /// arrives decides, however it got there (`tab`, a click, [`Command::focus`](crate::runtime::Command::focus)),
390    /// so nothing has to be tracked in application state.
391    ///
392    /// The innermost node that answers the action wins. The key must first get past the focused
393    /// widgets: a widget that uses it (a text field typing a character) keeps it, and a
394    /// [`Terminal`](crate::widgets::Terminal) lets it out only for actions named with its
395    /// `pass_through`. The actions the runtime owns (`quit`, `focus-next`, `focus-prev`,
396    /// `debug`, `copy`, `paste`, `toggle-panel`) are not answered here. Call once per action.
397    ///
398    /// ```
399    /// use qframe::env::Env;
400    /// use qframe::prelude::*;
401    /// use qframe::widgets::TextInput;
402    ///
403    /// #[derive(Clone, Debug, PartialEq)]
404    /// enum Msg {
405    ///     Leave,
406    ///     Enter,
407    /// }
408    ///
409    /// struct Editor;
410    ///
411    /// impl App for Editor {
412    ///     type Msg = Msg;
413    ///
414    ///     fn update(&mut self, msg: Msg) -> Command<Msg> {
415    ///         match msg {
416    ///             Msg::Leave => Command::focus("files"),
417    ///             Msg::Enter => Command::focus("note"),
418    ///         }
419    ///     }
420    ///
421    ///     fn view(&self, ui: &mut View<'_, Msg>) {
422    ///         ui.add(List::new(["notes.md", "todo.md"].map(ListItem::new))).id("files");
423    ///         ui.add(TextInput::new("")).id("note").on_action(Scope::App, "switch", Msg::Leave);
424    ///     }
425    ///
426    ///     // Reached only while focus is outside the note.
427    ///     fn action(&self, name: &str) -> Option<Msg> {
428    ///         (name == "switch").then_some(Msg::Enter)
429    ///     }
430    /// }
431    ///
432    /// let mut env = Env::builtin();
433    /// env.keymap_mut().bind(Scope::App, "switch", &["alt+s".parse().unwrap()]);
434    /// let mut app = Harness::with_env(Editor, env, 30, 3);
435    /// app.press("alt+s");
436    /// assert!(app.is_focused("note"));
437    /// app.press("alt+s");
438    /// assert!(app.is_focused("files"));
439    /// ```
440    pub fn on_action(self, scope: Scope, action: impl Into<String>, message: Msg) -> Self {
441        self.node.actions.push(FocusAction {
442            scope,
443            action: action.into(),
444            message: Box::new(move || message.clone()),
445        });
446        self
447    }
448}
449
450impl<Msg: 'static> NodeMut<'_, Msg> {
451    /// Moves the children of a row that do not fit to the next line, instead of letting them
452    /// run past the row's edge. Off by default; a row whose children fit lays out exactly as
453    /// it does without it.
454    ///
455    /// Lines are filled in order: each child takes the width it measures, and a child that
456    /// does not fit after the ones already on the line starts the next line. A child wider
457    /// than the whole row gets a line of its own and the row's width, where it narrows or cuts
458    /// as it does in any row too narrow for it. The row measures as tall as all its lines, so
459    /// the widgets after it move down.
460    ///
461    /// Every line is laid out as a row of its own: [`gap`](Self::gap) falls between the
462    /// children of a line, never at its start or end; [`justify`](Self::justify) places each
463    /// line in the room it leaves; a [`spacer`](View::spacer) or another filling child takes
464    /// what is left on its own line. A spacer stays on the line of the child before it; when
465    /// that line has no room even for the gap before it, the spacer is left out, since at the
466    /// start of the next line it would only push that line away from the edge.
467    ///
468    /// Lines touch; [`line_gap`](Self::line_gap) puts empty rows between them. The option has
469    /// no effect on anything but a row.
470    ///
471    /// ```
472    /// use qframe::prelude::*;
473    ///
474    /// fn actions(ui: &mut View<'_, ()>) {
475    ///     ui.row(|ui| {
476    ///         ui.add(Button::new("Install").on_press(()));
477    ///         ui.add(Button::new("Show the command").on_press(()));
478    ///         ui.add(Button::new("Cancel").on_press(()));
479    ///     })
480    ///     .gap(1)
481    ///     .wrap(true);
482    /// }
483    /// ```
484    pub fn wrap(self, wrap: bool) -> Self {
485        if let Some(flex) = (&mut *self.node.widget as &mut dyn Any).downcast_mut::<Flex<Msg>>() {
486            flex.set_wrap(wrap);
487        }
488        self
489    }
490
491    /// Leaves `rows` empty rows between the lines of a row that [wraps](Self::wrap). A row
492    /// that fits on one line has no gap under it.
493    pub fn line_gap(self, rows: u16) -> Self {
494        if let Some(flex) = (&mut *self.node.widget as &mut dyn Any).downcast_mut::<Flex<Msg>>() {
495            flex.set_line_gap(rows);
496        }
497        self
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use std::cell::RefCell;
504
505    use crate::geometry::Size;
506    use crate::runtime::{App, Command, Harness};
507    use crate::widget::{Length, View};
508    use crate::widgets::{Modal, Text};
509
510    /// Records the size every builder of its view saw: the root, a nested column and a row with
511    /// a fixed width inside it, and the content of a modal layer.
512    #[derive(Default)]
513    struct Probe {
514        seen: RefCell<Vec<Size>>,
515    }
516
517    impl Probe {
518        fn take(&self) -> Vec<Size> {
519            std::mem::take(&mut *self.seen.borrow_mut())
520        }
521    }
522
523    impl App for Probe {
524        type Msg = ();
525
526        fn update(&mut self, (): ()) -> Command<()> {
527            Command::none()
528        }
529
530        fn view(&self, ui: &mut View<'_, ()>) {
531            self.seen.borrow_mut().push(ui.size());
532            ui.column(|ui| {
533                self.seen.borrow_mut().push(ui.size());
534                ui.row(|ui| {
535                    self.seen.borrow_mut().push(ui.size());
536                    ui.add(Text::new("probe"));
537                })
538                .width(Length::Cells(10));
539            });
540            ui.add_with(Modal::new(), |ui| {
541                self.seen.borrow_mut().push(ui.size());
542                ui.add(Text::new("layer"));
543            });
544        }
545    }
546
547    #[test]
548    fn every_builder_of_the_view_sees_the_terminal_size() {
549        let mut harness = Harness::new(Probe::default(), 83, 27);
550        let seen = harness.app().take();
551        assert!(seen.len() >= 4, "{seen:?}");
552        assert!(seen.iter().all(|size| *size == Size::new(83, 27)), "{seen:?}");
553
554        harness.resize(31, 9);
555        let seen = harness.app().take();
556        assert!(seen.len() >= 4, "{seen:?}");
557        assert!(seen.iter().all(|size| *size == Size::new(31, 9)), "{seen:?}");
558
559        harness.resize(0, 0);
560        let seen = harness.app().take();
561        assert!(seen.len() >= 4, "{seen:?}");
562        assert!(seen.iter().all(|size| *size == Size::new(0, 0)), "{seen:?}");
563    }
564
565    /// Three columns side by side from 48 columns up; below that the groups fold into a strip
566    /// above the list and the detail is left out.
567    struct Folding;
568
569    impl App for Folding {
570        type Msg = ();
571
572        fn update(&mut self, (): ()) -> Command<()> {
573            Command::none()
574        }
575
576        fn view(&self, ui: &mut View<'_, ()>) {
577            if ui.size().width < 48 {
578                ui.column(|ui| {
579                    ui.add(Text::new("groups"));
580                    ui.add(Text::new("items"));
581                })
582                .fill();
583            } else {
584                ui.row(|ui| {
585                    ui.add(Text::new("groups"));
586                    ui.add(Text::new("items"));
587                    ui.add(Text::new("detail"));
588                })
589                .gap(2)
590                .fill();
591            }
592        }
593    }
594
595    #[test]
596    fn an_application_folds_its_layout_below_a_width() {
597        let mut harness = Harness::new(Folding, 120, 10);
598        assert_eq!(harness.screen().lines().next(), Some("groups  items  detail"), "{}", harness.screen());
599
600        harness.resize(40, 10);
601        let screen = harness.screen();
602        let lines: Vec<&str> = screen.lines().collect();
603        assert_eq!(lines.get(..2), Some(&["groups", "items"][..]), "{screen}");
604        assert!(!screen.contains("detail"), "{screen}");
605
606        harness.resize(120, 10);
607        assert_eq!(harness.screen().lines().next(), Some("groups  items  detail"), "{}", harness.screen());
608    }
609}