Skip to main content

rat_widget/layout/
generic_layout.rs

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/// Stores labels and areas for a calculated layout,
8/// a widget-key is used to access the stored layout.
9///
10/// * You can keep the layout in some state and
11/// only rebuild on [GenericLayout::area_changed].
12/// * It can support the concept of a multipage layout.
13///     * A widgets y-position divided by the page-height
14///       gives a page.
15///     * Query widgets based on page.
16/// * As extra Blocks can be added for some visual structure.
17///
18/// __See__
19/// [LayoutForm](crate::layout::LayoutForm)
20/// [layout_edit](crate::layout::layout_edit())
21///
22#[derive(Debug, Clone)]
23pub struct GenericLayout<W>
24where
25    W: Eq + Hash + Clone,
26{
27    /// Area of the layout.
28    area: Rect,
29    /// Page size.
30    page_size: Size,
31    /// Pages.
32    page_count: usize,
33    ///
34    place: Position,
35
36    /// Widget keys.
37    widgets: HashMap<W, usize>,
38    rwidgets: HashMap<usize, W>,
39    /// Widget areas.
40    widget_areas: Vec<Rect>,
41    /// Widget labels.
42    labels: Vec<Option<Cow<'static, str>>>,
43    /// Label areas.
44    label_areas: Vec<Rect>,
45
46    /// Container areas.
47    block_areas: Vec<Rect>,
48    /// Container blocks.
49    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    /// Initialize with a certain capacity.
82    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    /// Clear all data.
99    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    /// Set an area that was used to build the layout.
114    /// This doesn't limit the layout, its sole use is to
115    /// invalidate the layout.
116    pub fn set_area(&mut self, area: Rect) {
117        self.area = area;
118    }
119
120    /// The area used to build this layout.
121    /// This doesn't limit the layout, its sole use is to
122    /// invalidate the layout.
123    pub fn area(&self) -> Rect {
124        self.area
125    }
126
127    /// Area differs from stored area?
128    pub fn area_changed(&self, area: Rect) -> bool {
129        self.area != area
130    }
131
132    /// Set the page-size for this layout.
133    ///
134    /// Defaults to (u16::MAX, u16::MAX).
135    pub fn set_page_size(&mut self, size: Size) {
136        self.page_size = size;
137    }
138
139    /// Get the page-size for this layout.
140    pub fn page_size(&self) -> Size {
141        self.page_size
142    }
143
144    /// Page-size changed.
145    pub fn size_changed(&self, size: Size) -> bool {
146        self.page_size != size
147    }
148
149    /// Number of pages.
150    pub fn set_page_count(&mut self, page_count: usize) {
151        self.page_count = page_count;
152    }
153
154    /// Number of pages.
155    pub fn page_count(&self) -> usize {
156        self.page_count
157    }
158
159    /// Add widget + label areas.
160    pub fn add(
161        &mut self, //
162        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    /// Add a block.
176    pub fn add_block(
177        &mut self, //
178        area: Rect,
179        block: Option<Block<'static>>,
180    ) {
181        self.block_areas.push(area);
182        self.blocks.push(block);
183    }
184
185    /// Places the layout at the given position.
186    /// This shifts all areas by the given offset.
187    ///
188    /// Most layout functions create a layout that starts at (0,0).
189    /// That is ok, as the widgets __using__ such a layout
190    /// associate their top/left position with (0,0) and start
191    /// from there.
192    ///
193    /// If you want to use the layout without such a widget,
194    /// this one is nice.
195    ///
196    /// __Caution__
197    /// Calling this twice doesn't accumulate the shifts,
198    /// the new place replaces the old one.
199    #[inline]
200    pub fn place(mut self, place: Position) -> Self {
201        self.place = place;
202        self
203    }
204
205    /// First widget on the given page.
206    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    /// Calculates the page of the widget.
217    #[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    /// Any widgets/blocks?
227    pub fn is_empty(&self) -> bool {
228        self.widget_areas.is_empty() && self.block_areas.is_empty()
229    }
230
231    /// Is this as layout with height = u16::MAX?
232    pub fn is_endless(&self) -> bool {
233        self.area.height == u16::MAX
234    }
235
236    /// Number of widgets/labels.
237    #[inline]
238    pub fn widget_len(&self) -> usize {
239        self.widgets.len()
240    }
241
242    /// Returns the index for this widget.
243    pub fn try_index_of(&self, widget: W) -> Option<usize> {
244        self.widgets.get(&widget).copied()
245    }
246
247    /// Returns the index for this widget.
248    ///
249    /// __Panic__
250    /// Panics if there is no widget for the key.
251    pub fn index_of(&self, widget: W) -> usize {
252        self.widgets.get(&widget).copied().expect("widget")
253    }
254
255    /// Access widget key.
256    ///
257    /// __Panic__
258    /// Panics on out of bounds.
259    #[inline]
260    pub fn widget_key(&self, idx: usize) -> W {
261        self.rwidgets.get(&idx).cloned().expect("valid_idx")
262    }
263
264    /// Access widget keys
265    #[inline]
266    pub fn widget_keys(&self) -> impl Iterator<Item = &W> {
267        self.widgets.keys()
268    }
269
270    /// Access the label area by key.
271    ///
272    /// __Panic__
273    /// Panics on out of bounds.
274    /// Panics if the key doesn't exist.
275    #[inline]
276    pub fn label_for(&self, widget: W) -> Rect {
277        place(self.label_areas[self.index_of(widget)], self.place)
278    }
279
280    /// Access label area.
281    ///
282    /// __Panic__
283    /// Panics on out of bounds.
284    #[inline]
285    pub fn label(&self, idx: usize) -> Rect {
286        place(self.label_areas[idx], self.place)
287    }
288
289    /// Set the label area.
290    ///
291    /// __Panic__
292    /// Panics on out of bounds.
293    #[inline]
294    pub fn set_label(&mut self, idx: usize, area: Rect) {
295        self.label_areas[idx] = area;
296    }
297
298    /// Access the widget area by key.
299    ///
300    /// __Panic__
301    /// Panics on out of bounds.
302    /// Panics if the key doesn't exist.
303    #[inline]
304    pub fn widget_for(&self, widget: W) -> Rect {
305        place(self.widget_areas[self.index_of(widget)], self.place)
306    }
307
308    /// Access widget area.
309    ///
310    /// __Panic__
311    /// Panics on out of bounds.
312    #[inline]
313    pub fn widget(&self, idx: usize) -> Rect {
314        place(self.widget_areas[idx], self.place)
315    }
316
317    /// Change the widget area.
318    ///
319    /// __Panic__
320    /// Panics on out of bounds.
321    #[inline]
322    pub fn set_widget(&mut self, idx: usize, area: Rect) {
323        self.widget_areas[idx] = area;
324    }
325
326    /// Access the label string by key.
327    ///
328    /// __Panic__
329    /// Panics on out of bounds.
330    /// Panics if the key doesn't exist.
331    #[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    /// Access label string.
337    ///
338    /// __Panic__
339    /// Panics on out of bounds.
340    #[inline]
341    pub fn try_label_str(&self, idx: usize) -> &Option<Cow<'static, str>> {
342        &self.labels[idx]
343    }
344
345    /// Access the label string by key.
346    ///
347    /// __Panic__
348    /// Panics on out of bounds.
349    /// Panics if the key doesn't exist.
350    #[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    /// Access label string.
359    ///
360    /// __Panic__
361    /// Panics on out of bounds.
362    #[inline]
363    pub fn label_str(&self, idx: usize) -> &str {
364        self.labels[idx]
365            .as_ref() //
366            .map(|v| v.as_ref())
367            .unwrap_or("")
368    }
369
370    /// Set the label string.
371    ///
372    /// __Panic__
373    /// Panics on out of bounds.
374    #[inline]
375    pub fn set_label_str(&mut self, idx: usize, str: Option<Cow<'static, str>>) {
376        self.labels[idx] = str;
377    }
378
379    /// Container count.
380    #[inline]
381    pub fn block_len(&self) -> usize {
382        self.blocks.len()
383    }
384
385    /// Access block area.
386    ///
387    /// __Panic__
388    /// Panics on out of bounds.
389    #[inline]
390    pub fn block_area(&self, idx: usize) -> Rect {
391        self.block_areas[idx]
392    }
393
394    /// Set the block area.
395    ///
396    /// __Panic__
397    /// Panics on out of bounds.
398    #[inline]
399    pub fn set_block_area(&mut self, idx: usize, area: Rect) {
400        self.block_areas[idx] = area;
401    }
402
403    /// Iterate block areas.
404    #[inline]
405    pub fn block_area_iter(&self) -> impl Iterator<Item = &Rect> {
406        self.block_areas.iter()
407    }
408
409    /// Access container block.
410    ///
411    /// __Panic__
412    /// Panics on out of bounds.
413    #[inline]
414    pub fn block(&self, idx: usize) -> &Option<Block<'static>> {
415        &self.blocks[idx]
416    }
417
418    /// Set the container block.
419    ///
420    /// __Panic__
421    /// Panics on out of bounds.
422    #[inline]
423    pub fn set_block(&mut self, idx: usize, block: Option<Block<'static>>) {
424        self.blocks[idx] = block;
425    }
426
427    /// Append another layout.
428    ///
429    /// Shifts the second layout to the given starting position and
430    /// adds everything.
431    ///
432    /// If the given layout uses the same widget-identifiers, they
433    /// will simply overwrite existing ones.
434    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}