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}