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    /// Number of widgets/labels.
234    #[inline]
235    pub fn widget_len(&self) -> usize {
236        self.widgets.len()
237    }
238
239    /// Returns the index for this widget.
240    pub fn try_index_of(&self, widget: W) -> Option<usize> {
241        self.widgets.get(&widget).copied()
242    }
243
244    /// Returns the index for this widget.
245    ///
246    /// __Panic__
247    /// Panics if there is no widget for the key.
248    pub fn index_of(&self, widget: W) -> usize {
249        self.widgets.get(&widget).copied().expect("widget")
250    }
251
252    /// Access widget key.
253    ///
254    /// __Panic__
255    /// Panics on out of bounds.
256    #[inline]
257    pub fn widget_key(&self, idx: usize) -> W {
258        self.rwidgets.get(&idx).cloned().expect("valid_idx")
259    }
260
261    /// Access widget keys
262    #[inline]
263    pub fn widget_keys(&self) -> impl Iterator<Item = &W> {
264        self.widgets.keys()
265    }
266
267    /// Access the label area by key.
268    ///
269    /// __Panic__
270    /// Panics on out of bounds.
271    /// Panics if the key doesn't exist.
272    #[inline]
273    pub fn label_for(&self, widget: W) -> Rect {
274        self.label_areas[self.index_of(widget)]
275    }
276
277    /// Access label area.
278    ///
279    /// __Panic__
280    /// Panics on out of bounds.
281    #[inline]
282    pub fn label(&self, idx: usize) -> Rect {
283        self.label_areas[idx]
284    }
285
286    /// Set the label area.
287    ///
288    /// __Panic__
289    /// Panics on out of bounds.
290    #[inline]
291    pub fn set_label(&mut self, idx: usize, area: Rect) {
292        self.label_areas[idx] = area;
293    }
294
295    /// Access the widget area by key.
296    ///
297    /// __Panic__
298    /// Panics on out of bounds.
299    /// Panics if the key doesn't exist.
300    #[inline]
301    pub fn widget_for(&self, widget: W) -> Rect {
302        self.widget_areas[self.index_of(widget)]
303    }
304
305    /// Access widget area.
306    ///
307    /// __Panic__
308    /// Panics on out of bounds.
309    #[inline]
310    pub fn widget(&self, idx: usize) -> Rect {
311        self.widget_areas[idx]
312    }
313
314    /// Change the widget area.
315    ///
316    /// __Panic__
317    /// Panics on out of bounds.
318    #[inline]
319    pub fn set_widget(&mut self, idx: usize, area: Rect) {
320        self.widget_areas[idx] = area;
321    }
322
323    /// Access the label string by key.
324    ///
325    /// __Panic__
326    /// Panics on out of bounds.
327    /// Panics if the key doesn't exist.
328    #[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    /// Access label string.
334    ///
335    /// __Panic__
336    /// Panics on out of bounds.
337    #[inline]
338    pub fn try_label_str(&self, idx: usize) -> &Option<Cow<'static, str>> {
339        &self.labels[idx]
340    }
341
342    /// Access the label string by key.
343    ///
344    /// __Panic__
345    /// Panics on out of bounds.
346    /// Panics if the key doesn't exist.
347    #[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    /// Access label string.
356    ///
357    /// __Panic__
358    /// Panics on out of bounds.
359    #[inline]
360    pub fn label_str<'a>(&'a self, idx: usize) -> &'a str {
361        self.labels[idx]
362            .as_ref() //
363            .map(|v| v.as_ref())
364            .unwrap_or("")
365    }
366
367    /// Set the label string.
368    ///
369    /// __Panic__
370    /// Panics on out of bounds.
371    #[inline]
372    pub fn set_label_str(&mut self, idx: usize, str: Option<Cow<'static, str>>) {
373        self.labels[idx] = str;
374    }
375
376    /// Container count.
377    #[inline]
378    pub fn block_len(&self) -> usize {
379        self.blocks.len()
380    }
381
382    /// Access block area.
383    ///
384    /// __Panic__
385    /// Panics on out of bounds.
386    #[inline]
387    pub fn block_area(&self, idx: usize) -> Rect {
388        self.block_areas[idx]
389    }
390
391    /// Set the block area.
392    ///
393    /// __Panic__
394    /// Panics on out of bounds.
395    #[inline]
396    pub fn set_block_area(&mut self, idx: usize, area: Rect) {
397        self.block_areas[idx] = area;
398    }
399
400    /// Iterate block areas.
401    #[inline]
402    pub fn block_area_iter(&self) -> impl Iterator<Item = &Rect> {
403        self.block_areas.iter()
404    }
405
406    /// Access container block.
407    ///
408    /// __Panic__
409    /// Panics on out of bounds.
410    #[inline]
411    pub fn block(&self, idx: usize) -> &Option<Block<'static>> {
412        &self.blocks[idx]
413    }
414
415    /// Set the container block.
416    ///
417    /// __Panic__
418    /// Panics on out of bounds.
419    #[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}