rat_widget/layout/
generic_layout.rs

1use ratatui::layout::{Position, Rect, Size};
2use ratatui::widgets::Block;
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::hash::Hash;
6
7/// Stores layout data resulting from some layout algorithm.
8///
9/// Widgets and labels are stored for some key that identifies
10/// the widget. It is also possible to store the label text.
11///
12/// [Block]s can be added too. It is expected that blocks
13/// will be rendered in order of addition.
14///
15/// There is a concept for pages too. The page-height defines
16/// the pages. The page-width is not used to constrain
17/// the pages and is just informational. It can be used
18/// to find if the layout has to be rebuilt after a resize.
19///
20/// The page-count is available too, but there may be
21/// areas that map beyond the page-count.
22///
23/// __See__
24/// [LayoutForm](crate::layout::LayoutForm)
25/// [layout_edit](crate::layout::layout_edit())
26///
27#[derive(Debug, Clone)]
28pub struct GenericLayout<W>
29where
30    W: Eq + Hash + Clone,
31{
32    /// Area of the layout.
33    area: Rect,
34    /// Page size.
35    page_size: Size,
36    /// Pages.
37    page_count: usize,
38
39    /// Widget keys.
40    widgets: HashMap<W, usize>,
41    rwidgets: HashMap<usize, W>,
42    /// Widget areas.
43    widget_areas: Vec<Rect>,
44    /// Widget labels.
45    labels: Vec<Option<Cow<'static, str>>>,
46    /// Label areas.
47    label_areas: Vec<Rect>,
48
49    /// Container areas.
50    block_areas: Vec<Rect>,
51    /// Container blocks.
52    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    /// Initialize with a certain capacity.
84    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    /// Clear all data.
100    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    /// Set the area used for this layout.
114    /// The area may or may not have anything to do with the page-size.
115    pub fn set_area(&mut self, area: Rect) {
116        self.area = area;
117    }
118
119    /// The area used for this layout.
120    /// The area may or may not have anything to do with the page-size.
121    pub fn area(&self) -> Rect {
122        self.area
123    }
124
125    /// Area differs from stored area?
126    pub fn area_changed(&self, area: Rect) -> bool {
127        self.area != area
128    }
129
130    /// Set the page-size for this layout.
131    ///
132    /// Defaults to (u16::MAX, u16::MAX).
133    pub fn set_page_size(&mut self, size: Size) {
134        self.page_size = size;
135    }
136
137    /// Get the page-size for this layout.
138    pub fn page_size(&self) -> Size {
139        self.page_size
140    }
141
142    /// Page-size changed.
143    pub fn size_changed(&self, size: Size) -> bool {
144        self.page_size != size
145    }
146
147    /// Number of pages
148    pub fn set_page_count(&mut self, page_count: usize) {
149        self.page_count = page_count;
150    }
151
152    /// Number of pages
153    pub fn page_count(&self) -> usize {
154        self.page_count
155    }
156
157    /// Add widget + label areas.
158    pub fn add(
159        &mut self, //
160        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    /// Add a block.
174    pub fn add_block(
175        &mut self, //
176        area: Rect,
177        block: Option<Block<'static>>,
178    ) {
179        self.block_areas.push(area);
180        self.blocks.push(block);
181    }
182
183    /// Places the layout at the given position.
184    /// This shifts all area right by the given offset.
185    ///
186    /// Most layout functions create a layout that starts at (0,0).
187    /// That is ok, as the widgets __using__ such a layout
188    /// associate their top/left position with (0,0) and start
189    /// from there.
190    ///
191    /// If you want to use the layout without such a widget,
192    /// this one is nice.
193    #[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    /// First widget on the given page.
208    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    /// Calculates the page of the widget.
219    #[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    /// Any widgets/blocks?
229    pub fn is_empty(&self) -> bool {
230        self.widget_areas.is_empty() && self.block_areas.is_empty()
231    }
232
233    /// Is this as layout with height = u16::MAX?
234    pub fn is_endless(&self) -> bool {
235        self.area.height == u16::MAX
236    }
237
238    /// Number of widgets/labels.
239    #[inline]
240    pub fn widget_len(&self) -> usize {
241        self.widgets.len()
242    }
243
244    /// Returns the index for this widget.
245    pub fn try_index_of(&self, widget: W) -> Option<usize> {
246        self.widgets.get(&widget).copied()
247    }
248
249    /// Returns the index for this widget.
250    ///
251    /// __Panic__
252    /// Panics if there is no widget for the key.
253    pub fn index_of(&self, widget: W) -> usize {
254        self.widgets.get(&widget).copied().expect("widget")
255    }
256
257    /// Access widget key.
258    ///
259    /// __Panic__
260    /// Panics on out of bounds.
261    #[inline]
262    pub fn widget_key(&self, idx: usize) -> W {
263        self.rwidgets.get(&idx).cloned().expect("valid_idx")
264    }
265
266    /// Access widget keys
267    #[inline]
268    pub fn widget_keys(&self) -> impl Iterator<Item = &W> {
269        self.widgets.keys()
270    }
271
272    /// Access the label area by key.
273    ///
274    /// __Panic__
275    /// Panics on out of bounds.
276    /// Panics if the key doesn't exist.
277    #[inline]
278    pub fn label_for(&self, widget: W) -> Rect {
279        self.label_areas[self.index_of(widget)]
280    }
281
282    /// Access label area.
283    ///
284    /// __Panic__
285    /// Panics on out of bounds.
286    #[inline]
287    pub fn label(&self, idx: usize) -> Rect {
288        self.label_areas[idx]
289    }
290
291    /// Set the label area.
292    ///
293    /// __Panic__
294    /// Panics on out of bounds.
295    #[inline]
296    pub fn set_label(&mut self, idx: usize, area: Rect) {
297        self.label_areas[idx] = area;
298    }
299
300    /// Access the widget area by key.
301    ///
302    /// __Panic__
303    /// Panics on out of bounds.
304    /// Panics if the key doesn't exist.
305    #[inline]
306    pub fn widget_for(&self, widget: W) -> Rect {
307        self.widget_areas[self.index_of(widget)]
308    }
309
310    /// Access widget area.
311    ///
312    /// __Panic__
313    /// Panics on out of bounds.
314    #[inline]
315    pub fn widget(&self, idx: usize) -> Rect {
316        self.widget_areas[idx]
317    }
318
319    /// Change the widget area.
320    ///
321    /// __Panic__
322    /// Panics on out of bounds.
323    #[inline]
324    pub fn set_widget(&mut self, idx: usize, area: Rect) {
325        self.widget_areas[idx] = area;
326    }
327
328    /// Access the label string by key.
329    ///
330    /// __Panic__
331    /// Panics on out of bounds.
332    /// Panics if the key doesn't exist.
333    #[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    /// Access label string.
339    ///
340    /// __Panic__
341    /// Panics on out of bounds.
342    #[inline]
343    pub fn try_label_str(&self, idx: usize) -> &Option<Cow<'static, str>> {
344        &self.labels[idx]
345    }
346
347    /// Access the label string by key.
348    ///
349    /// __Panic__
350    /// Panics on out of bounds.
351    /// Panics if the key doesn't exist.
352    #[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    /// Access label string.
361    ///
362    /// __Panic__
363    /// Panics on out of bounds.
364    #[inline]
365    pub fn label_str(&self, idx: usize) -> &str {
366        self.labels[idx]
367            .as_ref() //
368            .map(|v| v.as_ref())
369            .unwrap_or("")
370    }
371
372    /// Set the label string.
373    ///
374    /// __Panic__
375    /// Panics on out of bounds.
376    #[inline]
377    pub fn set_label_str(&mut self, idx: usize, str: Option<Cow<'static, str>>) {
378        self.labels[idx] = str;
379    }
380
381    /// Container count.
382    #[inline]
383    pub fn block_len(&self) -> usize {
384        self.blocks.len()
385    }
386
387    /// Access block area.
388    ///
389    /// __Panic__
390    /// Panics on out of bounds.
391    #[inline]
392    pub fn block_area(&self, idx: usize) -> Rect {
393        self.block_areas[idx]
394    }
395
396    /// Set the block area.
397    ///
398    /// __Panic__
399    /// Panics on out of bounds.
400    #[inline]
401    pub fn set_block_area(&mut self, idx: usize, area: Rect) {
402        self.block_areas[idx] = area;
403    }
404
405    /// Iterate block areas.
406    #[inline]
407    pub fn block_area_iter(&self) -> impl Iterator<Item = &Rect> {
408        self.block_areas.iter()
409    }
410
411    /// Access container block.
412    ///
413    /// __Panic__
414    /// Panics on out of bounds.
415    #[inline]
416    pub fn block(&self, idx: usize) -> &Option<Block<'static>> {
417        &self.blocks[idx]
418    }
419
420    /// Set the container block.
421    ///
422    /// __Panic__
423    /// Panics on out of bounds.
424    #[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}