Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_tui/src/lib_tui/draw/tab.rs

9.3 KiB, 20 runs

created by r1870400018:1185, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::lib_tui::{
2 style::Style,
3 draw::{
4 canvas::CanvasConfig,
5 tbox::TextBox,
6 },
7 render::{
8 Drawable,
9 Drawer,
10 Renderer,
11 Sink,
12 When,
13 },
14};
15
16use oxedyne_fe2o3_core::{
17 prelude::*,
18 ord::ord_string,
19};
20use oxedyne_fe2o3_geom::{
21 dim::{
22 Coord,
23 Dim,
24 },
25 rect::AbsRect,
26};
27use oxedyne_fe2o3_text::string::Stringer;
28
29use std::collections::HashSet;
30
31
32#[derive(Clone, Debug)]
33pub struct TabStripConfig {
34 pub canvases: Vec<CanvasConfig>,
35}
36
37impl Default for TabStripConfig {
38 fn default() -> Self {
39 Self {
40 canvases: Vec::new(),
41 }
42 }
43}
44
45impl TabStripConfig {
46
47 pub fn new(styles: Vec<Style>) -> Outcome<Self> {
48 let len = styles.len();
49 if len < 3 {
50 return Err(err!(
51 "Please provide at least three tab styles, starting with the empty tab, \
52 only {} provided.", len;
53 Input, TooSmall, Init));
54 }
55 let mut canvases = Vec::new();
56 for style in styles {
57 canvases.push(CanvasConfig { style, });
58 }
59 Ok(Self {
60 canvases,
61 })
62 }
63}
64
65#[derive(Clone, Debug, Default)]
66pub struct Tab {
67 pub width: Dim,
68 pub canvas: CanvasConfig,
69 pub label: String,
70}
71
72#[derive(Clone, Debug, Default)]
73pub struct TabbedTextBox {
74 pub tab: Tab,
75 pub tbox: TextBox,
76}
77
78#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
79pub struct TabbedTile {
80 pub index: usize,
81 pub width: Dim,
82}
83
84/// A "tab strip" consists of one or more rows of tabs. The first is for window-width text boxes.
85/// The second is optionally for tiled text boxes`n` equal length tabs where the rightmost tab is
86/// empty of content.
87#[derive(Clone, Debug, Default)]
88pub struct TabbedTextManager {
89 pub cfg: TabStripConfig,
90 pub tboxes: Vec<TabbedTextBox>,
91 pub tiled: Option<Vec<TabbedTile>>,
92 pub focus: usize,
93 pub term_view: AbsRect,
94}
95
96impl Drawable for TabbedTextManager {
97 fn render<S: Sink, R: Renderer<S>>(
98 &mut self,
99 drawer: &mut Drawer<S, R>,
100 when: When,
101 )
102 -> Outcome<()>
103 {
104 let (x, y) = self.term_view.top_left.tup();
105
106 // First row.
107 let mut start_x = x;
108 for tabbed_tbox in self.tboxes.iter_mut() {
109 let tab = &mut tabbed_tbox.tab;
110 let width = tab.width;
111 let mut label = Stringer::new(tab.label.clone());
112 let avail_len = width - 2;
113 label.fit_into(*avail_len, "...");
114 let lab_len = label.chars().count();
115 let trailing_padding = " ".repeat(*(width - lab_len - 1));
116
117 res!(tab.canvas.style.render(drawer, When::Later));
118 res!(drawer.rend.set_cursor(Coord::new((start_x, y)), When::Later));
119 res!(drawer.rend.print(&fmt!(" {}{}", label, trailing_padding), When::Later));
120 start_x += width;
121 }
122 // Possible second row.
123 if let Some(tabbed_tiles) = &self.tiled {
124 if self.focus_is_on_tiled() {
125 let mut start_x = x;
126 for tabbed_tile in tabbed_tiles {
127 let tabbed_tbox = &mut self.tboxes[tabbed_tile.index];
128 let tab = &mut tabbed_tbox.tab;
129 let width = tabbed_tile.width;
130 let mut label = Stringer::new(tab.label.clone());
131 let avail_len = width - 2;
132 label.fit_into(*avail_len, "...");
133 let lab_len = label.chars().count();
134 let trailing_padding = " ".repeat(*(width - lab_len - 1));
135
136 res!(tab.canvas.style.render(drawer, When::Later));
137 res!(drawer.rend.set_cursor(Coord::new((start_x, y + 1)), When::Later));
138 res!(drawer.rend.print(&fmt!(" {}{}", label, trailing_padding), When::Later));
139 start_x += width;
140 }
141 }
142 }
143
144 res!(drawer.rend.reset_style(When::Later));
145
146 if when == When::Now {
147 res!(drawer.rend.flush());
148 }
149 Ok(())
150 }
151}
152
153impl TabbedTextManager {
154
155 pub fn new(
156 cfg: TabStripConfig,
157 mut tboxes: Vec<TabbedTextBox>,
158 tiled: Option<Vec<TabbedTile>>,
159 focus: Option<usize>,
160 )
161 -> Outcome<Self>
162 {
163 for ttbox in tboxes.iter_mut() {
164 ttbox.tbox.state.canvas = ttbox.tab.canvas.clone();
165 }
166 if let Some(tiled) = &tiled {
167 res!(Self::check_tiles(&tiled, tboxes.len()));
168 }
169
170 Ok(Self {
171 cfg,
172 tboxes,
173 tiled,
174 focus: if let Some(f) = focus { f } else { 0 },
175 term_view: AbsRect::default(),
176 })
177 }
178
179 pub fn is_empty(&self) -> bool {
180 if self.tboxes.len() == 0 {
181 true
182 } else {
183 false
184 }
185 }
186
187 fn check_tiles(tiled: &Vec<TabbedTile>, num_tabs: usize) -> Outcome<()> {
188 let mut set = HashSet::new();
189 for (i, tabbed_tile) in tiled.iter().enumerate() {
190 if !set.insert(tabbed_tile) {
191 return Err(err!(
192 "The {} tabbed tile {:?} duplicates an existing tabbed tile.",
193 ord_string(i), tabbed_tile;
194 Input, Duplicate, Invalid));
195 }
196 if tabbed_tile.index >= num_tabs {
197 return Err(err!(
198 "The {} tabbed tile {:?} has an index exceeding the number \
199 of tabs, {}.", ord_string(i), tabbed_tile, num_tabs;
200 Input, TooBig, Invalid));
201 }
202 }
203 Ok(())
204 }
205
206 pub fn strip_height(&self) -> Dim {
207 let h1 = if self.tboxes.len() > 0 {
208 Dim(1)
209 } else {
210 return Dim(0);
211 };
212 let h2 = if self.focus_is_on_tiled() {
213 Dim(1)
214 } else {
215 Dim(0)
216 };
217 h1 + h2
218 }
219
220 pub fn get_number_of_tabs(&self) -> (usize, usize) {
221 (
222 self.tboxes.len(),
223 if let Some(tiled) = &self.tiled { tiled.len() } else { 0 },
224 )
225 }
226
227 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut TextBox> {
228 self.tboxes.iter_mut().map(|ttbox| &mut ttbox.tbox)
229 }
230
231 pub fn next_focus(&mut self) -> Outcome<()> {
232 let (num_non_tiled, num_tiled) = self.get_number_of_tabs();
233 self.focus = try_rem!(self.focus + 1, num_non_tiled + num_tiled);
234 Ok(())
235 }
236
237 pub fn focus_is_on_tiled(&self) -> bool {
238 self.focus >= self.tboxes.len()
239 }
240
241 pub fn get_focal_tabbed_text_box(&self) -> Option<&TabbedTextBox> {
242 if self.tboxes.len() > 0 {
243 let (num_non_tiled, _num_tiled) = self.get_number_of_tabs();
244 if self.focus < num_non_tiled {
245 Some(&self.tboxes[self.focus])
246 } else {
247 if let Some(tiled) = &self.tiled {
248 let tabbed_tile = &tiled[self.focus - num_non_tiled];
249 Some(&self.tboxes[tabbed_tile.index])
250 } else {
251 None
252 }
253 }
254 } else {
255 None
256 }
257 }
258
259 pub fn get_focal_tabbed_text_box_mut(&mut self) -> Option<&mut TabbedTextBox> {
260 if self.tboxes.len() > 0 {
261 let (num_non_tiled, _num_tiled) = self.get_number_of_tabs();
262 if self.focus < num_non_tiled {
263 Some(&mut self.tboxes[self.focus])
264 } else {
265 if let Some(tiled) = &self.tiled {
266 let tabbed_tile = &tiled[self.focus - num_non_tiled];
267 Some(&mut self.tboxes[tabbed_tile.index])
268 } else {
269 None
270 }
271 }
272 } else {
273 None
274 }
275 }
276
277 pub fn update_tabs(&mut self, term_view: AbsRect) -> Outcome<()> {
278 self.term_view = term_view;
279 let w = term_view.size.x;
280 // First row.
281 let n = self.tboxes.len();
282 let canvas_len = self.cfg.canvases.len();
283 let widths = res!(Self::divide_evenly(w, n));
284 for t in 0..n {
285 // When the number of tabs exceeds the number of canvas configurations, cycle back
286 // through the configurations.
287 let canvas_ind = if t < canvas_len { t } else { try_rem!(t, canvas_len) + 1 };
288 self.tboxes[t].tab.width = widths[t];
289 self.tboxes[t].tab.canvas = self.cfg.canvases[canvas_ind].clone();
290 self.tboxes[t].tbox.state.canvas = self.tboxes[t].tab.canvas.clone();
291 }
292 // Possible second row.
293 if let Some(tabbed_tiles) = &mut self.tiled {
294 let n = tabbed_tiles.len();
295 if n > 0 {
296 let widths = res!(Self::divide_evenly(w, n));
297 for t in 0..tabbed_tiles.len() {
298 tabbed_tiles[t].width = widths[t];
299 }
300 }
301 }
302 Ok(())
303 }
304
305 /// Divide the interval `w` as equally as possibly `n` ways, using integer arithmetic.
306 /// Distribute any remainder amongst the leading parts.
307 pub fn divide_evenly(w: Dim, n: usize) -> Outcome<Vec<Dim>> {
308 let n0 = n;
309 let n = Dim::from(n);
310 let mut parts = vec![try_div!(w, n); n0];
311 let rem = Dim::from(try_rem!(w, *n));
312 for i in 0..rem.as_index() {
313 parts[i] += 1;
314 }
315 Ok(parts)
316 }
317}