emath/align.rs
1//! One- and two-dimensional alignment ([`Align::Center`], [`Align2::LEFT_TOP`] etc).
2
3use crate::{Pos2, Rangef, Rect, Vec2, pos2, vec2};
4
5/// left/center/right or top/center/bottom alignment for e.g. anchors and layouts.
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
8pub enum Align {
9 /// Left or top.
10 #[default]
11 Min,
12
13 /// Horizontal or vertical center.
14 Center,
15
16 /// Right or bottom.
17 Max,
18}
19
20impl Align {
21 /// Convenience for [`Self::Min`]
22 pub const LEFT: Self = Self::Min;
23
24 /// Convenience for [`Self::Max`]
25 pub const RIGHT: Self = Self::Max;
26
27 /// Convenience for [`Self::Min`]
28 pub const TOP: Self = Self::Min;
29
30 /// Convenience for [`Self::Max`]
31 pub const BOTTOM: Self = Self::Max;
32
33 /// Convert `Min => 0.0`, `Center => 0.5` or `Max => 1.0`.
34 #[inline(always)]
35 pub fn to_factor(self) -> f32 {
36 match self {
37 Self::Min => 0.0,
38 Self::Center => 0.5,
39 Self::Max => 1.0,
40 }
41 }
42
43 /// Convert `Min => -1.0`, `Center => 0.0` or `Max => 1.0`.
44 #[inline(always)]
45 pub fn to_sign(self) -> f32 {
46 match self {
47 Self::Min => -1.0,
48 Self::Center => 0.0,
49 Self::Max => 1.0,
50 }
51 }
52
53 /// Returns the inverse alignment.
54 /// `Min` becomes `Max`, `Center` stays the same, `Max` becomes `Min`.
55 pub fn flip(self) -> Self {
56 match self {
57 Self::Min => Self::Max,
58 Self::Center => Self::Center,
59 Self::Max => Self::Min,
60 }
61 }
62
63 /// Returns a range of given size within a specified range.
64 ///
65 /// If the requested `size` is bigger than the size of `range`, then the returned
66 /// range will not fit into the available `range`. The extra space will be allocated
67 /// from:
68 ///
69 /// |Align |Side |
70 /// |------|------------|
71 /// |Min |right (end) |
72 /// |Center|both |
73 /// |Max |left (start)|
74 ///
75 /// # Examples
76 /// ```
77 /// use std::f32::{INFINITY, NEG_INFINITY};
78 /// use emath::Align::*;
79 ///
80 /// // The size is smaller than a range
81 /// assert_eq!(Min .align_size_within_range(2.0, 10.0..=20.0), 10.0..=12.0);
82 /// assert_eq!(Center.align_size_within_range(2.0, 10.0..=20.0), 14.0..=16.0);
83 /// assert_eq!(Max .align_size_within_range(2.0, 10.0..=20.0), 18.0..=20.0);
84 ///
85 /// // The size is bigger than a range
86 /// assert_eq!(Min .align_size_within_range(20.0, 10.0..=20.0), 10.0..=30.0);
87 /// assert_eq!(Center.align_size_within_range(20.0, 10.0..=20.0), 5.0..=25.0);
88 /// assert_eq!(Max .align_size_within_range(20.0, 10.0..=20.0), 0.0..=20.0);
89 ///
90 /// // The size is infinity, but range is finite - a special case of a previous example
91 /// assert_eq!(Min .align_size_within_range(INFINITY, 10.0..=20.0), 10.0..=INFINITY);
92 /// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=INFINITY);
93 /// assert_eq!(Max .align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=20.0);
94 /// ```
95 ///
96 /// The infinity-sized ranges can produce a surprising results, if the size is also infinity,
97 /// use such ranges with carefully!
98 ///
99 /// ```
100 /// use std::f32::{INFINITY, NEG_INFINITY};
101 /// use emath::Align::*;
102 ///
103 /// // Allocating a size aligned for infinity bound will lead to empty ranges!
104 /// assert_eq!(Min .align_size_within_range(2.0, 10.0..=INFINITY), 10.0..=12.0);
105 /// assert_eq!(Center.align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
106 /// assert_eq!(Max .align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
107 ///
108 /// assert_eq!(Min .align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
109 /// assert_eq!(Center.align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
110 /// assert_eq!(Max .align_size_within_range(2.0, NEG_INFINITY..=20.0), 18.0..=20.0);
111 ///
112 ///
113 /// // The infinity size will always return the given range if it has at least one infinity bound
114 /// assert_eq!(Min .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
115 /// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
116 /// assert_eq!(Max .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
117 ///
118 /// assert_eq!(Min .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
119 /// assert_eq!(Center.align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
120 /// assert_eq!(Max .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
121 /// ```
122 #[inline]
123 pub fn align_size_within_range(self, size: f32, range: impl Into<Rangef>) -> Rangef {
124 let range = range.into();
125 let Rangef { min, max } = range;
126
127 if max - min == f32::INFINITY && size == f32::INFINITY {
128 return range;
129 }
130
131 match self {
132 Self::Min => Rangef::new(min, min + size),
133 Self::Center => {
134 if size == f32::INFINITY {
135 Rangef::new(f32::NEG_INFINITY, f32::INFINITY)
136 } else {
137 let left = (min + max) / 2.0 - size / 2.0;
138 Rangef::new(left, left + size)
139 }
140 }
141 Self::Max => Rangef::new(max - size, max),
142 }
143 }
144}
145
146// ----------------------------------------------------------------------------
147
148/// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`].
149#[derive(Clone, Copy, PartialEq, Eq, Hash)]
150#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
151pub struct Align2(pub [Align; 2]);
152
153impl Align2 {
154 pub const LEFT_BOTTOM: Self = Self([Align::Min, Align::Max]);
155 pub const LEFT_CENTER: Self = Self([Align::Min, Align::Center]);
156 pub const LEFT_TOP: Self = Self([Align::Min, Align::Min]);
157 pub const CENTER_BOTTOM: Self = Self([Align::Center, Align::Max]);
158 pub const CENTER_CENTER: Self = Self([Align::Center, Align::Center]);
159 pub const CENTER_TOP: Self = Self([Align::Center, Align::Min]);
160 pub const RIGHT_BOTTOM: Self = Self([Align::Max, Align::Max]);
161 pub const RIGHT_CENTER: Self = Self([Align::Max, Align::Center]);
162 pub const RIGHT_TOP: Self = Self([Align::Max, Align::Min]);
163}
164
165impl Align2 {
166 /// Returns an alignment by the X (horizontal) axis
167 #[inline(always)]
168 pub fn x(self) -> Align {
169 self.0[0]
170 }
171
172 /// Returns an alignment by the Y (vertical) axis
173 #[inline(always)]
174 pub fn y(self) -> Align {
175 self.0[1]
176 }
177
178 /// -1, 0, or +1 for each axis
179 pub fn to_sign(self) -> Vec2 {
180 vec2(self.x().to_sign(), self.y().to_sign())
181 }
182
183 /// Flip on the x-axis
184 /// e.g. `TOP_LEFT` -> `TOP_RIGHT`
185 pub fn flip_x(self) -> Self {
186 Self([self.x().flip(), self.y()])
187 }
188
189 /// Flip on the y-axis
190 /// e.g. `TOP_LEFT` -> `BOTTOM_LEFT`
191 pub fn flip_y(self) -> Self {
192 Self([self.x(), self.y().flip()])
193 }
194
195 /// Flip on both axes
196 /// e.g. `TOP_LEFT` -> `BOTTOM_RIGHT`
197 pub fn flip(self) -> Self {
198 Self([self.x().flip(), self.y().flip()])
199 }
200
201 /// Used e.g. to anchor a piece of text to a part of the rectangle.
202 /// Give a position within the rect, specified by the aligns
203 pub fn anchor_rect(self, rect: Rect) -> Rect {
204 let x = match self.x() {
205 Align::Min => rect.left(),
206 Align::Center => rect.left() - 0.5 * rect.width(),
207 Align::Max => rect.left() - rect.width(),
208 };
209 let y = match self.y() {
210 Align::Min => rect.top(),
211 Align::Center => rect.top() - 0.5 * rect.height(),
212 Align::Max => rect.top() - rect.height(),
213 };
214 Rect::from_min_size(pos2(x, y), rect.size())
215 }
216
217 /// Use this anchor to position something around `pos`,
218 /// e.g. [`Self::RIGHT_TOP`] means the right-top of the rect
219 /// will end up at `pos`.
220 pub fn anchor_size(self, pos: Pos2, size: Vec2) -> Rect {
221 let x = match self.x() {
222 Align::Min => pos.x,
223 Align::Center => pos.x - 0.5 * size.x,
224 Align::Max => pos.x - size.x,
225 };
226 let y = match self.y() {
227 Align::Min => pos.y,
228 Align::Center => pos.y - 0.5 * size.y,
229 Align::Max => pos.y - size.y,
230 };
231 Rect::from_min_size(pos2(x, y), size)
232 }
233
234 /// e.g. center a size within a given frame
235 pub fn align_size_within_rect(self, size: Vec2, frame: Rect) -> Rect {
236 let x_range = self.x().align_size_within_range(size.x, frame.x_range());
237 let y_range = self.y().align_size_within_range(size.y, frame.y_range());
238 Rect::from_x_y_ranges(x_range, y_range)
239 }
240
241 /// Returns the point on the rect's frame or in the center of a rect according
242 /// to the alignments of this object.
243 ///
244 /// ```text
245 /// (*)-----------+------(*)------+-----------(*)--> X
246 /// | | | |
247 /// | Min, Min | Center, Min | Max, Min |
248 /// | | | |
249 /// +------------+---------------+------------+
250 /// | | | |
251 /// (*)Min, Center|Center(*)Center|Max, Center(*)
252 /// | | | |
253 /// +------------+---------------+------------+
254 /// | | | |
255 /// | Min, Max | Center, Max | Max, Max |
256 /// | | | |
257 /// (*)-----------+------(*)------+-----------(*)
258 /// |
259 /// Y
260 /// ```
261 pub fn pos_in_rect(self, frame: &Rect) -> Pos2 {
262 let x = match self.x() {
263 Align::Min => frame.left(),
264 Align::Center => frame.center().x,
265 Align::Max => frame.right(),
266 };
267 let y = match self.y() {
268 Align::Min => frame.top(),
269 Align::Center => frame.center().y,
270 Align::Max => frame.bottom(),
271 };
272
273 pos2(x, y)
274 }
275}
276
277impl std::ops::Index<usize> for Align2 {
278 type Output = Align;
279
280 #[inline(always)]
281 fn index(&self, index: usize) -> &Align {
282 &self.0[index]
283 }
284}
285
286impl std::ops::IndexMut<usize> for Align2 {
287 #[inline(always)]
288 fn index_mut(&mut self, index: usize) -> &mut Align {
289 &mut self.0[index]
290 }
291}
292
293/// Allocates a rectangle of the specified `size` inside the `frame` rectangle
294/// around of its center.
295///
296/// If `size` is bigger than the `frame`s size the returned rect will bounce out
297/// of the `frame`.
298pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
299 Align2::CENTER_CENTER.align_size_within_rect(size, frame)
300}
301
302impl std::fmt::Debug for Align2 {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 write!(f, "Align2({:?}, {:?})", self.x(), self.y())
305 }
306}