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
| 1 | use 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 | |
| 16 | use oxedyne_fe2o3_core::{ |
| 17 | prelude::*, |
| 18 | ord::ord_string, |
| 19 | }; |
| 20 | use oxedyne_fe2o3_geom::{ |
| 21 | dim::{ |
| 22 | Coord, |
| 23 | Dim, |
| 24 | }, |
| 25 | rect::AbsRect, |
| 26 | }; |
| 27 | use oxedyne_fe2o3_text::string::Stringer; |
| 28 | |
| 29 | use std::collections::HashSet; |
| 30 | |
| 31 | |
| 32 | #[derive(Clone, Debug)] |
| 33 | pub struct TabStripConfig { |
| 34 | pub canvases: Vec<CanvasConfig>, |
| 35 | } |
| 36 | |
| 37 | impl Default for TabStripConfig { |
| 38 | fn default() -> Self { |
| 39 | Self { |
| 40 | canvases: Vec::new(), |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | impl 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)] |
| 66 | pub struct Tab { |
| 67 | pub width: Dim, |
| 68 | pub canvas: CanvasConfig, |
| 69 | pub label: String, |
| 70 | } |
| 71 | |
| 72 | #[derive(Clone, Debug, Default)] |
| 73 | pub struct TabbedTextBox { |
| 74 | pub tab: Tab, |
| 75 | pub tbox: TextBox, |
| 76 | } |
| 77 | |
| 78 | #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] |
| 79 | pub 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)] |
| 88 | pub 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 | |
| 96 | impl 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 | |
| 153 | impl 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 | } |