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 #[inline]
235 pub fn widget_len(&self) -> usize {
236 self.widgets.len()
237 }
238
239 pub fn try_index_of(&self, widget: W) -> Option<usize> {
241 self.widgets.get(&widget).copied()
242 }
243
244 pub fn index_of(&self, widget: W) -> usize {
249 self.widgets.get(&widget).copied().expect("widget")
250 }
251
252 #[inline]
257 pub fn widget_key(&self, idx: usize) -> W {
258 self.rwidgets.get(&idx).cloned().expect("valid_idx")
259 }
260
261 #[inline]
263 pub fn widget_keys(&self) -> impl Iterator<Item = &W> {
264 self.widgets.keys()
265 }
266
267 #[inline]
273 pub fn label_for(&self, widget: W) -> Rect {
274 self.label_areas[self.index_of(widget)]
275 }
276
277 #[inline]
282 pub fn label(&self, idx: usize) -> Rect {
283 self.label_areas[idx]
284 }
285
286 #[inline]
291 pub fn set_label(&mut self, idx: usize, area: Rect) {
292 self.label_areas[idx] = area;
293 }
294
295 #[inline]
301 pub fn widget_for(&self, widget: W) -> Rect {
302 self.widget_areas[self.index_of(widget)]
303 }
304
305 #[inline]
310 pub fn widget(&self, idx: usize) -> Rect {
311 self.widget_areas[idx]
312 }
313
314 #[inline]
319 pub fn set_widget(&mut self, idx: usize, area: Rect) {
320 self.widget_areas[idx] = area;
321 }
322
323 #[inline]
329 pub fn try_label_str_for(&self, widget: W) -> &Option<Cow<'static, str>> {
330 &self.labels[self.index_of(widget)]
331 }
332
333 #[inline]
338 pub fn try_label_str(&self, idx: usize) -> &Option<Cow<'static, str>> {
339 &self.labels[idx]
340 }
341
342 #[inline]
348 pub fn label_str_for<'a>(&'a self, widget: W) -> &'a str {
349 self.labels[self.index_of(widget)]
350 .as_ref()
351 .map(|v| v.as_ref())
352 .unwrap_or("")
353 }
354
355 #[inline]
360 pub fn label_str<'a>(&'a self, idx: usize) -> &'a str {
361 self.labels[idx]
362 .as_ref() .map(|v| v.as_ref())
364 .unwrap_or("")
365 }
366
367 #[inline]
372 pub fn set_label_str(&mut self, idx: usize, str: Option<Cow<'static, str>>) {
373 self.labels[idx] = str;
374 }
375
376 #[inline]
378 pub fn block_len(&self) -> usize {
379 self.blocks.len()
380 }
381
382 #[inline]
387 pub fn block_area(&self, idx: usize) -> Rect {
388 self.block_areas[idx]
389 }
390
391 #[inline]
396 pub fn set_block_area(&mut self, idx: usize, area: Rect) {
397 self.block_areas[idx] = area;
398 }
399
400 #[inline]
402 pub fn block_area_iter(&self) -> impl Iterator<Item = &Rect> {
403 self.block_areas.iter()
404 }
405
406 #[inline]
411 pub fn block(&self, idx: usize) -> &Option<Block<'static>> {
412 &self.blocks[idx]
413 }
414
415 #[inline]
420 pub fn set_block(&mut self, idx: usize, block: Option<Block<'static>>) {
421 self.blocks[idx] = block;
422 }
423}
424
425#[inline]
426fn place(mut area: Rect, pos: Position) -> Rect {
427 area.x += pos.x;
428 area.y += pos.y;
429 area
430}