1use ratatui_core::layout::{Position, Rect, Size};
2use ratatui_widgets::block::Block;
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::hash::Hash;
6
7#[derive(Debug, Clone)]
23pub struct GenericLayout<W>
24where
25 W: Eq + Hash + Clone,
26{
27 area: Rect,
29 page_size: Size,
31 page_count: usize,
33 place: Position,
35
36 widgets: HashMap<W, usize>,
38 rwidgets: HashMap<usize, W>,
39 widget_areas: Vec<Rect>,
41 labels: Vec<Option<Cow<'static, str>>>,
43 label_areas: Vec<Rect>,
45
46 block_areas: Vec<Rect>,
48 blocks: Vec<Option<Block<'static>>>,
50}
51
52impl<W> Default for GenericLayout<W>
53where
54 W: Eq + Hash + Clone,
55{
56 fn default() -> Self {
57 Self {
58 area: Default::default(),
59 page_size: Size::new(u16::MAX, u16::MAX),
60 page_count: 1,
61 place: Default::default(),
62 widgets: Default::default(),
63 rwidgets: Default::default(),
64 widget_areas: Default::default(),
65 labels: Default::default(),
66 label_areas: Default::default(),
67 block_areas: Default::default(),
68 blocks: Default::default(),
69 }
70 }
71}
72
73impl<W> GenericLayout<W>
74where
75 W: Eq + Hash + Clone,
76{
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn with_capacity(num_widgets: usize, num_blocks: usize) -> Self {
83 Self {
84 area: Default::default(),
85 page_size: Size::new(u16::MAX, u16::MAX),
86 page_count: Default::default(),
87 place: Default::default(),
88 widgets: HashMap::with_capacity(num_widgets),
89 rwidgets: HashMap::with_capacity(num_widgets),
90 widget_areas: Vec::with_capacity(num_widgets),
91 labels: Vec::with_capacity(num_widgets),
92 label_areas: Vec::with_capacity(num_widgets),
93 block_areas: Vec::with_capacity(num_blocks),
94 blocks: Vec::with_capacity(num_blocks),
95 }
96 }
97
98 pub fn clear(&mut self) {
100 self.area = Rect::default();
101 self.page_size = Size::default();
102 self.page_count = 0;
103 self.place = Position::default();
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) {
117 self.area = area;
118 }
119
120 pub fn area(&self) -> Rect {
124 self.area
125 }
126
127 pub fn area_changed(&self, area: Rect) -> bool {
129 self.area != area
130 }
131
132 pub fn set_page_size(&mut self, size: Size) {
136 self.page_size = size;
137 }
138
139 pub fn page_size(&self) -> Size {
141 self.page_size
142 }
143
144 pub fn size_changed(&self, size: Size) -> bool {
146 self.page_size != size
147 }
148
149 pub fn set_page_count(&mut self, page_count: usize) {
151 self.page_count = page_count;
152 }
153
154 pub fn page_count(&self) -> usize {
156 self.page_count
157 }
158
159 pub fn add(
161 &mut self, key: W,
163 area: Rect,
164 label: Option<Cow<'static, str>>,
165 label_area: Rect,
166 ) {
167 let idx = self.widget_areas.len();
168 self.widgets.insert(key.clone(), idx);
169 self.rwidgets.insert(idx, key);
170 self.widget_areas.push(area);
171 self.labels.push(label);
172 self.label_areas.push(label_area);
173 }
174
175 pub fn add_block(
177 &mut self, area: Rect,
179 block: Option<Block<'static>>,
180 ) {
181 self.block_areas.push(area);
182 self.blocks.push(block);
183 }
184
185 #[inline]
200 pub fn place(mut self, place: Position) -> Self {
201 self.place = place;
202 self
203 }
204
205 pub fn first(&self, page: usize) -> Option<W> {
207 for (idx, area) in self.widget_areas.iter().enumerate() {
208 let test = (area.y / self.page_size.height) as usize;
209 if page == test {
210 return self.rwidgets.get(&idx).cloned();
211 }
212 }
213 None
214 }
215
216 #[allow(clippy::question_mark)]
218 pub fn page_of(&self, widget: W) -> Option<usize> {
219 let Some(idx) = self.try_index_of(widget) else {
220 return None;
221 };
222
223 Some((self.widget_areas[idx].y / self.page_size.height) as usize)
224 }
225
226 pub fn is_empty(&self) -> bool {
228 self.widget_areas.is_empty() && self.block_areas.is_empty()
229 }
230
231 pub fn is_endless(&self) -> bool {
233 self.area.height == u16::MAX
234 }
235
236 #[inline]
238 pub fn widget_len(&self) -> usize {
239 self.widgets.len()
240 }
241
242 pub fn try_index_of(&self, widget: W) -> Option<usize> {
244 self.widgets.get(&widget).copied()
245 }
246
247 pub fn index_of(&self, widget: W) -> usize {
252 self.widgets.get(&widget).copied().expect("widget")
253 }
254
255 #[inline]
260 pub fn widget_key(&self, idx: usize) -> W {
261 self.rwidgets.get(&idx).cloned().expect("valid_idx")
262 }
263
264 #[inline]
266 pub fn widget_keys(&self) -> impl Iterator<Item = &W> {
267 self.widgets.keys()
268 }
269
270 #[inline]
276 pub fn label_for(&self, widget: W) -> Rect {
277 place(self.label_areas[self.index_of(widget)], self.place)
278 }
279
280 #[inline]
285 pub fn label(&self, idx: usize) -> Rect {
286 place(self.label_areas[idx], self.place)
287 }
288
289 #[inline]
294 pub fn set_label(&mut self, idx: usize, area: Rect) {
295 self.label_areas[idx] = area;
296 }
297
298 #[inline]
304 pub fn widget_for(&self, widget: W) -> Rect {
305 place(self.widget_areas[self.index_of(widget)], self.place)
306 }
307
308 #[inline]
313 pub fn widget(&self, idx: usize) -> Rect {
314 place(self.widget_areas[idx], self.place)
315 }
316
317 #[inline]
322 pub fn set_widget(&mut self, idx: usize, area: Rect) {
323 self.widget_areas[idx] = area;
324 }
325
326 #[inline]
332 pub fn try_label_str_for(&self, widget: W) -> &Option<Cow<'static, str>> {
333 &self.labels[self.index_of(widget)]
334 }
335
336 #[inline]
341 pub fn try_label_str(&self, idx: usize) -> &Option<Cow<'static, str>> {
342 &self.labels[idx]
343 }
344
345 #[inline]
351 pub fn label_str_for(&self, widget: W) -> &str {
352 self.labels[self.index_of(widget)]
353 .as_ref()
354 .map(|v| v.as_ref())
355 .unwrap_or("")
356 }
357
358 #[inline]
363 pub fn label_str(&self, idx: usize) -> &str {
364 self.labels[idx]
365 .as_ref() .map(|v| v.as_ref())
367 .unwrap_or("")
368 }
369
370 #[inline]
375 pub fn set_label_str(&mut self, idx: usize, str: Option<Cow<'static, str>>) {
376 self.labels[idx] = str;
377 }
378
379 #[inline]
381 pub fn block_len(&self) -> usize {
382 self.blocks.len()
383 }
384
385 #[inline]
390 pub fn block_area(&self, idx: usize) -> Rect {
391 self.block_areas[idx]
392 }
393
394 #[inline]
399 pub fn set_block_area(&mut self, idx: usize, area: Rect) {
400 self.block_areas[idx] = area;
401 }
402
403 #[inline]
405 pub fn block_area_iter(&self) -> impl Iterator<Item = &Rect> {
406 self.block_areas.iter()
407 }
408
409 #[inline]
414 pub fn block(&self, idx: usize) -> &Option<Block<'static>> {
415 &self.blocks[idx]
416 }
417
418 #[inline]
423 pub fn set_block(&mut self, idx: usize, block: Option<Block<'static>>) {
424 self.blocks[idx] = block;
425 }
426
427 pub fn append(&mut self, pos: Position, mut layout: GenericLayout<W>) {
435 for (w, idx) in layout.widgets {
436 let new_idx = self.widget_areas.len();
437 self.widget_areas.push(place(layout.widget_areas[idx], pos));
438 self.labels.push(layout.labels[idx].take());
439 self.label_areas.push(place(layout.label_areas[idx], pos));
440 self.widgets.insert(w.clone(), new_idx);
441 self.rwidgets.insert(new_idx, w);
442 }
443 self.block_areas
444 .extend(layout.block_areas.into_iter().map(|v| place(v, pos)));
445 self.blocks.extend(layout.blocks);
446 }
447}
448
449#[inline]
450fn place(mut area: Rect, pos: Position) -> Rect {
451 area.x += pos.x;
452 area.y += pos.y;
453 area
454}