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}