zng_layout/unit/
alignment.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Write},
4    ops,
5};
6
7use crate::{context::LayoutDirection, unit::ParseFloatCompositeError};
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, PartialEq, Eq, Hash)]
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 Default for Align {
57    /// [`Align::START`].
58    fn default() -> Self {
59        Align::START
60    }
61}
62impl Align {
63    /// Gets the best finite [`x`] align value.
64    ///
65    /// Replaces `FILL` with `START`, flips `x` for right-to-left if applicable.
66    ///
67    /// [`x`]: Self::x
68    pub fn x(self, direction: LayoutDirection) -> Factor {
69        let x = if self.x.0.is_finite() { self.x } else { 0.fct() };
70
71        if self.x_rtl_aware && direction.is_rtl() { x.flip() } else { x }
72    }
73
74    /// Gets the best finite [`y`] align value.
75    ///
76    /// Returns `1.fct()` for [`is_baseline`], implementers must add the baseline offset to that.
77    ///
78    /// [`y`]: Self::y
79    /// [`is_baseline`]: Self::is_baseline
80    pub fn y(self) -> Factor {
81        if self.y.0.is_finite() {
82            self.y
83        } else if self.is_baseline() {
84            1.fct()
85        } else {
86            0.fct()
87        }
88    }
89
90    /// Gets the best finite [`x`] and [`y`] align values.
91    ///
92    /// [`x`]: fn@Self::x
93    /// [`y`]: fn@Self::y
94    pub fn xy(self, direction: LayoutDirection) -> Factor2d {
95        Factor2d::new(self.x(direction), self.y())
96    }
97
98    /// Returns `true` if [`x`] is a special value that indicates the content width must be the container width.
99    ///
100    /// [`x`]: Align::x
101    pub fn is_fill_x(self) -> bool {
102        self.x.0.is_infinite() && self.x.0.is_sign_positive()
103    }
104
105    /// Returns `true` if [`y`] is a special value that indicates the content height must be the container height.
106    ///
107    /// [`y`]: Align::y
108    pub fn is_fill_y(self) -> bool {
109        self.y.0.is_infinite() && self.y.0.is_sign_positive()
110    }
111
112    /// Returns `true` if [`y`] is a special value that indicates the contents must be aligned by their baseline.
113    ///
114    /// If this is `true` the *y* alignment must be `BOTTOM` plus the baseline offset.
115    ///
116    /// [`y`]: Align::y
117    pub fn is_baseline(self) -> bool {
118        self.y.0.is_infinite() && self.y.0.is_sign_negative()
119    }
120
121    /// Returns a boolean vector of the fill values.
122    pub fn fill_vector(self) -> super::euclid::BoolVector2D {
123        super::euclid::BoolVector2D {
124            x: self.is_fill_x(),
125            y: self.is_fill_y(),
126        }
127    }
128
129    /// Constraints that must be used to layout a child node with the alignment.
130    ///
131    /// Note that these constraints define the child inner bounds (the visual size) only,
132    /// using the child size call [`layout`] to get the child outer bounds size.
133    ///
134    /// [`layout`]: Self::layout
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 [`layout`] to cover all corner cases.
148    ///
149    /// [`layout`]: Self::layout
150    pub fn child_offset(self, child_size: PxSize, parent_size: PxSize, direction: LayoutDirection) -> PxVector {
151        let mut offset = PxVector::zero();
152        if !self.is_fill_x() {
153            let x = if self.x_rtl_aware && direction.is_rtl() {
154                self.x.flip().0
155            } else {
156                self.x.0
157            };
158
159            offset.x = (parent_size.width - child_size.width) * x;
160        }
161
162        let baseline = self.is_baseline();
163
164        if !self.is_fill_y() {
165            let y = if baseline { 1.0 } else { self.y.0 };
166
167            offset.y = (parent_size.height - child_size.height) * y;
168        }
169        offset
170    }
171
172    /// Computes the size returned by [`layout`] for the given child size and constraints.
173    ///
174    /// Note that the child must be measured using [`child_constraints`], the `child_size` is the size the child
175    /// will be rendered at, this method computes the child outer bounds.
176    ///
177    /// [`layout`]: Self::layout
178    /// [`child_constraints`]: Self::child_constraints
179    pub fn measure(self, child_size: PxSize, parent_constraints: PxConstraints2d) -> PxSize {
180        PxSize::new(
181            self.measure_x(child_size.width, parent_constraints.x),
182            self.measure_y(child_size.height, parent_constraints.y),
183        )
184    }
185
186    /// Computes the width returned by layout for the given child width and ***x*** constraints.
187    ///
188    /// See [`measure`] for more details.
189    ///
190    /// [`measure`]: Self::measure
191    pub fn measure_x(self, child_width: Px, parent_constraints_x: PxConstraints) -> Px {
192        if parent_constraints_x.is_inner() {
193            child_width
194        } else {
195            let width = parent_constraints_x.fill().max(child_width);
196            parent_constraints_x.clamp(width)
197        }
198    }
199
200    /// Computes the height returned by layout for the given child height and ***y*** constraints.
201    ///
202    /// See [`measure`] for more details.
203    ///
204    /// [`measure`]: Self::measure
205    pub fn measure_y(self, child_height: Px, parent_constraints_y: PxConstraints) -> Px {
206        if parent_constraints_y.is_inner() {
207            child_height
208        } else {
209            let height = parent_constraints_y.fill().max(child_height);
210            parent_constraints_y.clamp(height)
211        }
212    }
213
214    /// Compute the outer size and inner offset.
215    ///
216    /// Note that the child must be layout using the [`child_constraints`], the `child_size` is the size the child
217    /// will be rendered at, this method computes the child outer bounds.
218    ///
219    /// Returns the outer size, inner offset and [`is_baseline`]
220    ///
221    /// [`child_constraints`]: Self::child_constraints
222    /// [`is_baseline`]: Self::is_baseline
223    pub fn layout(self, child_size: PxSize, parent_constraints: PxConstraints2d, direction: LayoutDirection) -> (PxSize, PxVector, bool) {
224        let size = self.measure(child_size, parent_constraints);
225        let offset = self.child_offset(child_size, size, direction);
226        (size, offset, self.is_baseline())
227    }
228}
229impl_from_and_into_var! {
230    fn from<X: Into<Factor>, Y: Into<Factor>>((x, y): (X, Y)) -> Align {
231        Align {
232            x: x.into(),
233            x_rtl_aware: false,
234            y: y.into(),
235        }
236    }
237
238    fn from<X: Into<Factor>, Y: Into<Factor>>((x, rtl, y): (X, bool, Y)) -> Align {
239        Align {
240            x: x.into(),
241            x_rtl_aware: rtl,
242            y: y.into(),
243        }
244    }
245
246    fn from(xy: Factor) -> Align {
247        Align {
248            x: xy,
249            x_rtl_aware: false,
250            y: xy,
251        }
252    }
253
254    fn from(xy: FactorPercent) -> Align {
255        xy.fct().into()
256    }
257}
258macro_rules! named_aligns {
259
260    ( $($(#[$doc:meta])* $NAME:ident = ($x:expr, $rtl:expr, $y:expr);)+ ) => {
261        $(
262        $(#[$doc])*
263        pub const $NAME: Align = Align { x: Factor($x), x_rtl_aware: $rtl, y: Factor($y) };
264        )+
265
266        /// Returns the alignment `const` name if `self` is equal to one of then.
267        pub fn name(self) -> Option<&'static str> {
268            $(
269                if self == Self::$NAME {
270                    Some(stringify!($NAME))
271                }
272            )else+
273            else {
274                None
275            }
276        }
277
278        /// Returns the named alignment.
279        pub fn from_name(name: &str) -> Option<Self> {
280            $(
281                if name == stringify!($NAME) {
282                    Some(Self::$NAME)
283                }
284            )else+
285            else {
286                None
287            }
288        }
289    };
290}
291impl fmt::Debug for Align {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        if let Some(name) = self.name() {
294            if f.alternate() {
295                write!(f, "Align::{name}")
296            } else {
297                f.write_str(name)
298            }
299        } else {
300            f.debug_struct("Align")
301                .field("x", &self.x)
302                .field("x_rtl_aware", &self.x_rtl_aware)
303                .field("y", &self.y)
304                .finish()
305        }
306    }
307}
308impl fmt::Display for Align {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        if let Some(name) = self.name() {
311            f.write_str(name)
312        } else {
313            f.write_char('(')?;
314            if self.is_fill_x() {
315                f.write_str("FILL")?;
316            } else {
317                write!(f, "{}", FactorPercent::from(self.x))?;
318            }
319            f.write_str(", ")?;
320            if self.is_fill_y() {
321                f.write_str("FILL")?;
322            } else if self.is_baseline() {
323                f.write_str("BASELINE")?;
324            } else {
325                write!(f, "{}", FactorPercent::from(self.y))?;
326            }
327            f.write_char(')')
328        }
329    }
330}
331/// Parse a named align or `"(x, y)"` were x `Factor` or `"FILL"` and y is `factor`, `"FILL"` or `"BASELINE"`.
332impl std::str::FromStr for Align {
333    type Err = ParseFloatCompositeError;
334
335    fn from_str(s: &str) -> Result<Self, Self::Err> {
336        if let Some(named) = Align::from_name(s) {
337            Ok(named)
338        } else if let Some(s) = s.strip_prefix('(')
339            && let Some(s) = s.strip_prefix(')')
340        {
341            let mut parser = ComponentParser { iter: s.split(',') };
342            let r = Self {
343                x: parser.next(false)?,
344                x_rtl_aware: false,
345                y: parser.next(true)?,
346            };
347            parser.end()?;
348            Ok(r)
349        } else {
350            Err(ParseFloatCompositeError::UnknownFormat)
351        }
352    }
353}
354struct ComponentParser<'a> {
355    iter: std::str::Split<'a, char>,
356}
357impl<'a> ComponentParser<'a> {
358    fn next(&mut self, y: bool) -> Result<Factor, ParseFloatCompositeError> {
359        let fct = match self.iter.next().ok_or(ParseFloatCompositeError::MissingComponent)?.trim() {
360            "FILL" => f32::INFINITY.fct(),
361            "BASELINE" if y => f32::NEG_INFINITY.fct(),
362            s => s.parse()?,
363        };
364        Ok(fct)
365    }
366    fn end(mut self) -> Result<(), ParseFloatCompositeError> {
367        if self.iter.next().is_some() {
368            Err(ParseFloatCompositeError::ExtraComponent)
369        } else {
370            Ok(())
371        }
372    }
373}
374impl Align {
375    named_aligns! {
376        /// x: 0, y: 0, RTL aware.
377        ///
378        /// In left-to-right contexts this is `TOP_LEFT`, in right-to-left contexts this is `TOP_RIGHT`.
379        TOP_START = (0.0, true, 0.0);
380        /// x: 0, y: 0
381        TOP_LEFT = (0.0, false, 0.0);
382        /// x: 0, y: 1, RTL aware.
383        ///
384        /// In left-to-right contexts this is `BOTTOM_LEFT`, in right-to-left contexts this is `BOTTOM_RIGHT`.
385        BOTTOM_START = (0.0, true, 1.0);
386        /// x: 0, y: 1
387        BOTTOM_LEFT = (0.0, false, 1.0);
388
389        /// x: 1, y: 0, RTL aware.
390        ///
391        /// In left-to-right contexts this is `TOP_RIGHT`, in right-to-left contexts this is `TOP_LEFT`.
392        TOP_END = (1.0, true, 0.0);
393        /// x: 1, y: 0
394        TOP_RIGHT = (1.0, false, 0.0);
395        /// x: 1, y: 1
396        ///
397        /// In left-to-right contexts this is `BOTTOM_RIGHT`, in right-to-left contexts this is `BOTTOM_LEFT`.
398        BOTTOM_END = (1.0, true, 1.0);
399        /// x: 1, y: 1
400        BOTTOM_RIGHT = (1.0, false, 1.0);
401
402        /// x: 0, y: 0.5, RTL aware.
403        ///
404        /// In left-to-right contexts this is `LEFT`, in right-to-left contexts this is `RIGHT`.
405        START = (0.0, true, 0.5);
406        /// x: 0, y: 0.5
407        LEFT = (0.0, false, 0.5);
408        /// x: 1, y: 0.5, RTL aware.
409        ///
410        /// In left-to-right contexts this is `RIGHT`, in right-to-left contexts this is `LEFT`.
411        END = (1.0, true, 0.5);
412        /// x: 1, y: 0.5
413        RIGHT = (1.0, false, 0.5);
414        /// x: 0.5, y: 0
415        TOP = (0.5, false, 0.0);
416        /// x: 0.5, y: 1
417        BOTTOM = (0.5, false, 1.0);
418
419        /// x: 0.5, y: 0.5
420        CENTER = (0.5, false, 0.5);
421
422        /// x: +inf, y: 0
423        FILL_TOP = (f32::INFINITY, false, 0.0);
424        /// x: +inf, y: 1
425        FILL_BOTTOM = (f32::INFINITY, false, 1.0);
426        /// x: 0, y: +inf, RTL aware.
427        ///
428        /// In left-to-right contexts this is `FILL_LEFT`, in right-to-left contexts this is `FILL_RIGHT`.
429        FILL_START = (0.0, true, f32::INFINITY);
430        /// x: 0, y: +inf
431        FILL_LEFT = (0.0, false, f32::INFINITY);
432        /// x: 1, y: +inf
433        FILL_RIGHT = (1.0, false, f32::INFINITY);
434        /// x: 1, y: +inf, RTL aware.
435        ///
436        /// In left-to-right contexts this is `FILL_RIGHT`, in right-to-left contexts this is `FILL_LEFT`.
437        FILL_END = (1.0, true, f32::INFINITY);
438
439        /// x: +inf, y: 0.5
440        FILL_X = (f32::INFINITY, false, 0.5);
441        /// x: 0.5, y: +inf
442        FILL_Y = (0.5, false, f32::INFINITY);
443
444        /// x: +inf, y: +inf
445        FILL = (f32::INFINITY, false, f32::INFINITY);
446
447        /// x: 0, y: -inf, RTL aware.
448        ///
449        /// In left-to-right contexts this is `BASELINE_LEFT`, in right-to-left contexts this is `BASELINE_RIGHT`.
450        BASELINE_START = (0.0, true, f32::NEG_INFINITY);
451        /// x: 0, y: -inf
452        BASELINE_LEFT = (0.0, false, f32::NEG_INFINITY);
453        /// x: 0.5, y: -inf
454        BASELINE_CENTER = (0.5, false, f32::NEG_INFINITY);
455        /// x: 1, y: -inf, RTL aware.
456        ///
457        /// In left-to-right contexts this is `BASELINE_RIGHT`, in right-to-left contexts this is `BASELINE_LEFT`.
458        BASELINE_END = (1.0, true, f32::NEG_INFINITY);
459        /// x: 1, y: -inf
460        BASELINE_RIGHT = (1.0, false, f32::NEG_INFINITY);
461
462        /// x: +inf, y: -inf
463        BASELINE = (f32::INFINITY, false, f32::NEG_INFINITY);
464    }
465}
466impl_from_and_into_var! {
467    /// To relative length x and y.
468    fn from(alignment: Align) -> Point {
469        Point {
470            x: alignment.x.into(),
471            y: alignment.y.into(),
472        }
473    }
474
475    fn from(factor2d: Factor2d) -> Align {
476        Align {
477            x: factor2d.x,
478            x_rtl_aware: false,
479            y: factor2d.y,
480        }
481    }
482}
483
484impl Transitionable for Align {
485    fn lerp(mut self, to: &Self, step: EasingStep) -> Self {
486        let end = step >= 1.fct();
487
488        if end {
489            self.x_rtl_aware = to.x_rtl_aware;
490        }
491
492        if self.x.0.is_finite() && to.x.0.is_finite() {
493            self.x = self.x.lerp(&to.x, step);
494        } else if end {
495            self.x = to.x;
496        }
497
498        if self.y.0.is_finite() && to.y.0.is_finite() {
499            self.y = self.y.lerp(&to.y, step);
500        } else if end {
501            self.y = to.y;
502        }
503
504        self
505    }
506}
507
508impl<S: Into<Factor2d>> ops::Mul<S> for Align {
509    type Output = Self;
510
511    fn mul(mut self, rhs: S) -> Self {
512        self *= rhs;
513        self
514    }
515}
516impl<S: Into<Factor2d>> ops::MulAssign<S> for Align {
517    fn mul_assign(&mut self, rhs: S) {
518        let rhs = rhs.into();
519
520        if self.x.0.is_finite() {
521            self.x *= rhs.x;
522        } else if rhs.x == 0.fct() {
523            self.x = 0.fct();
524        }
525        if self.y.0.is_finite() {
526            self.y *= rhs.y;
527        } else if rhs.y == 0.fct() {
528            self.y = 0.fct()
529        }
530    }
531}
532impl<S: Into<Factor2d>> ops::Div<S> for Align {
533    type Output = Self;
534
535    fn div(mut self, rhs: S) -> Self {
536        self /= rhs;
537        self
538    }
539}
540impl<S: Into<Factor2d>> ops::DivAssign<S> for Align {
541    fn div_assign(&mut self, rhs: S) {
542        let rhs = rhs.into();
543
544        if self.x.0.is_finite() {
545            self.x /= rhs.x;
546        }
547        if self.y.0.is_finite() {
548            self.y /= rhs.y;
549        }
550    }
551}
552
553#[derive(serde::Serialize, serde::Deserialize)]
554#[serde(untagged)]
555enum AlignSerde<'s> {
556    Named(Cow<'s, str>),
557    Unnamed { x: Factor, x_rtl_aware: bool, y: Factor },
558}
559impl serde::Serialize for Align {
560    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
561    where
562        S: serde::Serializer,
563    {
564        if serializer.is_human_readable()
565            && let Some(name) = self.name()
566        {
567            return AlignSerde::Named(Cow::Borrowed(name)).serialize(serializer);
568        }
569
570        AlignSerde::Unnamed {
571            x: self.x,
572            x_rtl_aware: self.x_rtl_aware,
573            y: self.y,
574        }
575        .serialize(serializer)
576    }
577}
578impl<'de> serde::Deserialize<'de> for Align {
579    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
580    where
581        D: serde::Deserializer<'de>,
582    {
583        use serde::de::Error;
584
585        match AlignSerde::deserialize(deserializer)? {
586            AlignSerde::Named(n) => match Align::from_name(&n) {
587                Some(a) => Ok(a),
588                None => Err(D::Error::custom("unknown align name")),
589            },
590            AlignSerde::Unnamed { x, x_rtl_aware, y } => Ok(Align { x, x_rtl_aware, y }),
591        }
592    }
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    #[test]
600    fn align_named() {
601        let value = serde_json::to_value(Align::TOP_START).unwrap();
602        assert_eq!(value, serde_json::Value::String("TOP_START".to_owned()));
603
604        let align: Align = serde_json::from_value(value).unwrap();
605        assert_eq!(align, Align::TOP_START);
606    }
607}