emath/
align.rs

1//! One- and two-dimensional alignment ([`Align::Center`], [`Align2::LEFT_TOP`] etc).
2
3use crate::{pos2, vec2, Pos2, Rangef, Rect, 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 a range of given size within a specified range.
54    ///
55    /// If the requested `size` is bigger than the size of `range`, then the returned
56    /// range will not fit into the available `range`. The extra space will be allocated
57    /// from:
58    ///
59    /// |Align |Side        |
60    /// |------|------------|
61    /// |Min   |right (end) |
62    /// |Center|both        |
63    /// |Max   |left (start)|
64    ///
65    /// # Examples
66    /// ```
67    /// use std::f32::{INFINITY, NEG_INFINITY};
68    /// use emath::Align::*;
69    ///
70    /// // The size is smaller than a range
71    /// assert_eq!(Min   .align_size_within_range(2.0, 10.0..=20.0), 10.0..=12.0);
72    /// assert_eq!(Center.align_size_within_range(2.0, 10.0..=20.0), 14.0..=16.0);
73    /// assert_eq!(Max   .align_size_within_range(2.0, 10.0..=20.0), 18.0..=20.0);
74    ///
75    /// // The size is bigger than a range
76    /// assert_eq!(Min   .align_size_within_range(20.0, 10.0..=20.0), 10.0..=30.0);
77    /// assert_eq!(Center.align_size_within_range(20.0, 10.0..=20.0),  5.0..=25.0);
78    /// assert_eq!(Max   .align_size_within_range(20.0, 10.0..=20.0),  0.0..=20.0);
79    ///
80    /// // The size is infinity, but range is finite - a special case of a previous example
81    /// assert_eq!(Min   .align_size_within_range(INFINITY, 10.0..=20.0),         10.0..=INFINITY);
82    /// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=INFINITY);
83    /// assert_eq!(Max   .align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=20.0);
84    /// ```
85    ///
86    /// The infinity-sized ranges can produce a surprising results, if the size is also infinity,
87    /// use such ranges with carefully!
88    ///
89    /// ```
90    /// use std::f32::{INFINITY, NEG_INFINITY};
91    /// use emath::Align::*;
92    ///
93    /// // Allocating a size aligned for infinity bound will lead to empty ranges!
94    /// assert_eq!(Min   .align_size_within_range(2.0, 10.0..=INFINITY),     10.0..=12.0);
95    /// assert_eq!(Center.align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
96    /// assert_eq!(Max   .align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
97    ///
98    /// assert_eq!(Min   .align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
99    /// assert_eq!(Center.align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
100    /// assert_eq!(Max   .align_size_within_range(2.0, NEG_INFINITY..=20.0),         18.0..=20.0);
101    ///
102    ///
103    /// // The infinity size will always return the given range if it has at least one infinity bound
104    /// assert_eq!(Min   .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
105    /// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
106    /// assert_eq!(Max   .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
107    ///
108    /// assert_eq!(Min   .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
109    /// assert_eq!(Center.align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
110    /// assert_eq!(Max   .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
111    /// ```
112    #[inline]
113    pub fn align_size_within_range(self, size: f32, range: impl Into<Rangef>) -> Rangef {
114        let range = range.into();
115        let Rangef { min, max } = range;
116
117        if max - min == f32::INFINITY && size == f32::INFINITY {
118            return range;
119        }
120
121        match self {
122            Self::Min => Rangef::new(min, min + size),
123            Self::Center => {
124                if size == f32::INFINITY {
125                    Rangef::new(f32::NEG_INFINITY, f32::INFINITY)
126                } else {
127                    let left = (min + max) / 2.0 - size / 2.0;
128                    Rangef::new(left, left + size)
129                }
130            }
131            Self::Max => Rangef::new(max - size, max),
132        }
133    }
134}
135
136// ----------------------------------------------------------------------------
137
138/// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`].
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
140#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
141pub struct Align2(pub [Align; 2]);
142
143impl Align2 {
144    pub const LEFT_BOTTOM: Self = Self([Align::Min, Align::Max]);
145    pub const LEFT_CENTER: Self = Self([Align::Min, Align::Center]);
146    pub const LEFT_TOP: Self = Self([Align::Min, Align::Min]);
147    pub const CENTER_BOTTOM: Self = Self([Align::Center, Align::Max]);
148    pub const CENTER_CENTER: Self = Self([Align::Center, Align::Center]);
149    pub const CENTER_TOP: Self = Self([Align::Center, Align::Min]);
150    pub const RIGHT_BOTTOM: Self = Self([Align::Max, Align::Max]);
151    pub const RIGHT_CENTER: Self = Self([Align::Max, Align::Center]);
152    pub const RIGHT_TOP: Self = Self([Align::Max, Align::Min]);
153}
154
155impl Align2 {
156    /// Returns an alignment by the X (horizontal) axis
157    #[inline(always)]
158    pub fn x(self) -> Align {
159        self.0[0]
160    }
161
162    /// Returns an alignment by the Y (vertical) axis
163    #[inline(always)]
164    pub fn y(self) -> Align {
165        self.0[1]
166    }
167
168    /// -1, 0, or +1 for each axis
169    pub fn to_sign(self) -> Vec2 {
170        vec2(self.x().to_sign(), self.y().to_sign())
171    }
172
173    /// Used e.g. to anchor a piece of text to a part of the rectangle.
174    /// Give a position within the rect, specified by the aligns
175    pub fn anchor_rect(self, rect: Rect) -> Rect {
176        let x = match self.x() {
177            Align::Min => rect.left(),
178            Align::Center => rect.left() - 0.5 * rect.width(),
179            Align::Max => rect.left() - rect.width(),
180        };
181        let y = match self.y() {
182            Align::Min => rect.top(),
183            Align::Center => rect.top() - 0.5 * rect.height(),
184            Align::Max => rect.top() - rect.height(),
185        };
186        Rect::from_min_size(pos2(x, y), rect.size())
187    }
188
189    /// Use this anchor to position something around `pos`,
190    /// e.g. [`Self::RIGHT_TOP`] means the right-top of the rect
191    /// will end up at `pos`.
192    pub fn anchor_size(self, pos: Pos2, size: Vec2) -> Rect {
193        let x = match self.x() {
194            Align::Min => pos.x,
195            Align::Center => pos.x - 0.5 * size.x,
196            Align::Max => pos.x - size.x,
197        };
198        let y = match self.y() {
199            Align::Min => pos.y,
200            Align::Center => pos.y - 0.5 * size.y,
201            Align::Max => pos.y - size.y,
202        };
203        Rect::from_min_size(pos2(x, y), size)
204    }
205
206    /// e.g. center a size within a given frame
207    pub fn align_size_within_rect(self, size: Vec2, frame: Rect) -> Rect {
208        let x_range = self.x().align_size_within_range(size.x, frame.x_range());
209        let y_range = self.y().align_size_within_range(size.y, frame.y_range());
210        Rect::from_x_y_ranges(x_range, y_range)
211    }
212
213    /// Returns the point on the rect's frame or in the center of a rect according
214    /// to the alignments of this object.
215    ///
216    /// ```text
217    /// (*)-----------+------(*)------+-----------(*)--> X
218    ///  |            |               |            |
219    ///  |  Min, Min  |  Center, Min  |  Max, Min  |
220    ///  |            |               |            |
221    ///  +------------+---------------+------------+
222    ///  |            |               |            |
223    /// (*)Min, Center|Center(*)Center|Max, Center(*)
224    ///  |            |               |            |
225    ///  +------------+---------------+------------+
226    ///  |            |               |            |
227    ///  |  Min, Max  | Center, Max   |  Max, Max  |
228    ///  |            |               |            |
229    /// (*)-----------+------(*)------+-----------(*)
230    ///  |
231    ///  Y
232    /// ```
233    pub fn pos_in_rect(self, frame: &Rect) -> Pos2 {
234        let x = match self.x() {
235            Align::Min => frame.left(),
236            Align::Center => frame.center().x,
237            Align::Max => frame.right(),
238        };
239        let y = match self.y() {
240            Align::Min => frame.top(),
241            Align::Center => frame.center().y,
242            Align::Max => frame.bottom(),
243        };
244
245        pos2(x, y)
246    }
247}
248
249impl std::ops::Index<usize> for Align2 {
250    type Output = Align;
251
252    #[inline(always)]
253    fn index(&self, index: usize) -> &Align {
254        &self.0[index]
255    }
256}
257
258impl std::ops::IndexMut<usize> for Align2 {
259    #[inline(always)]
260    fn index_mut(&mut self, index: usize) -> &mut Align {
261        &mut self.0[index]
262    }
263}
264
265/// Allocates a rectangle of the specified `size` inside the `frame` rectangle
266/// around of its center.
267///
268/// If `size` is bigger than the `frame`s size the returned rect will bounce out
269/// of the `frame`.
270pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
271    Align2::CENTER_CENTER.align_size_within_rect(size, frame)
272}