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}