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    ( $($NAME:ident = ($x:expr, $rtl:expr, $y:expr);)+ ) => {named_aligns!{$(
260        [stringify!(($x, $y))] $NAME = ($x, $rtl, $y);
261    )+}};
262
263    ( $([$doc:expr] $NAME:ident = ($x:expr, $rtl:expr, $y:expr);)+ ) => {
264        $(
265        #[doc=$doc]
266        pub const $NAME: Align = Align { x: Factor($x), x_rtl_aware: $rtl, y: Factor($y) };
267        )+
268
269        /// Returns the alignment `const` name if `self` is equal to one of then.
270        pub fn name(self) -> Option<&'static str> {
271            $(
272                if self == Self::$NAME {
273                    Some(stringify!($NAME))
274                }
275            )else+
276            else {
277                None
278            }
279        }
280
281        /// Returns the named alignment.
282        pub fn from_name(name: &str) -> Option<Self> {
283            $(
284                if name == stringify!($NAME) {
285                    Some(Self::$NAME)
286                }
287            )else+
288            else {
289                None
290            }
291        }
292    };
293}
294impl fmt::Debug for Align {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        if let Some(name) = self.name() {
297            if f.alternate() {
298                write!(f, "Align::{name}")
299            } else {
300                f.write_str(name)
301            }
302        } else {
303            f.debug_struct("Align")
304                .field("x", &self.x)
305                .field("x_rtl_aware", &self.x_rtl_aware)
306                .field("y", &self.y)
307                .finish()
308        }
309    }
310}
311impl fmt::Display for Align {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        if let Some(name) = self.name() {
314            f.write_str(name)
315        } else {
316            f.write_char('(')?;
317            if self.is_fill_x() {
318                f.write_str("FILL")?;
319            } else {
320                write!(f, "{}", FactorPercent::from(self.x))?;
321            }
322            f.write_str(", ")?;
323            if self.is_fill_y() {
324                f.write_str("FILL")?;
325            } else if self.is_baseline() {
326                f.write_str("BASELINE")?;
327            } else {
328                write!(f, "{}", FactorPercent::from(self.y))?;
329            }
330            f.write_char(')')
331        }
332    }
333}
334/// Parse a named align or `"(x, y)"` were x `Factor` or `"FILL"` and y is `factor`, `"FILL"` or `"BASELINE"`.
335impl std::str::FromStr for Align {
336    type Err = ParseFloatCompositeError;
337
338    fn from_str(s: &str) -> Result<Self, Self::Err> {
339        if let Some(named) = Align::from_name(s) {
340            Ok(named)
341        } else if let Some(s) = s.strip_prefix('(')
342            && let Some(s) = s.strip_prefix(')')
343        {
344            let mut parser = ComponentParser { iter: s.split(',') };
345            let r = Self {
346                x: parser.next(false)?,
347                x_rtl_aware: false,
348                y: parser.next(true)?,
349            };
350            parser.end()?;
351            Ok(r)
352        } else {
353            Err(ParseFloatCompositeError::UnknownFormat)
354        }
355    }
356}
357struct ComponentParser<'a> {
358    iter: std::str::Split<'a, char>,
359}
360impl<'a> ComponentParser<'a> {
361    fn next(&mut self, y: bool) -> Result<Factor, ParseFloatCompositeError> {
362        let fct = match self.iter.next().ok_or(ParseFloatCompositeError::MissingComponent)?.trim() {
363            "FILL" => f32::INFINITY.fct(),
364            "BASELINE" if y => f32::NEG_INFINITY.fct(),
365            s => s.parse()?,
366        };
367        Ok(fct)
368    }
369    fn end(mut self) -> Result<(), ParseFloatCompositeError> {
370        if self.iter.next().is_some() {
371            Err(ParseFloatCompositeError::ExtraComponent)
372        } else {
373            Ok(())
374        }
375    }
376}
377impl Align {
378    named_aligns! {
379        TOP_START = (0.0, true, 0.0);
380        TOP_LEFT = (0.0, false, 0.0);
381        BOTTOM_START = (0.0, true, 1.0);
382        BOTTOM_LEFT = (0.0, false, 1.0);
383
384        TOP_END = (1.0, true, 0.0);
385        TOP_RIGHT = (1.0, false, 0.0);
386        BOTTOM_END = (1.0, true, 1.0);
387        BOTTOM_RIGHT = (1.0, false, 1.0);
388
389        START = (0.0, true, 0.5);
390        LEFT = (0.0, false, 0.5);
391        END = (1.0, true, 0.5);
392        RIGHT = (1.0, false, 0.5);
393        TOP = (0.5, false, 0.0);
394        BOTTOM = (0.5, false, 1.0);
395
396        CENTER = (0.5, false, 0.5);
397
398        FILL_TOP = (f32::INFINITY, false, 0.0);
399        FILL_BOTTOM = (f32::INFINITY, false, 1.0);
400        FILL_START = (0.0, true, f32::INFINITY);
401        FILL_LEFT = (0.0, false, f32::INFINITY);
402        FILL_RIGHT = (1.0, false, f32::INFINITY);
403        FILL_END = (1.0, true, f32::INFINITY);
404
405        FILL_X = (f32::INFINITY, false, 0.5);
406        FILL_Y = (0.5, false, f32::INFINITY);
407
408        FILL = (f32::INFINITY, false, f32::INFINITY);
409
410        BASELINE_START = (0.0, true, f32::NEG_INFINITY);
411        BASELINE_LEFT = (0.0, false, f32::NEG_INFINITY);
412        BASELINE_CENTER = (0.5, false, f32::NEG_INFINITY);
413        BASELINE_END = (1.0, true, f32::NEG_INFINITY);
414        BASELINE_RIGHT = (1.0, false, f32::NEG_INFINITY);
415
416        BASELINE = (f32::INFINITY, false, f32::NEG_INFINITY);
417    }
418}
419impl_from_and_into_var! {
420    /// To relative length x and y.
421    fn from(alignment: Align) -> Point {
422        Point {
423            x: alignment.x.into(),
424            y: alignment.y.into(),
425        }
426    }
427
428    fn from(factor2d: Factor2d) -> Align {
429        Align {
430            x: factor2d.x,
431            x_rtl_aware: false,
432            y: factor2d.y,
433        }
434    }
435}
436
437impl Transitionable for Align {
438    fn lerp(mut self, to: &Self, step: EasingStep) -> Self {
439        let end = step >= 1.fct();
440
441        if end {
442            self.x_rtl_aware = to.x_rtl_aware;
443        }
444
445        if self.x.0.is_finite() && to.x.0.is_finite() {
446            self.x = self.x.lerp(&to.x, step);
447        } else if end {
448            self.x = to.x;
449        }
450
451        if self.y.0.is_finite() && to.y.0.is_finite() {
452            self.y = self.y.lerp(&to.y, step);
453        } else if end {
454            self.y = to.y;
455        }
456
457        self
458    }
459}
460
461impl<S: Into<Factor2d>> ops::Mul<S> for Align {
462    type Output = Self;
463
464    fn mul(mut self, rhs: S) -> Self {
465        self *= rhs;
466        self
467    }
468}
469impl<S: Into<Factor2d>> ops::MulAssign<S> for Align {
470    fn mul_assign(&mut self, rhs: S) {
471        let rhs = rhs.into();
472
473        if self.x.0.is_finite() {
474            self.x *= rhs.x;
475        } else if rhs.x == 0.fct() {
476            self.x = 0.fct();
477        }
478        if self.y.0.is_finite() {
479            self.y *= rhs.y;
480        } else if rhs.y == 0.fct() {
481            self.y = 0.fct()
482        }
483    }
484}
485impl<S: Into<Factor2d>> ops::Div<S> for Align {
486    type Output = Self;
487
488    fn div(mut self, rhs: S) -> Self {
489        self /= rhs;
490        self
491    }
492}
493impl<S: Into<Factor2d>> ops::DivAssign<S> for Align {
494    fn div_assign(&mut self, rhs: S) {
495        let rhs = rhs.into();
496
497        if self.x.0.is_finite() {
498            self.x /= rhs.x;
499        }
500        if self.y.0.is_finite() {
501            self.y /= rhs.y;
502        }
503    }
504}
505
506#[derive(serde::Serialize, serde::Deserialize)]
507#[serde(untagged)]
508enum AlignSerde<'s> {
509    Named(Cow<'s, str>),
510    Unnamed { x: Factor, x_rtl_aware: bool, y: Factor },
511}
512impl serde::Serialize for Align {
513    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
514    where
515        S: serde::Serializer,
516    {
517        if serializer.is_human_readable()
518            && let Some(name) = self.name()
519        {
520            return AlignSerde::Named(Cow::Borrowed(name)).serialize(serializer);
521        }
522
523        AlignSerde::Unnamed {
524            x: self.x,
525            x_rtl_aware: self.x_rtl_aware,
526            y: self.y,
527        }
528        .serialize(serializer)
529    }
530}
531impl<'de> serde::Deserialize<'de> for Align {
532    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
533    where
534        D: serde::Deserializer<'de>,
535    {
536        use serde::de::Error;
537
538        match AlignSerde::deserialize(deserializer)? {
539            AlignSerde::Named(n) => match Align::from_name(&n) {
540                Some(a) => Ok(a),
541                None => Err(D::Error::custom("unknown align name")),
542            },
543            AlignSerde::Unnamed { x, x_rtl_aware, y } => Ok(Align { x, x_rtl_aware, y }),
544        }
545    }
546}
547
548#[cfg(test)]
549mod tests {
550    use super::*;
551
552    #[test]
553    fn align_named() {
554        let value = serde_json::to_value(Align::TOP_START).unwrap();
555        assert_eq!(value, serde_json::Value::String("TOP_START".to_owned()));
556
557        let align: Align = serde_json::from_value(value).unwrap();
558        assert_eq!(align, Align::TOP_START);
559    }
560}