zng_layout/unit/
alignment.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Write},
4    ops,
5};
6
7use crate::context::LayoutDirection;
8use zng_var::{
9    animation::{Transitionable, easing::EasingStep},
10    impl_from_and_into_var,
11};
12
13use super::{Factor, Factor2d, FactorPercent, FactorUnits, Point, Px, PxConstraints, PxConstraints2d, PxSize, PxVector};
14
15/// `x` and `y` alignment.
16///
17/// The values indicate how much to the right and bottom the content is moved within
18/// a larger available space. An `x` value of `0.0` means the content left border touches
19/// the container left border, a value of `1.0` means the content right border touches the
20/// container right border.
21///
22/// There is a constant for each of the usual alignment values, the alignment is defined as two factors like this
23/// primarily for animating transition between alignments.
24///
25/// Values outside of the `[0.0..=1.0]` range places the content outside of the container bounds.
26///
27/// ## Special Values
28///
29/// The [`f32::INFINITY`] value can be used in ***x*** or ***y*** to indicate that the content must *fill* the available space.
30///
31/// The [`f32::NEG_INFINITY`] value can be used in ***y*** to indicate that a panel widget must align its items by each *baseline*,
32/// for most widgets this is the same as `BOTTOM`, but for texts this aligns to the baseline of the texts (bottom + baseline).
33///
34/// You can use the [`is_fill_x`], [`is_fill_y`] and [`is_baseline`] methods to probe for these special values.
35///
36/// ## Right-to-Left
37///
38/// The `x` alignment can be flagged as `x_rtl_aware`, in widgets that implement right-to-left the `x` value is flipped around `0.5.fct()`.
39/// The named `const` values that contain `START` and `END` are `x_rtl_aware`, the others are not. The `x_rtl_aware` flag is sticky, all
40/// arithmetic operations between aligns output an `x_rtl_aware` align if any of the inputs is flagged. The flag is only resolved explicitly,
41/// arithmetic operations apply on the
42///
43/// [`is_fill_x`]: Align::is_fill_x
44/// [`is_fill_y`]: Align::is_fill_y
45/// [`is_baseline`]: Align::is_baseline
46#[derive(Clone, Copy)]
47pub struct Align {
48    /// *x* alignment in a `[0.0..=1.0]` range.
49    pub x: Factor,
50    /// If `x` is flipped (around `0.5`) in right-to-left contexts.
51    pub x_rtl_aware: bool,
52
53    /// *y* alignment in a `[0.0..=1.0]` range.
54    pub y: Factor,
55}
56impl PartialEq for Align {
57    fn eq(&self, other: &Self) -> bool {
58        self.is_fill_x() == other.is_fill_x() && self.is_fill_y() == other.is_fill_y() && self.x == other.x && self.y == other.y
59    }
60}
61impl Default for Align {
62    /// [`Align::START`].
63    fn default() -> Self {
64        Align::START
65    }
66}
67impl Align {
68    /// Gets the best finite [`x`] align value.
69    ///
70    /// Replaces `FILL` with `START`, flips `x` for right-to-left if applicable.
71    ///
72    /// [`x`]: Self::x
73    pub fn x(self, direction: LayoutDirection) -> Factor {
74        let x = if self.x.0.is_finite() { self.x } else { 0.fct() };
75
76        if self.x_rtl_aware && direction.is_rtl() { x.flip() } else { x }
77    }
78
79    /// Gets the best finite [`y`] align value.
80    ///
81    /// Returns `1.fct()` for [`is_baseline`], implementers must add the baseline offset to that.
82    ///
83    /// [`y`]: Self::y
84    /// [`is_baseline`]: Self::is_baseline
85    pub fn y(self) -> Factor {
86        if self.y.0.is_finite() {
87            self.y
88        } else if self.is_baseline() {
89            1.fct()
90        } else {
91            0.fct()
92        }
93    }
94
95    /// Gets the best finite [`x`] and [`y`] align values.
96    ///
97    /// [`x`]: fn@Self::x
98    /// [`y`]: fn@Self::y
99    pub fn xy(self, direction: LayoutDirection) -> Factor2d {
100        Factor2d::new(self.x(direction), self.y())
101    }
102
103    /// Returns `true` if [`x`] is a special value that indicates the content width must be the container width.
104    ///
105    /// [`x`]: Align::x
106    pub fn is_fill_x(self) -> bool {
107        self.x.0.is_infinite() && self.x.0.is_sign_positive()
108    }
109
110    /// Returns `true` if [`y`] is a special value that indicates the content height must be the container height.
111    ///
112    /// [`y`]: Align::y
113    pub fn is_fill_y(self) -> bool {
114        self.y.0.is_infinite() && self.y.0.is_sign_positive()
115    }
116
117    /// Returns `true` if [`y`] is a special value that indicates the contents must be aligned by their baseline.
118    ///
119    /// If this is `true` the *y* alignment must be `BOTTOM` plus the baseline offset.
120    ///
121    /// [`y`]: Align::y
122    pub fn is_baseline(self) -> bool {
123        self.y.0.is_infinite() && self.y.0.is_sign_negative()
124    }
125
126    /// Returns a boolean vector of the fill values.
127    pub fn fill_vector(self) -> super::euclid::BoolVector2D {
128        super::euclid::BoolVector2D {
129            x: self.is_fill_x(),
130            y: self.is_fill_y(),
131        }
132    }
133
134    /// Constraints that must be used to layout a child node with the alignment.
135    pub fn child_constraints(self, parent_constraints: PxConstraints2d) -> PxConstraints2d {
136        // FILL is the *default* property value, so it must behave the same way as if the alignment was not applied.
137        parent_constraints
138            .with_new_min(
139                if self.is_fill_x() { parent_constraints.x.min() } else { Px(0) },
140                if self.is_fill_y() { parent_constraints.y.min() } else { Px(0) },
141            )
142            .with_fill_and(self.is_fill_x(), self.is_fill_y())
143    }
144
145    /// Compute the offset for a given child size, parent size and layout direction.
146    ///
147    /// Note that this does not flag baseline offset, you can use [`Align::layout`] to cover all corner cases.
148    pub fn child_offset(self, child_size: PxSize, parent_size: PxSize, direction: LayoutDirection) -> PxVector {
149        let mut offset = PxVector::zero();
150        if !self.is_fill_x() {
151            let x = if self.x_rtl_aware && direction.is_rtl() {
152                self.x.flip().0
153            } else {
154                self.x.0
155            };
156
157            offset.x = (parent_size.width - child_size.width) * x;
158        }
159
160        let baseline = self.is_baseline();
161
162        if !self.is_fill_y() {
163            let y = if baseline { 1.0 } else { self.y.0 };
164
165            offset.y = (parent_size.height - child_size.height) * y;
166        }
167        offset
168    }
169
170    /// Computes the size returned by [`layout`] for the given child size and constraints.
171    ///
172    /// [`layout`]: Self::layout
173    pub fn measure(self, child_size: PxSize, parent_constraints: PxConstraints2d) -> PxSize {
174        let size = parent_constraints.fill_size().max(child_size);
175        parent_constraints.clamp_size(size)
176    }
177
178    /// Computes the width returned by layout for the given child width and ***x*** constraints.
179    pub fn measure_x(self, child_width: Px, parent_constraints_x: PxConstraints) -> Px {
180        let width = parent_constraints_x.fill().max(child_width);
181        parent_constraints_x.clamp(width)
182    }
183
184    /// Computes the height returned by layout for the given child height and ***y*** constraints.
185    pub fn measure_y(self, child_height: Px, parent_constraints_y: PxConstraints) -> Px {
186        let height = parent_constraints_y.fill().max(child_height);
187        parent_constraints_y.clamp(height)
188    }
189
190    /// Applies the alignment transform to `wl` and returns the size of the parent align node, the translate offset and if
191    /// baseline must be translated.
192    pub fn layout(self, child_size: PxSize, parent_constraints: PxConstraints2d, direction: LayoutDirection) -> (PxSize, PxVector, bool) {
193        let size = parent_constraints.fill_size().max(child_size);
194        let size = parent_constraints.clamp_size(size);
195
196        let offset = self.child_offset(child_size, size, direction);
197
198        (size, offset, self.is_baseline())
199    }
200}
201impl_from_and_into_var! {
202    fn from<X: Into<Factor>, Y: Into<Factor>>((x, y): (X, Y)) -> Align {
203        Align {
204            x: x.into(),
205            x_rtl_aware: false,
206            y: y.into(),
207        }
208    }
209
210    fn from<X: Into<Factor>, Y: Into<Factor>>((x, rtl, y): (X, bool, Y)) -> Align {
211        Align {
212            x: x.into(),
213            x_rtl_aware: rtl,
214            y: y.into(),
215        }
216    }
217
218    fn from(xy: Factor) -> Align {
219        Align {
220            x: xy,
221            x_rtl_aware: false,
222            y: xy,
223        }
224    }
225
226    fn from(xy: FactorPercent) -> Align {
227        xy.fct().into()
228    }
229}
230macro_rules! named_aligns {
231    ( $($NAME:ident = ($x:expr, $rtl:expr, $y:expr);)+ ) => {named_aligns!{$(
232        [stringify!(($x, $y))] $NAME = ($x, $rtl, $y);
233    )+}};
234
235    ( $([$doc:expr] $NAME:ident = ($x:expr, $rtl:expr, $y:expr);)+ ) => {
236        $(
237        #[doc=$doc]
238        pub const $NAME: Align = Align { x: Factor($x), x_rtl_aware: $rtl, y: Factor($y) };
239        )+
240
241        /// Returns the alignment `const` name if `self` is equal to one of then.
242        pub fn name(self) -> Option<&'static str> {
243            $(
244                if self == Self::$NAME {
245                    Some(stringify!($NAME))
246                }
247            )else+
248            else {
249                None
250            }
251        }
252
253        /// Returns the named alignment.
254        pub fn from_name(name: &str) -> Option<Self> {
255            $(
256                if name == stringify!($NAME) {
257                    Some(Self::$NAME)
258                }
259            )else+
260            else {
261                None
262            }
263        }
264    };
265}
266impl fmt::Debug for Align {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        if let Some(name) = self.name() {
269            if f.alternate() {
270                write!(f, "Align::{name}")
271            } else {
272                f.write_str(name)
273            }
274        } else {
275            f.debug_struct("Align")
276                .field("x", &self.x)
277                .field("x_rtl_aware", &self.x_rtl_aware)
278                .field("y", &self.y)
279                .finish()
280        }
281    }
282}
283impl fmt::Display for Align {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        if let Some(name) = self.name() {
286            f.write_str(name)
287        } else {
288            f.write_char('(')?;
289            if self.is_fill_x() {
290                f.write_str("<fill>")?;
291            } else {
292                write!(f, "{}", FactorPercent::from(self.x))?;
293            }
294            f.write_str(", ")?;
295            if self.is_fill_y() {
296                f.write_str("<fill>")?;
297            } else if self.is_baseline() {
298                f.write_str("<baseline>")?;
299            } else {
300                write!(f, "{}", FactorPercent::from(self.x))?;
301            }
302            f.write_char(')')
303        }
304    }
305}
306impl Align {
307    named_aligns! {
308        TOP_START = (0.0, true, 0.0);
309        TOP_LEFT = (0.0, false, 0.0);
310        BOTTOM_START = (0.0, true, 1.0);
311        BOTTOM_LEFT = (0.0, false, 1.0);
312
313        TOP_END = (1.0, true, 0.0);
314        TOP_RIGHT = (1.0, false, 0.0);
315        BOTTOM_END = (1.0, true, 1.0);
316        BOTTOM_RIGHT = (1.0, false, 1.0);
317
318        START = (0.0, true, 0.5);
319        LEFT = (0.0, false, 0.5);
320        END = (1.0, true, 0.5);
321        RIGHT = (1.0, false, 0.5);
322        TOP = (0.5, false, 0.0);
323        BOTTOM = (0.5, false, 1.0);
324
325        CENTER = (0.5, false, 0.5);
326
327        FILL_TOP = (f32::INFINITY, false, 0.0);
328        FILL_BOTTOM = (f32::INFINITY, false, 1.0);
329        FILL_START = (0.0, true, f32::INFINITY);
330        FILL_LEFT = (0.0, false, f32::INFINITY);
331        FILL_RIGHT = (1.0, false, f32::INFINITY);
332        FILL_END = (1.0, true, f32::INFINITY);
333
334        FILL_X = (f32::INFINITY, false, 0.5);
335        FILL_Y = (0.5, false, f32::INFINITY);
336
337        FILL = (f32::INFINITY, false, f32::INFINITY);
338
339        BASELINE_START = (0.0, true, f32::NEG_INFINITY);
340        BASELINE_LEFT = (0.0, false, f32::NEG_INFINITY);
341        BASELINE_CENTER = (0.5, false, f32::NEG_INFINITY);
342        BASELINE_END = (1.0, true, f32::NEG_INFINITY);
343        BASELINE_RIGHT = (1.0, false, f32::NEG_INFINITY);
344
345        BASELINE = (f32::INFINITY, false, f32::NEG_INFINITY);
346    }
347}
348impl_from_and_into_var! {
349    /// To relative length x and y.
350    fn from(alignment: Align) -> Point {
351        Point {
352            x: alignment.x.into(),
353            y: alignment.y.into(),
354        }
355    }
356
357    fn from(factor2d: Factor2d) -> Align {
358        Align {
359            x: factor2d.x,
360            x_rtl_aware: false,
361            y: factor2d.y,
362        }
363    }
364}
365
366impl Transitionable for Align {
367    fn lerp(mut self, to: &Self, step: EasingStep) -> Self {
368        let end = step >= 1.fct();
369
370        if end {
371            self.x_rtl_aware = to.x_rtl_aware;
372        }
373
374        if self.x.0.is_finite() && self.y.0.is_finite() {
375            self.x = self.x.lerp(&to.x, step);
376        } else if end {
377            self.x = to.x;
378        }
379
380        if self.y.0.is_finite() && self.y.0.is_finite() {
381            self.y = self.y.lerp(&to.y, step);
382        } else if end {
383            self.y = to.y;
384        }
385
386        self
387    }
388}
389
390impl<S: Into<Factor2d>> ops::Mul<S> for Align {
391    type Output = Self;
392
393    fn mul(mut self, rhs: S) -> Self {
394        self *= rhs;
395        self
396    }
397}
398impl<S: Into<Factor2d>> ops::MulAssign<S> for Align {
399    fn mul_assign(&mut self, rhs: S) {
400        let rhs = rhs.into();
401
402        if self.x.0.is_finite() {
403            self.x *= rhs.x;
404        } else if rhs.x == 0.fct() {
405            self.x = 0.fct();
406        }
407        if self.y.0.is_finite() {
408            self.y *= rhs.y;
409        } else if rhs.y == 0.fct() {
410            self.y = 0.fct()
411        }
412    }
413}
414impl<S: Into<Factor2d>> ops::Div<S> for Align {
415    type Output = Self;
416
417    fn div(mut self, rhs: S) -> Self {
418        self /= rhs;
419        self
420    }
421}
422impl<S: Into<Factor2d>> ops::DivAssign<S> for Align {
423    fn div_assign(&mut self, rhs: S) {
424        let rhs = rhs.into();
425
426        if self.x.0.is_finite() {
427            self.x /= rhs.x;
428        }
429        if self.y.0.is_finite() {
430            self.y /= rhs.y;
431        }
432    }
433}
434
435#[derive(serde::Serialize, serde::Deserialize)]
436#[serde(untagged)]
437enum AlignSerde<'s> {
438    Named(Cow<'s, str>),
439    Unnamed { x: Factor, x_rtl_aware: bool, y: Factor },
440}
441impl serde::Serialize for Align {
442    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
443    where
444        S: serde::Serializer,
445    {
446        if serializer.is_human_readable() {
447            if let Some(name) = self.name() {
448                return AlignSerde::Named(Cow::Borrowed(name)).serialize(serializer);
449            }
450        }
451
452        AlignSerde::Unnamed {
453            x: self.x,
454            x_rtl_aware: self.x_rtl_aware,
455            y: self.y,
456        }
457        .serialize(serializer)
458    }
459}
460impl<'de> serde::Deserialize<'de> for Align {
461    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
462    where
463        D: serde::Deserializer<'de>,
464    {
465        use serde::de::Error;
466
467        match AlignSerde::deserialize(deserializer)? {
468            AlignSerde::Named(n) => match Align::from_name(&n) {
469                Some(a) => Ok(a),
470                None => Err(D::Error::custom("unknown align name")),
471            },
472            AlignSerde::Unnamed { x, x_rtl_aware, y } => Ok(Align { x, x_rtl_aware, y }),
473        }
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480
481    #[test]
482    fn align_named() {
483        let value = serde_json::to_value(Align::TOP_START).unwrap();
484        assert_eq!(value, serde_json::Value::String("TOP_START".to_owned()));
485
486        let align: Align = serde_json::from_value(value).unwrap();
487        assert_eq!(align, Align::TOP_START);
488    }
489}