Skip to main content

qcode/ui/
page.rs

1//! The page a screen of a list is drawn on: a column of its own width, in the middle of a wide
2//! terminal.
3//!
4//! QCode's screens are read at a glance and often left open beside a harness. A page that spread
5//! from one edge of a wide terminal to the other would put a row's name and its right-hand detail
6//! as far apart as the terminal happened to be wide; a page of its own width in the middle keeps
7//! them one glance apart and gives the eye one edge to start at and one to finish at. Settings and
8//! the lists of workspaces, profiles and providers stand on such a page, and so does whatever an
9//! open workspace shows in its middle in place of a terminal: a blank tab's choices, a tab waiting
10//! for its container or refused one, a missing image and its build, a desktop harness's window.
11//! A terminal never does: every column of it is a column the program in it can use.
12//!
13//! The page takes the whole width of a terminal narrower than itself, so a narrow window loses
14//! nothing. The dialogs drawn over these pages keep their own widths: a layer is centred on the
15//! screen, and so is the page, so a dialog stands over the page it belongs to.
16
17use qframe::prelude::*;
18
19/// The width a page keeps, in cells. A label, the detail on the other end of its row and the
20/// control under it read as one line at this width; the widest sentence QCode puts on a page of
21/// its own is about as long as this, so nothing wraps.
22pub const WIDTH: u16 = 84;
23
24/// The width of a page that keeps `own` cells, on a screen `screen` cells across: the page's own
25/// width, or every cell of the screen when the screen is narrower than the page.
26#[must_use]
27pub fn width(own: u16, screen: u16) -> u16 {
28    own.min(screen)
29}
30
31/// Draws `body` as a page of `own` cells at the most, in the middle of the screen and from its
32/// top row to its bottom one.
33///
34/// The page keeps the room it is given from top to bottom: only its two sides are bounded.
35pub fn column<Msg: 'static>(ui: &mut View<'_, Msg>, own: u16, body: impl FnOnce(&mut View<'_, Msg>)) {
36    let cells = width(own, ui.size().width);
37    ui.column(|ui| {
38        ui.column(body).width(Length::Cells(cells)).fill_height();
39    })
40    .fill()
41    .align(Align::Center);
42}
43
44#[cfg(test)]
45mod tests {
46    use qframe::prelude::*;
47    use qframe::runtime::Harness;
48
49    use super::{WIDTH, column, width};
50
51    /// A heading over one row with a detail at its other end, which is the shape of the three
52    /// lists the page was made for.
53    struct Page;
54
55    impl App for Page {
56        type Msg = ();
57
58        fn update(&mut self, _: ()) -> Command<()> {
59            Command::none()
60        }
61
62        fn view(&self, ui: &mut View<'_, ()>) {
63            column(ui, WIDTH, |ui| {
64                ui.add(Text::new("head"));
65                ui.add(List::new([ListItem::new("row").detail("end")])).fill_width();
66            });
67        }
68    }
69
70    /// The page in a terminal `wide` cells across: where its heading, its row and the row's
71    /// detail start.
72    fn drawn(wide: u16) -> (i32, i32, i32) {
73        let mut harness = Harness::new(Page, wide, 6);
74        harness.render();
75        let head = harness.find("head").expect("the heading is drawn");
76        let row = harness.find("row").expect("the row is drawn");
77        let end = harness.find("end").expect("the row's detail is drawn");
78        (head.0, row.0, end.0)
79    }
80
81    #[test]
82    fn a_page_stands_in_the_middle_at_its_own_width() {
83        let (head, row, end) = drawn(200);
84        let left = i32::from((200 - WIDTH) / 2);
85        assert_eq!(head, left, "the heading is not at the page's left edge");
86        // A list keeps two cells in front of a row for the mark of the chosen one, and one cell
87        // after its detail, so the last letter stands one in from the page's edge.
88        assert_eq!(row, left + 2, "the row does not begin at the page's left edge");
89        assert_eq!(end + 3, left + i32::from(WIDTH) - 1, "the detail does not end at the page's right edge");
90    }
91
92    #[test]
93    fn a_terminal_narrower_than_the_page_gives_it_every_column() {
94        assert_eq!(width(WIDTH, 70), 70);
95        let (head, _, end) = drawn(70);
96        assert_eq!(head, 0, "the page is not at the screen's edge");
97        assert_eq!(end + 3, 69, "the page does not take every column");
98    }
99}