rat_widget/layout/
layout_edit.rs

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