rat_widget/layout/
layout_edit.rs

1//!
2//! Calculate the layout for an edit-mask with lots of label/widget pairs.
3//!
4
5use crate::layout::GenericLayout;
6use ratatui::layout::{Flex, Rect, Size};
7use std::borrow::Cow;
8use std::cmp::{max, min};
9
10/// Constraint data for [layout_edit]
11#[allow(variant_size_differences)]
12#[derive(Debug)]
13pub enum EditConstraint {
14    /// Label by sample.
15    Label(Cow<'static, str>),
16    /// Label by width.
17    /// __unit: cols__
18    LabelWidth(u16),
19    /// Label by height+width.
20    /// __unit: cols, rows__
21    LabelRows(u16, u16),
22    /// Label occupying the full row.
23    /// This is added with its own index.
24    TitleLabel(Cow<'static, str>),
25    /// Label occupying the full row, but rendering only part of it.
26    /// This is added with its own index.
27    /// __unit: cols__
28    TitleLabelWidth(u16),
29    /// Label occupying multiple full rows.
30    /// This is added with its own index.
31    /// __unit: rows__
32    TitleLabelRows(u16),
33    /// Widget aligned with the label.
34    /// __unit: cols__
35    Widget(u16),
36    /// Widget aligned with the label.
37    /// __unit: cols, rows__
38    WidgetRows(u16, u16),
39    /// Empty space.
40    /// This is not a widget, just some spacing.
41    Empty,
42    /// Empty space.
43    /// This is not a widget, just some spacing.
44    /// __unit: rows__
45    EmptyRows(u16),
46    /// Widget aligned with the left margin.
47    /// __unit: cols__
48    LineWidget(u16),
49    /// Widget aligned with the left margin.
50    /// __unit: cols, rows__
51    LineWidgetRows(u16, u16),
52}
53
54/// Layout for an edit mask with lots of label+widget pairs.
55///
56/// This neatly aligns labels and widgets in one column.
57/// Use the edit constraints to define the layout.
58///
59/// This returns a [GenericLayout] with indexed widgets.
60///
61/// If the space runs out during layout, everything
62/// gets stuffed in the last row, regardless.
63///
64/// For more features see [LayoutForm](crate::layout::LayoutForm).
65///
66#[allow(clippy::comparison_chain)]
67pub fn layout_edit(
68    area: Size,
69    constraints: &[EditConstraint],
70    mut spacing: u16,
71    flex: Flex,
72) -> GenericLayout<usize> {
73    let mut max_label = 0;
74    let mut max_widget = 0;
75
76    // find max
77    for l in constraints.iter() {
78        match l {
79            EditConstraint::Label(s) => max_label = max(max_label, s.len() as u16),
80            EditConstraint::LabelWidth(w) => max_label = max(max_label, *w),
81            EditConstraint::LabelRows(w, _) => max_label = max(max_label, *w),
82            EditConstraint::TitleLabel(_) => {}
83            EditConstraint::TitleLabelWidth(_) => {}
84            EditConstraint::TitleLabelRows(_) => {}
85            EditConstraint::Widget(w) => max_widget = max(max_widget, *w),
86            EditConstraint::WidgetRows(w, _) => max_widget = max(max_widget, *w),
87            EditConstraint::LineWidget(_) => {}
88            EditConstraint::LineWidgetRows(_, _) => {}
89            EditConstraint::Empty => {}
90            EditConstraint::EmptyRows(_) => {}
91        }
92    }
93
94    let mut gen_layout = GenericLayout::new();
95
96    // cut excess
97    if max_label + spacing + max_widget > area.width {
98        let mut reduce = max_label + spacing + max_widget - area.width;
99
100        if spacing > reduce {
101            spacing -= reduce;
102            reduce = 0;
103        } else {
104            reduce -= spacing;
105            spacing = 0;
106        }
107        if max_label > 5 {
108            if max_label - 5 > reduce {
109                max_label -= reduce;
110                reduce = 0;
111            } else {
112                reduce -= max_label - 5;
113                max_label = 5;
114            }
115        }
116        if max_widget > 5 {
117            if max_widget - 5 > reduce {
118                max_widget -= reduce;
119                reduce = 0;
120            } else {
121                reduce -= max_widget - 5;
122                max_widget = 5;
123            }
124        }
125        if max_label > reduce {
126            max_label -= reduce;
127            reduce = 0;
128        } else {
129            reduce -= max_label;
130            max_label = 0;
131        }
132        if max_widget > reduce {
133            max_widget -= reduce;
134            // reduce = 0;
135        } else {
136            // reduce -= max_widget;
137            max_widget = 0;
138        }
139    }
140
141    let label_x;
142    let widget_x;
143
144    match flex {
145        Flex::Legacy => {
146            label_x = 0;
147            widget_x = label_x + spacing + max_label;
148        }
149        Flex::Start => {
150            label_x = 0;
151            widget_x = label_x + spacing + max_label;
152        }
153        Flex::End => {
154            widget_x = area.width - max_widget;
155            label_x = widget_x - spacing - max_label;
156        }
157        Flex::Center => {
158            let rest = area.width - max_label - max_widget - spacing;
159            label_x = rest / 2;
160            widget_x = label_x + spacing + max_label;
161        }
162        Flex::SpaceAround => {
163            let rest = area.width - max_label - max_widget - spacing;
164            label_x = rest / 2;
165            widget_x = label_x + spacing + max_label;
166        }
167        Flex::SpaceBetween => {
168            let rest = area.width - max_label - max_widget;
169            label_x = rest / 3;
170            widget_x = label_x + rest / 3 + max_label;
171        }
172    }
173    let total = max_label + spacing + max_widget;
174
175    let mut n = 0;
176    let mut y = 0;
177
178    let mut label_area = Rect::default();
179    let mut label_text = None;
180
181    let mut rest_height = if area.height > 0 { area.height - 1 } else { 0 };
182
183    for l in constraints.iter() {
184        let height;
185
186        // self
187        match l {
188            EditConstraint::Label(s) => {
189                height = 0;
190                label_area = Rect::new(label_x, y, max_label, min(1, rest_height));
191                label_text = Some(s.clone());
192            }
193            EditConstraint::LabelWidth(_) => {
194                height = 0;
195                label_area = Rect::new(label_x, y, max_label, min(1, rest_height));
196                label_text = None;
197            }
198            EditConstraint::LabelRows(_, h) => {
199                height = 0;
200                label_area = Rect::new(label_x, y, max_label, min(1, min(*h, rest_height)));
201                label_text = None;
202            }
203            EditConstraint::TitleLabel(s) => {
204                height = min(1, rest_height);
205
206                label_area = Rect::new(label_x, y, total, height);
207                gen_layout.add(n, Rect::default(), Some(s.clone()), label_area);
208
209                n += 1;
210                label_area = Rect::default();
211                label_text = None;
212            }
213            EditConstraint::TitleLabelWidth(w) => {
214                height = min(1, rest_height);
215
216                label_area = Rect::new(label_x, y, min(*w, max_label), height);
217                gen_layout.add(n, Rect::default(), None, label_area);
218
219                n += 1;
220                label_area = Rect::default();
221                label_text = None;
222            }
223            EditConstraint::TitleLabelRows(h) => {
224                height = min(*h, rest_height);
225
226                label_area = Rect::new(label_x, y, total, height);
227                gen_layout.add(n, Rect::default(), None, label_area);
228
229                n += 1;
230                label_area = Rect::default();
231                label_text = None;
232            }
233            EditConstraint::Widget(w) => {
234                let w_height = min(1, rest_height);
235                height = max(label_area.height, w_height);
236
237                let area = Rect::new(widget_x, y, min(*w, max_widget), w_height);
238                gen_layout.add(n, area, label_text.take(), label_area);
239
240                n += 1;
241                label_area = Rect::default();
242            }
243            EditConstraint::WidgetRows(w, h) => {
244                let w_height = min(*h, rest_height);
245                height = max(label_area.height, w_height);
246
247                let area = Rect::new(widget_x, y, min(*w, max_widget), w_height);
248                gen_layout.add(n, area, label_text.take(), label_area);
249
250                n += 1;
251                label_area = Rect::default();
252            }
253            EditConstraint::LineWidget(w) => {
254                height = min(1, rest_height);
255
256                let area = Rect::new(label_x, y, min(*w, total), height);
257                gen_layout.add(n, area, label_text.take(), label_area);
258
259                n += 1;
260                label_area = Rect::default();
261            }
262            EditConstraint::LineWidgetRows(w, h) => {
263                height = min(*h, rest_height);
264
265                let area = Rect::new(label_x, y, min(*w, total), height);
266                gen_layout.add(n, area, label_text.take(), label_area);
267
268                n += 1;
269                label_area = Rect::default();
270            }
271            EditConstraint::Empty => {
272                height = min(1, rest_height);
273
274                label_text = None;
275                label_area = Rect::default();
276            }
277            EditConstraint::EmptyRows(h) => {
278                height = min(*h, rest_height);
279
280                label_text = None;
281                label_area = Rect::default();
282            }
283        }
284
285        y += height;
286        rest_height -= height;
287    }
288
289    gen_layout
290}