Skip to main content

max / quasi

5.0 KB · 129 lines History Blame Raw
1 //! Regions into panels.
2 //!
3 //! The half of the drawing a webview never has to do. A stylesheet turns
4 //! `list-detail` into two columns and the browser does the arithmetic; here it is
5 //! the renderer's, and every place the description does not say enough to do it
6 //! is a finding.
7 //!
8 //! **The same three findings `quasi-tui`'s `region` module records apply here,
9 //! unchanged**, because they are about the description rather than about
10 //! terminals. Repeated rather than cross-referenced only, since a reader here
11 //! needs to know the guesses are guesses:
12 //!
13 //! - **A tabbed arrangement does not say which tab is showing.** This draws the
14 //! first, which is a guess.
15 //! - **A tab has no label.** [`Slot::id`] is an address chosen to be stable for
16 //! fragment targeting, so using it as a heading puts `contacts-detail` on
17 //! screen.
18 //! - **A region's share is [`Arrangement::share`] and nothing finer.**
19 //!
20 //! What differs from the terminal is only the mechanism: `egui::SidePanel` and
21 //! `Ui::columns` rather than rect arithmetic, and a `ScrollArea` per pane
22 //! because scroll is egui's here and the terminal's there.
23
24 use egui::Ui;
25 use quasi_router::layout::Arrangement;
26 use quasi_router::{RegionKind, Screen, Slot};
27
28 use crate::{Pass, node};
29
30 /// Lay a screen's regions out and draw them.
31 pub(crate) fn screen_regions(pass: &mut Pass<'_>, ui: &mut Ui, screen: &Screen) {
32 // Bands stack at the top, full width, in the order they were said. A band is
33 // an arrangement rather than a type -- a page header, a toolbar -- so it
34 // takes the height it needs and gets out of the way.
35 for slot in screen
36 .slots
37 .iter()
38 .filter(|slot| matches!(slot.kind, RegionKind::Band))
39 {
40 node::region(pass, ui, slot);
41 }
42
43 let body: Vec<&Slot> = screen
44 .slots
45 .iter()
46 .filter(|slot| !matches!(slot.kind, RegionKind::Band))
47 .collect();
48
49 match screen.arrangement {
50 Arrangement::SidebarContent { share } => {
51 let sidebar = body
52 .iter()
53 .position(|slot| matches!(slot.kind, RegionKind::Sidebar));
54 match sidebar {
55 Some(at) => {
56 let width = ui.available_width() * share_of(share);
57 ui.horizontal_top(|ui| {
58 ui.allocate_ui(egui::vec2(width, ui.available_height()), |ui| {
59 scrolled(ui, body[at], pass);
60 });
61 ui.vertical(|ui| {
62 for (i, slot) in body.iter().enumerate() {
63 if i != at {
64 scrolled(ui, slot, pass);
65 }
66 }
67 });
68 });
69 }
70 // A sidebar arrangement with no sidebar region is a description
71 // that named a shape it did not fill. Drawn down the page
72 // rather than refused: the regions are real and the reader can
73 // still use them.
74 None => {
75 for slot in &body {
76 scrolled(ui, slot, pass);
77 }
78 }
79 }
80 }
81
82 Arrangement::ListDetail { share, tabbed } => {
83 if tabbed {
84 // The guess named in the header: nothing says which of the two
85 // is showing, so the first is.
86 if let Some(slot) = body.first() {
87 scrolled(ui, slot, pass);
88 }
89 } else {
90 let left = ui.available_width() * share_of(share);
91 ui.horizontal_top(|ui| {
92 if let Some(slot) = body.first() {
93 ui.allocate_ui(egui::vec2(left, ui.available_height()), |ui| {
94 scrolled(ui, slot, pass);
95 });
96 }
97 ui.vertical(|ui| {
98 for slot in body.iter().skip(1) {
99 scrolled(ui, slot, pass);
100 }
101 });
102 });
103 }
104 }
105 }
106 }
107
108 /// A pane with its own scroll.
109 ///
110 /// Scroll is egui's here, which is one of the three things `quasi-tui`'s `View`
111 /// holds and this crate's does not. The id is the slot's, so a pane keeps its
112 /// position across frames the way the description's stable ids intend.
113 fn scrolled(ui: &mut Ui, slot: &Slot, pass: &mut Pass<'_>) {
114 egui::ScrollArea::vertical()
115 .id_salt(&slot.id)
116 .show(ui, |ui| {
117 node::region(pass, ui, slot);
118 });
119 }
120
121 /// A share as a fraction of the width on offer.
122 fn share_of(share: quasi_router::layout::Share) -> f32 {
123 // `Share::of` answers in whole columns for a terminal. A window has
124 // fractional width, so the ratio is taken against a large denominator and
125 // divided back, which keeps one fact behind both renderers' proportions.
126 const SCALE: u16 = 1000;
127 f32::from(share.of(SCALE)) / f32::from(SCALE)
128 }
129