tuviv/widgets/
size_constraint.rs

1//! Houses the [`SizeConstraint`] widget
2
3use std::ops::{Bound, RangeBounds};
4
5use le::{Layout, MinimumNatural};
6
7use crate::{
8    le::layout::{Rect, Vec2},
9    terminal::Buffer,
10    Widget,
11};
12
13/// A widget which forces its child to be of a fixed size
14pub struct SizeConstraint<
15    W: RangeBounds<usize>,
16    H: RangeBounds<usize>,
17    T: Widget,
18> {
19    /// The child widget
20    pub widget: T,
21    /// The range of allowed heights
22    pub width: W,
23    /// The range of allowed heights
24    pub height: H,
25}
26
27impl<W, H, T> SizeConstraint<W, H, T>
28where
29    W: RangeBounds<usize>,
30    H: RangeBounds<usize>,
31    T: Widget,
32{
33    /// Creates a new [`SizeConstraint`]
34    /// See [`WidgetExt`](crate::prelude::WidgetExt::fixed_size) for
35    /// multiple builder methods
36    pub fn new(widget: T, width: W, height: H) -> Self {
37        Self {
38            widget,
39            width,
40            height,
41        }
42    }
43
44    /// Clamps the width to our size constraints
45    fn clamp_width(&self, width: usize) -> usize {
46        let start = start_bound_n(&self.width);
47        let end = end_bound_n(&self.width);
48        width.clamp(start, end.max(start))
49    }
50
51    /// Clamps the height to our size constraints
52    fn clamp_height(&self, height: usize) -> usize {
53        let start = start_bound_n(&self.height);
54        let end = end_bound_n(&self.height);
55
56        height.clamp(start, end.max(start))
57    }
58}
59
60/// Gets the start bound as a `usize`
61fn start_bound_n<R: RangeBounds<usize>>(range: &R) -> usize {
62    bound_n(&range.start_bound()).unwrap_or(0)
63}
64
65/// Gets the end bound as a `usize`
66fn end_bound_n<R: RangeBounds<usize>>(range: &R) -> usize {
67    bound_n(&range.end_bound()).unwrap_or(usize::MAX)
68}
69
70/// Gets the bound as an `Option<usize>`
71fn bound_n(bound: &Bound<&usize>) -> Option<usize> {
72    match bound {
73        Bound::Excluded(x) => Some(x.saturating_sub(1)),
74        Bound::Included(x) => Some(**x),
75        Bound::Unbounded => None,
76    }
77}
78
79impl<W, H, T> Layout for SizeConstraint<W, H, T>
80where
81    W: RangeBounds<usize>,
82    H: RangeBounds<usize>,
83    T: Widget,
84{
85    fn width_for_height(&self, height: usize) -> MinimumNatural<usize> {
86        let height = self.clamp_height(height);
87
88        MinimumNatural {
89            minimum: self
90                .clamp_width(self.widget.width_for_height(height).minimum),
91            natural: self
92                .clamp_width(self.widget.width_for_height(height).natural),
93        }
94    }
95
96    fn height_for_width(&self, width: usize) -> MinimumNatural<usize> {
97        let width = self.clamp_width(width);
98        MinimumNatural {
99            minimum: self
100                .clamp_height(self.widget.height_for_width(width).minimum),
101            natural: self
102                .clamp_height(self.widget.height_for_width(width).natural),
103        }
104    }
105
106    fn prefered_size(&self) -> MinimumNatural<Vec2> {
107        // Get the widgets prefered size
108        let size_bounds = (
109            bound_n(&self.width.end_bound()),
110            bound_n(&self.height.end_bound()),
111        );
112        let widget_size_og = if let (Some(size_x), Some(size_y)) = size_bounds {
113            self.widget
114                .prefered_size_of_container(Vec2::new(size_x, size_y))
115        } else {
116            self.widget.prefered_size()
117        };
118        let mut widget_size = widget_size_og;
119
120        // Clamp there size
121        widget_size.minimum.x = self.clamp_width(widget_size_og.minimum.x);
122        widget_size.minimum.y = self.clamp_height(widget_size_og.minimum.y);
123
124        widget_size.natural.x = self.clamp_width(widget_size_og.natural.x);
125        widget_size.natural.y = self.clamp_height(widget_size_og.natural.y);
126
127        // See if we can relayout their height-for-width
128        // or width-for-height based of this new layout
129        if widget_size.minimum.x != widget_size_og.minimum.x {
130            widget_size.minimum.y = self.clamp_height(
131                self.height_for_width(widget_size.minimum.x).minimum,
132            );
133        }
134        if widget_size.minimum.y != widget_size_og.minimum.y {
135            widget_size.minimum.x = self.clamp_width(
136                self.width_for_height(widget_size.minimum.y).minimum,
137            );
138        }
139
140        if widget_size.natural.x != widget_size_og.natural.x {
141            widget_size.natural.y = self.clamp_height(
142                self.height_for_width(widget_size.natural.x).natural,
143            );
144        }
145        if widget_size.natural.y != widget_size_og.natural.y {
146            widget_size.natural.x = self.clamp_width(
147                self.width_for_height(widget_size.natural.y).natural,
148            );
149        }
150
151        // Return
152        widget_size
153    }
154
155    fn prefered_size_of_container(
156        &self,
157        container: Vec2,
158    ) -> MinimumNatural<Vec2> {
159        let container = Vec2::new(
160            self.clamp_width(container.x),
161            self.clamp_height(container.y),
162        );
163        let widget_size_og = self.widget.prefered_size_of_container(container);
164        let mut widget_size = self.widget.prefered_size_of_container(container);
165
166        // Clamp there size
167        widget_size.minimum.x = self.clamp_width(widget_size_og.minimum.x);
168        widget_size.minimum.y = self.clamp_height(widget_size_og.minimum.y);
169
170        widget_size.natural.x = self.clamp_width(widget_size_og.natural.x);
171        widget_size.natural.y = self.clamp_height(widget_size_og.natural.y);
172
173        // See if we can relayout their height-for-width
174        // or width-for-height based of this new layout
175        if widget_size.minimum.x != widget_size_og.minimum.x {
176            widget_size.minimum.y = self.clamp_height(
177                self.height_for_width(widget_size.minimum.x).minimum,
178            );
179        }
180        if widget_size.minimum.y != widget_size_og.minimum.y {
181            widget_size.minimum.x = self.clamp_width(
182                self.width_for_height(widget_size.minimum.y).minimum,
183            );
184        }
185
186        if widget_size.natural.x != widget_size_og.natural.x {
187            widget_size.natural.y = self.clamp_height(
188                self.height_for_width(widget_size.natural.x).natural,
189            );
190        }
191        if widget_size.natural.y != widget_size_og.natural.y {
192            widget_size.natural.x = self.clamp_width(
193                self.width_for_height(widget_size.natural.y).natural,
194            );
195        }
196
197        // Return
198        widget_size
199    }
200}
201
202impl<W, H, T> Widget for SizeConstraint<W, H, T>
203where
204    W: RangeBounds<usize>,
205    H: RangeBounds<usize>,
206    T: Widget,
207{
208    fn render(&self, rect: Rect, buffer: &mut Buffer) {
209        let mut rect = rect;
210
211        if self.clamp_width(rect.size.x) != rect.size.x {
212            rect.size.x =
213                self.clamp_width(self.width_for_height(rect.size.y).natural);
214        }
215        if self.clamp_height(rect.size.y) != rect.size.y {
216            rect.size.y =
217                self.clamp_height(self.height_for_width(rect.size.x).natural);
218        }
219
220        self.widget.render(rect, buffer);
221    }
222}