Skip to main content

incular_config/
alignment.rs

1use incular_core::{Offset, Size};
2
3/// Direction in which a linear layout consumes its main axis.
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
5pub enum Axis {
6    #[default]
7    Horizontal,
8    Vertical,
9}
10
11impl Axis {
12    #[must_use]
13    pub const fn is_horizontal(self) -> bool {
14        matches!(self, Self::Horizontal)
15    }
16
17    #[must_use]
18    pub const fn is_vertical(self) -> bool {
19        matches!(self, Self::Vertical)
20    }
21
22    #[must_use]
23    pub const fn main_extent(self, size: Size) -> f32 {
24        match self {
25            Self::Horizontal => size.width,
26            Self::Vertical => size.height,
27        }
28    }
29
30    #[must_use]
31    pub const fn cross_extent(self, size: Size) -> f32 {
32        match self {
33            Self::Horizontal => size.height,
34            Self::Vertical => size.width,
35        }
36    }
37
38    #[must_use]
39    pub fn size(self, main: f32, cross: f32) -> Size {
40        match self {
41            Self::Horizontal => Size::new(main, cross),
42            Self::Vertical => Size::new(cross, main),
43        }
44    }
45
46    #[must_use]
47    pub const fn offset(self, main: f32, cross: f32) -> Offset {
48        match self {
49            Self::Horizontal => Offset::new(main, cross),
50            Self::Vertical => Offset::new(cross, main),
51        }
52    }
53}
54
55/// Physical direction used by directional layout policies.
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
57pub enum AxisDirection {
58    #[default]
59    Right,
60    Left,
61    Down,
62    Up,
63}
64
65impl AxisDirection {
66    #[must_use]
67    pub const fn axis(self) -> Axis {
68        match self {
69            Self::Right | Self::Left => Axis::Horizontal,
70            Self::Down | Self::Up => Axis::Vertical,
71        }
72    }
73
74    #[must_use]
75    pub const fn is_reversed(self) -> bool {
76        matches!(self, Self::Left | Self::Up)
77    }
78}
79
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
81pub enum TextDirection {
82    #[default]
83    Ltr,
84    Rtl,
85}
86
87#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
88pub enum VerticalDirection {
89    #[default]
90    Down,
91    Up,
92}
93
94/// Alignment expressed as a normalized point in the range `[-1, 1]`.
95/// `(-1, -1)` is top-left, `(0, 0)` is center, and `(1, 1)` is bottom-right.
96#[derive(Clone, Copy, Debug, Default, PartialEq)]
97pub struct Alignment {
98    pub x: f32,
99    pub y: f32,
100}
101
102impl Alignment {
103    pub const TOP_LEFT: Self = Self { x: -1.0, y: -1.0 };
104    pub const TOP_CENTER: Self = Self { x: 0.0, y: -1.0 };
105    pub const TOP_RIGHT: Self = Self { x: 1.0, y: -1.0 };
106    pub const CENTER_LEFT: Self = Self { x: -1.0, y: 0.0 };
107    pub const CENTER: Self = Self { x: 0.0, y: 0.0 };
108    pub const CENTER_RIGHT: Self = Self { x: 1.0, y: 0.0 };
109    pub const BOTTOM_LEFT: Self = Self { x: -1.0, y: 1.0 };
110    pub const BOTTOM_CENTER: Self = Self { x: 0.0, y: 1.0 };
111    pub const BOTTOM_RIGHT: Self = Self { x: 1.0, y: 1.0 };
112
113    #[must_use]
114    pub const fn new(x: f32, y: f32) -> Self {
115        Self { x, y }
116    }
117
118    #[must_use]
119    pub const fn along_size(self, size: Size) -> Offset {
120        Offset::new(size.width * self.x * 0.5, size.height * self.y * 0.5)
121    }
122
123    /// Computes the child origin inside a parent. Values outside `[-1, 1]`
124    /// intentionally permit overflow, matching Flutter's `Alignment`.
125    #[must_use]
126    pub const fn within(self, parent: Size, child: Size) -> Offset {
127        Offset::new(
128            (parent.width - child.width) * (self.x + 1.0) * 0.5,
129            (parent.height - child.height) * (self.y + 1.0) * 0.5,
130        )
131    }
132
133    #[must_use]
134    pub fn inscribe(self, child: Size, parent: incular_core::Rect) -> incular_core::Rect {
135        incular_core::Rect::from_origin_size(parent.origin + self.within(parent.size, child), child)
136    }
137
138    #[must_use]
139    pub const fn resolve(self, _direction: TextDirection) -> Self {
140        self
141    }
142}
143
144impl std::ops::Add for Alignment {
145    type Output = Self;
146
147    fn add(self, rhs: Self) -> Self::Output {
148        Self::new(self.x + rhs.x, self.y + rhs.y)
149    }
150}
151
152impl std::ops::Sub for Alignment {
153    type Output = Self;
154
155    fn sub(self, rhs: Self) -> Self::Output {
156        Self::new(self.x - rhs.x, self.y - rhs.y)
157    }
158}
159
160impl std::ops::Mul<f32> for Alignment {
161    type Output = Self;
162
163    fn mul(self, rhs: f32) -> Self::Output {
164        Self::new(self.x * rhs, self.y * rhs)
165    }
166}
167
168impl std::ops::Div<f32> for Alignment {
169    type Output = Self;
170
171    fn div(self, rhs: f32) -> Self::Output {
172        Self::new(self.x / rhs, self.y / rhs)
173    }
174}
175
176impl std::ops::Neg for Alignment {
177    type Output = Self;
178
179    fn neg(self) -> Self::Output {
180        Self::new(-self.x, -self.y)
181    }
182}
183
184impl incular_core::Lerp for Alignment {
185    fn lerp(&self, other: &Self, t: f32) -> Self {
186        let t = t.clamp(0.0, 1.0);
187        let mix = |a: f32, b: f32| a + (b - a) * t;
188        Self::new(mix(self.x, other.x), mix(self.y, other.y))
189    }
190}
191
192/// An offset that's expressed as a fraction of a `[0, 1]` bounding box.
193#[derive(Clone, Copy, Debug, Default, PartialEq)]
194pub struct FractionalOffset {
195    pub dx: f32,
196    pub dy: f32,
197}
198
199impl FractionalOffset {
200    pub const TOP_LEFT: Self = Self { dx: 0.0, dy: 0.0 };
201    pub const TOP_CENTER: Self = Self { dx: 0.5, dy: 0.0 };
202    pub const TOP_RIGHT: Self = Self { dx: 1.0, dy: 0.0 };
203    pub const CENTER_LEFT: Self = Self { dx: 0.0, dy: 0.5 };
204    pub const CENTER: Self = Self { dx: 0.5, dy: 0.5 };
205    pub const CENTER_RIGHT: Self = Self { dx: 1.0, dy: 0.5 };
206    pub const BOTTOM_LEFT: Self = Self { dx: 0.0, dy: 1.0 };
207    pub const BOTTOM_CENTER: Self = Self { dx: 0.5, dy: 1.0 };
208    pub const BOTTOM_RIGHT: Self = Self { dx: 1.0, dy: 1.0 };
209
210    #[must_use]
211    pub const fn new(dx: f32, dy: f32) -> Self {
212        Self { dx, dy }
213    }
214
215    #[must_use]
216    pub const fn from_alignment(alignment: Alignment) -> Self {
217        Self {
218            dx: (alignment.x + 1.0) / 2.0,
219            dy: (alignment.y + 1.0) / 2.0,
220        }
221    }
222
223    #[must_use]
224    pub const fn to_alignment(self) -> Alignment {
225        Alignment::new(self.dx * 2.0 - 1.0, self.dy * 2.0 - 1.0)
226    }
227}
228
229impl incular_core::Lerp for FractionalOffset {
230    fn lerp(&self, other: &Self, t: f32) -> Self {
231        let t = t.clamp(0.0, 1.0);
232        let mix = |a: f32, b: f32| a + (b - a) * t;
233        Self::new(mix(self.dx, other.dx), mix(self.dy, other.dy))
234    }
235}
236
237/// Alignment whose horizontal value is relative to text direction.
238#[derive(Clone, Copy, Debug, Default, PartialEq)]
239pub struct AlignmentDirectional {
240    pub start: f32,
241    pub y: f32,
242}
243
244impl AlignmentDirectional {
245    pub const TOP_START: Self = Self {
246        start: -1.0,
247        y: -1.0,
248    };
249    pub const TOP_CENTER: Self = Self {
250        start: 0.0,
251        y: -1.0,
252    };
253    pub const TOP_END: Self = Self {
254        start: 1.0,
255        y: -1.0,
256    };
257    pub const CENTER_START: Self = Self {
258        start: -1.0,
259        y: 0.0,
260    };
261    pub const CENTER: Self = Self { start: 0.0, y: 0.0 };
262    pub const CENTER_END: Self = Self { start: 1.0, y: 0.0 };
263    pub const BOTTOM_START: Self = Self {
264        start: -1.0,
265        y: 1.0,
266    };
267    pub const BOTTOM_CENTER: Self = Self { start: 0.0, y: 1.0 };
268    pub const BOTTOM_END: Self = Self { start: 1.0, y: 1.0 };
269
270    #[must_use]
271    pub const fn new(start: f32, y: f32) -> Self {
272        Self { start, y }
273    }
274
275    #[must_use]
276    pub const fn resolve(self, direction: TextDirection) -> Alignment {
277        Alignment::new(
278            match direction {
279                TextDirection::Ltr => self.start,
280                TextDirection::Rtl => -self.start,
281            },
282            self.y,
283        )
284    }
285}
286
287impl incular_core::Lerp for AlignmentDirectional {
288    fn lerp(&self, other: &Self, t: f32) -> Self {
289        let t = t.clamp(0.0, 1.0);
290        let mix = |a: f32, b: f32| a + (b - a) * t;
291        Self::new(mix(self.start, other.start), mix(self.y, other.y))
292    }
293}
294
295#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
296pub enum MainAxisSize {
297    Min,
298    #[default]
299    Max,
300}
301
302#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
303pub enum MainAxisAlignment {
304    #[default]
305    Start,
306    End,
307    Center,
308    SpaceBetween,
309    SpaceAround,
310    SpaceEvenly,
311}
312
313impl MainAxisAlignment {
314    #[must_use]
315    pub const fn is_distributed(self) -> bool {
316        matches!(
317            self,
318            Self::SpaceBetween | Self::SpaceAround | Self::SpaceEvenly
319        )
320    }
321}
322
323#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
324pub enum CrossAxisAlignment {
325    #[default]
326    Start,
327    End,
328    Center,
329    Stretch,
330    Baseline,
331}
332
333#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
334pub enum FlexFit {
335    #[default]
336    Tight,
337    Loose,
338}
339
340#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
341pub enum WrapAlignment {
342    #[default]
343    Start,
344    End,
345    Center,
346    SpaceBetween,
347    SpaceAround,
348    SpaceEvenly,
349}
350
351#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
352pub enum WrapCrossAlignment {
353    #[default]
354    Start,
355    End,
356    Center,
357}
358
359#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
360pub enum Clip {
361    None,
362    #[default]
363    HardEdge,
364    AntiAlias,
365    AntiAliasWithSaveLayer,
366}
367
368#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
369pub enum StackFit {
370    #[default]
371    Loose,
372    Expand,
373    Passthrough,
374}