1use ratatui::layout::{Position, Rect, Size};
2use ratatui::widgets::Block;
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::hash::Hash;
6
7#[derive(Debug, Clone)]
28pub struct GenericLayout<W>
29where
30 W: Eq + Hash + Clone,
31{
32 area: Rect,
34 page_size: Size,
36 page_count: usize,
38
39 widgets: HashMap<W, usize>,
41 rwidgets: HashMap<usize, W>,
42 widget_areas: Vec<Rect>,
44 labels: Vec<Option<Cow<'static, str>>>,
46 label_areas: Vec<Rect>,
48
49 block_areas: Vec<Rect>,
51 blocks: Vec<Option<Block<'static>>>,
53}
54
55impl<W> Default for GenericLayout<W>
56where
57 W: Eq + Hash + Clone,
58{
59 fn default() -> Self {
60 Self {
61 area: Default::default(),
62 page_size: Size::new(u16::MAX, u16::MAX),
63 page_count: 1,
64 widgets: Default::default(),
65 rwidgets: Default::default(),
66 widget_areas: Default::default(),
67 labels: Default::default(),
68 label_areas: Default::default(),
69 block_areas: Default::default(),
70 blocks: Default::default(),
71 }
72 }
73}
74
75impl<W> GenericLayout<W>
76where
77 W: Eq + Hash + Clone,
78{
79 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn with_capacity(num_widgets: usize, num_blocks: usize) -> Self {
85 Self {
86 area: Default::default(),
87 page_size: Size::new(u16::MAX, u16::MAX),
88 page_count: Default::default(),
89 widgets: HashMap::with_capacity(num_widgets),
90 rwidgets: HashMap::with_capacity(num_widgets),
91 widget_areas: Vec::with_capacity(num_widgets),
92 labels: Vec::with_capacity(num_widgets),
93 label_areas: Vec::with_capacity(num_widgets),
94 block_areas: Vec::with_capacity(num_blocks),
95 blocks: Vec::with_capacity(num_blocks),
96 }
97 }
98
99 pub fn clear(&mut self) {
101 self.area = Rect::default();
102 self.page_size = Size::default();
103 self.page_count = 0;
104 self.widgets.clear();
105 self.rwidgets.clear();
106 self.widget_areas.clear();
107 self.labels.clear();
108 self.label_areas.clear();
109 self.block_areas.clear();
110 self.blocks.clear();
111 }
112
113 pub fn set_area(&mut self, area: Rect) {
116 self.area = area;
117 }
118
119 pub fn area(&self) -> Rect {
122 self.area
123 }
124
125 pub fn area_changed(&self, area: Rect) -> bool {
127 self.area != area
128 }
129
130 pub fn set_page_size(&mut self, size: Size) {
134 self.page_size = size;
135 }
136
137 pub fn page_size(&self) -> Size {
139 self.page_size
140 }
141
142 pub fn size_changed(&self, size: Size) -> bool {
144 self.page_size != size
145 }
146
147 pub fn set_page_count(&mut self, page_count: usize) {
149 self.page_count = page_count;
150 }
151
152 pub fn page_count(&self) -> usize {
154 self.page_count
155 }
156
157 pub fn add(
159 &mut self, key: W,
161 area: Rect,
162 label: Option<Cow<'static, str>>,
163 label_area: Rect,
164 ) {
165 let idx = self.widget_areas.len();
166 self.widgets.insert(key.clone(), idx);
167 self.rwidgets.insert(idx, key);
168 self.widget_areas.push(area);
169 self.labels.push(label);
170 self.label_areas.push(label_area);
171 }
172
173 pub fn add_block(
175 &mut self, area: Rect,
177 block: Option<Block<'static>>,
178 ) {
179 self.block_areas.push(area);
180 self.blocks.push(block);
181 }
182
183 #[inline]
194 pub fn place(mut self, pos: Position) -> Self {
195 for v in self.widget_areas.iter_mut() {
196 *v = place(*v, pos);
197 }
198 for v in self.label_areas.iter_mut() {
199 *v = place(*v, pos);
200 }
201 for v in self.block_areas.iter_mut() {
202 *v = place(*v, pos);
203 }
204 self
205 }
206
207 pub fn first(&self, page: usize) -> Option<W> {
209 for (idx, area) in self.widget_areas.iter().enumerate() {
210 let test = (area.y / self.page_size.height) as usize;
211 if page == test {
212 return self.rwidgets.get(&idx).cloned();
213 }
214 }
215 None
216 }
217
218 #[allow(clippy::question_mark)]
220 pub fn page_of(&self, widget: W) -> Option<usize> {
221 let Some(idx) = self.try_index_of(widget) else {
222 return None;
223 };
224
225 Some((self.widget_areas[idx].y / self.page_size.height) as usize)
226 }
227
228 pub fn is_empty(&self) -> bool {
230 self.widget_areas.is_empty() && self.block_areas.is_empty()
231 }
232
233 pub fn is_endless(&self) -> bool {
235 self.area.height == u16::MAX
236 }
237
238 #[inline]
240 pub fn widget_len(&self) -> usize {
241 self.widgets.len()
242 }
243
244 pub fn try_index_of(&self, widget: W) -> Option<usize> {
246 self.widgets.get(&widget).copied()
247 }
248
249 pub fn index_of(&self, widget: W) -> usize {
254 self.widgets.get(&widget).copied().expect("widget")
255 }
256
257 #[inline]
262 pub fn widget_key(&self, idx: usize) -> W {
263 self.rwidgets.get(&idx).cloned().expect("valid_idx")
264 }
265
266 #[inline]
268 pub fn widget_keys(&self) -> impl Iterator<Item = &W> {
269 self.widgets.keys()
270 }
271
272 #[inline]
278 pub fn label_for(&self, widget: W) -> Rect {
279 self.label_areas[self.index_of(widget)]
280 }
281
282 #[inline]
287 pub fn label(&self, idx: usize) -> Rect {
288 self.label_areas[idx]
289 }
290
291 #[inline]
296 pub fn set_label(&mut self, idx: usize, area: Rect) {
297 self.label_areas[idx] = area;
298 }
299
300 #[inline]
306 pub fn widget_for(&self, widget: W) -> Rect {
307 self.widget_areas[self.index_of(widget)]
308 }
309
310 #[inline]
315 pub fn widget(&self, idx: usize) -> Rect {
316 self.widget_areas[idx]
317 }
318
319 #[inline]
324 pub fn set_widget(&mut self, idx: usize, area: Rect) {
325 self.widget_areas[idx] = area;
326 }
327
328 #[inline]
334 pub fn try_label_str_for(&self, widget: W) -> &Option<Cow<'static, str>> {
335 &self.labels[self.index_of(widget)]
336 }
337
338 #[inline]
343 pub fn try_label_str(&self, idx: usize) -> &Option<Cow<'static, str>> {
344 &self.labels[idx]
345 }
346
347 #[inline]
353 pub fn label_str_for(&self, widget: W) -> &str {
354 self.labels[self.index_of(widget)]
355 .as_ref()
356 .map(|v| v.as_ref())
357 .unwrap_or("")
358 }
359
360 #[inline]
365 pub fn label_str(&self, idx: usize) -> &str {
366 self.labels[idx]
367 .as_ref() .map(|v| v.as_ref())
369 .unwrap_or("")
370 }
371
372 #[inline]
377 pub fn set_label_str(&mut self, idx: usize, str: Option<Cow<'static, str>>) {
378 self.labels[idx] = str;
379 }
380
381 #[inline]
383 pub fn block_len(&self) -> usize {
384 self.blocks.len()
385 }
386
387 #[inline]
392 pub fn block_area(&self, idx: usize) -> Rect {
393 self.block_areas[idx]
394 }
395
396 #[inline]
401 pub fn set_block_area(&mut self, idx: usize, area: Rect) {
402 self.block_areas[idx] = area;
403 }
404
405 #[inline]
407 pub fn block_area_iter(&self) -> impl Iterator<Item = &Rect> {
408 self.block_areas.iter()
409 }
410
411 #[inline]
416 pub fn block(&self, idx: usize) -> &Option<Block<'static>> {
417 &self.blocks[idx]
418 }
419
420 #[inline]
425 pub fn set_block(&mut self, idx: usize, block: Option<Block<'static>>) {
426 self.blocks[idx] = block;
427 }
428}
429
430#[inline]
431fn place(mut area: Rect, pos: Position) -> Rect {
432 area.x += pos.x;
433 area.y += pos.y;
434 area
435}