pixel_widgets/
layout.rs

1/// A sizing request
2#[derive(Debug, Clone, Copy)]
3pub enum Size {
4    /// Try to fit all children exactly
5    Shrink,
6    /// An exact size in units
7    Exact(f32),
8    /// Fill the available space using a weight in units.
9    /// The available space is divided between `Fill` sizes according to their weight.
10    Fill(u32),
11}
12
13/// Alignment
14#[allow(missing_docs)]
15#[derive(Clone, Copy, Debug)]
16pub enum Align {
17    Begin,
18    Center,
19    End,
20}
21
22/// Layout direction
23#[allow(missing_docs)]
24#[derive(Clone, Copy, Debug)]
25pub enum Direction {
26    TopToBottom,
27    LeftToRight,
28    RightToLeft,
29    BottomToTop,
30}
31
32/// A rectangle
33#[allow(missing_docs)]
34#[derive(Clone, Copy, Debug)]
35pub struct Rectangle {
36    pub left: f32,
37    pub top: f32,
38    pub right: f32,
39    pub bottom: f32,
40}
41
42impl Size {
43    /// Resolve the `Size` to an actual size
44    pub fn resolve(self, available_space: f32, available_parts: u32) -> f32 {
45        match self {
46            Size::Shrink => 0.0,
47            Size::Exact(wanted) => wanted,
48            Size::Fill(parts) => (available_space * parts as f32) / available_parts as f32,
49        }
50    }
51
52    /// Get the weight of this `Size`, which is 0 for non fill sizes.
53    pub fn parts(&self) -> u32 {
54        match self {
55            Size::Fill(parts) => *parts,
56            _ => 0,
57        }
58    }
59
60    /// Get the minimum size of this `Size`, which is 0 for non exact sizes.
61    pub fn min_size(&self) -> f32 {
62        match self {
63            Size::Exact(wanted) => *wanted,
64            _ => 0.0,
65        }
66    }
67}
68
69impl Align {
70    /// Align `space` units within `available_space`.
71    pub fn resolve_start(self, space: f32, available_space: f32) -> f32 {
72        match self {
73            Align::Begin => 0.0,
74            Align::Center => (available_space - space) * 0.5,
75            Align::End => available_space - space,
76        }
77    }
78}
79
80impl Rectangle {
81    /// Convert a rectangle to device coordinates (`[-1.0, 1.0]`) using a `Viewport`.
82    /// (-1, -1) is the top left corner (0, 0), where (1, 1) is the bottom right
83    /// corner (viewport.width(), viewport.height()).
84    pub fn to_device_coordinates(self, viewport: Rectangle) -> Rectangle {
85        let center = (
86            (viewport.left + viewport.right) * 0.5,
87            (viewport.top + viewport.bottom) * 0.5,
88        );
89        let size = (
90            (viewport.right - viewport.left) * 0.5,
91            (viewport.top - viewport.bottom) * -0.5,
92        );
93        Rectangle {
94            left: (self.left - center.0) / size.0,
95            top: (self.top - center.1) / size.1,
96            right: (self.right - center.0) / size.0,
97            bottom: (self.bottom - center.1) / size.1,
98        }
99    }
100
101    /// Return a zero size rectangle
102    pub fn zero() -> Rectangle {
103        Rectangle {
104            left: 0.0,
105            right: 0.0,
106            top: 0.0,
107            bottom: 0.0,
108        }
109    }
110
111    /// Construct a new rectangle with (0, 0) as (left, top), and w, h as (right, bottom)
112    pub fn from_wh(w: f32, h: f32) -> Rectangle {
113        Rectangle {
114            left: 0.0,
115            right: w,
116            top: 0.0,
117            bottom: h,
118        }
119    }
120
121    /// Construct a new rectangle from a position and a size
122    pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Rectangle {
123        Rectangle {
124            left: x,
125            right: x + w,
126            top: y,
127            bottom: y + h,
128        }
129    }
130
131    /// Returns `true` when the queried point is inside the rectangle
132    pub fn point_inside(&self, x: f32, y: f32) -> bool {
133        x >= self.left && x < self.right && y >= self.top && y < self.bottom
134    }
135
136    /// Returns the rectangle that is covered both by `self` and `other`.
137    /// `None` is returned if the rectangles do not overlap.
138    pub fn intersect(&self, other: &Rectangle) -> Option<Rectangle> {
139        let result = Rectangle {
140            left: self.left.max(other.left),
141            top: self.top.max(other.top),
142            right: self.right.min(other.right),
143            bottom: self.bottom.min(other.bottom),
144        };
145        if result.left < result.right && result.top < result.bottom {
146            Some(result)
147        } else {
148            None
149        }
150    }
151
152    /// Return a point within this rectangle. The point should be in [0, 1] range.
153    pub fn pt(&self, x: f32, y: f32) -> [f32; 2] {
154        [
155            self.left + (self.right - self.left) * x,
156            self.top + (self.bottom - self.top) * y,
157        ]
158    }
159
160    /// Return a rectangle with all fields rounded
161    pub fn round(self) -> Rectangle {
162        Rectangle {
163            left: self.left.round(),
164            top: self.top.round(),
165            right: self.right.round(),
166            bottom: self.bottom.round(),
167        }
168    }
169
170    pub(crate) fn sub(&self, lerps: Rectangle) -> Rectangle {
171        Rectangle {
172            left: self.left + (self.right - self.left) * lerps.left,
173            right: self.left + (self.right - self.left) * lerps.right,
174            top: self.top + (self.bottom - self.top) * lerps.top,
175            bottom: self.top + (self.bottom - self.top) * lerps.bottom,
176        }
177    }
178
179    /// Apply a translation the the rectangle
180    pub fn translate(&self, x: f32, y: f32) -> Rectangle {
181        Rectangle {
182            left: self.left + x,
183            top: self.top + y,
184            right: self.right + x,
185            bottom: self.bottom + y,
186        }
187    }
188
189    /// Increase the size of the rectangle on the right and bottom side.
190    pub fn grow(&self, w: f32, h: f32) -> Rectangle {
191        Rectangle {
192            left: self.left,
193            top: self.top,
194            right: self.right + w,
195            bottom: self.bottom + h,
196        }
197    }
198
199    /// Decrease the size of the rectangle on all sides
200    pub fn inset(&self, x: f32, y: f32) -> Option<Rectangle> {
201        if self.width() > y * 2.0 && self.height() > x * 2.0 {
202            Some(Rectangle {
203                left: self.left + x,
204                top: self.top + y,
205                right: self.right - x,
206                bottom: self.bottom - y,
207            })
208        } else {
209            None
210        }
211    }
212
213    /// Grow the rectangle on all sides
214    pub fn outset(&self, x: f32, y: f32) -> Rectangle {
215        Rectangle {
216            left: self.left - x,
217            top: self.top - y,
218            right: self.right + x,
219            bottom: self.bottom + y,
220        }
221    }
222
223    /// Return a rectangle with the same size, but positioned at the origin
224    pub fn size(&self) -> Rectangle {
225        Rectangle {
226            left: 0.0,
227            top: 0.0,
228            right: self.width(),
229            bottom: self.height(),
230        }
231    }
232
233    /// The width of the rectangle
234    pub fn width(&self) -> f32 {
235        self.right - self.left
236    }
237
238    /// The height of the rectangle
239    pub fn height(&self) -> f32 {
240        self.bottom - self.top
241    }
242
243    /// Apply a margin to the rectangle
244    pub fn after_margin(self, margin: Rectangle) -> Rectangle {
245        Rectangle {
246            left: self.left - margin.left,
247            top: self.top - margin.top,
248            right: self.right + margin.right,
249            bottom: self.bottom + margin.bottom,
250        }
251    }
252
253    /// Apply padding to the rectangle
254    pub fn after_padding(self, padding: Rectangle) -> Rectangle {
255        Rectangle {
256            left: self.left + padding.left,
257            top: self.top + padding.top,
258            right: self.right - padding.right,
259            bottom: self.bottom - padding.bottom,
260        }
261    }
262
263    /// Return the smallest rectangle that covers both `self` and `other`
264    pub fn union(self, other: Rectangle) -> Rectangle {
265        Rectangle {
266            left: self.left.min(other.left),
267            right: self.right.max(other.right),
268            top: self.top.min(other.top),
269            bottom: self.bottom.max(other.bottom),
270        }
271    }
272}
273
274impl From<[f32; 4]> for Rectangle {
275    fn from(a: [f32; 4]) -> Rectangle {
276        Rectangle {
277            left: a[0],
278            top: a[1],
279            right: a[2],
280            bottom: a[3],
281        }
282    }
283}
284
285impl From<f32> for Size {
286    fn from(value: f32) -> Size {
287        Size::Exact(value)
288    }
289}