guido 0.12.0

A reactive Rust GUI library using wgpu for Wayland layer shell widgets
Documentation
//! Whether a declared value is a number arithmetic can carry on with.
//!
//! A property is a closure the application wrote, and a division by a measured
//! zero is all it takes to produce a NaN. Once one is in it stays: `lerp` from
//! a NaN start is NaN at every `t`, so a later and perfectly finite target
//! animates from nothing to nothing for the life of the process — and
//! `animate_to`'s `new_target == self.target` early-out never fires, because
//! NaN is equal to nothing including itself, so the surface asks for another
//! frame at the compositor's rate for ever.
//!
//! The answer is CSS's: a value that is not a number is coerced *where it is
//! resolved*, once, so no consumer downstream ever has to know it happened. The
//! alternative — a guard at each consumer — is what was tried before, and each
//! guard changed what the next one saw.
//!
//! This lives apart from [`Animatable`](crate::animation::Animatable) on
//! purpose. "Can this be interpolated" and "is this a number" are different
//! questions, and hanging the second on the first put the door out of reach of
//! every declared value that is not animatable — a [`Length`], a [`Pivot`], a
//! gradient — which is most of the ways a bad number gets in.
//!
//! [`Length`]: crate::layout::Length
//! [`Pivot`]: crate::pivot::Pivot

use crate::reactive::Prop;

/// A value that can say whether every number inside it is finite.
///
/// Implemented for every type a property can be declared with. The name is
/// `all_finite` rather than `is_finite` because `f32` already has an inherent
/// method by that name: a trait method taking `&self` matches a `&f32` receiver
/// *exactly* while the inherent one needs a deref, so `c.is_finite()` inside a
/// blanket body resolves to the trait and calls itself until the stack ends.
pub(crate) trait AllFinite {
    /// Whether this value is one arithmetic can carry on with.
    fn all_finite(&self) -> bool;
}

impl AllFinite for f32 {
    fn all_finite(&self) -> bool {
        f32::is_finite(*self)
    }
}

/// Reading a declared property, with a value nothing can compute treated as no
/// declaration at all.
pub(crate) trait FiniteOr<T> {
    /// The value, or `default` where it is not finite.
    ///
    /// `property` is only for the diagnostic, and is the name a caller would
    /// recognise — the setter they wrote, not the accessor that resolves it.
    fn get_finite_or(&self, default: T, id: crate::tree::WidgetId, property: &'static str) -> T;

    /// Tracked, and silent about a value that is not a number.
    ///
    /// For a second reader of a property somebody else already reports: the
    /// reach calculation reads the same transform the paint beside it does,
    /// and both would otherwise name it. It still has to *subscribe*, because
    /// the reach is what brings a culled widget back into view and the paint
    /// that would have reported it never ran (#319) — so being quiet and
    /// being untracked, which used to be the same call, are now two.
    fn get_finite_or_quietly(&self, default: T) -> T;
}

impl<T: AllFinite + Clone + 'static> FiniteOr<T> for Prop<T> {
    // Matching on `get` rather than reading through `get_or`, so `default` is
    // not cloned in order to be dropped. These run per property per container
    // per painted frame, and a `Shadow` is 32 bytes of it.
    //
    // An undeclared property is `None` and falls back without a word: there is
    // no declaration to report.
    fn get_finite_or(&self, default: T, id: crate::tree::WidgetId, property: &'static str) -> T {
        match self.get() {
            Some(value) if value.all_finite() => value,
            Some(_) => {
                crate::reactive::diagnostics::non_finite_value(id, property);
                default
            }
            None => default,
        }
    }

    fn get_finite_or_quietly(&self, default: T) -> T {
        match self.get() {
            Some(value) if value.all_finite() => value,
            _ => default,
        }
    }
}

/// The nearest state override that is a number, or `base` where none of them
/// is.
///
/// [`FiniteOr`] answers for a property declared once. A property with state
/// overrides is declared more than once — the override, and the declaration it
/// supplies a value for — and then reading a bad override as if nothing had been
/// declared is the wrong answer: what it falls through to is the declaration
/// underneath it.
///
/// `base` is that declaration, already resolved through [`FiniteOr`], so it is
/// finite and a bad one has already been reported. These are the same two steps
/// in the same order as a container's resolution, and they carry the same rule:
/// an override that is not a number is passed over exactly as one that says
/// nothing about this property is, and passed over *silently*, because the
/// diagnostic belongs to the declaration the widget is left following.
///
/// Nearest override first, stopping at the first that is a number. The ones
/// beyond it are left unread on purpose and nothing is lost by it: only an
/// override nearer than the winner can change the answer, and every one of those
/// was read on the way in, so a frame where one of them stops being NaN is a
/// frame this widget is woken for.
pub(crate) fn first_finite_override<T: AllFinite + Clone + 'static>(
    overrides: &[Prop<T>],
    base: T,
) -> T {
    for declared in overrides {
        if let Some(value) = declared.get()
            && value.all_finite()
        {
            return value;
        }
    }
    base
}

/// Every type a `Container` property is declared with, answered from the
/// numbers it is made of.
///
/// The animatable ones already have to say which numbers those are in order to
/// be interpolated at all, so they answer from
/// [`channels`](crate::animation::Animatable::channels) and cannot go stale
/// when a field is added — the last attempt at this wrote the check out per
/// type, covered three of seven, and nobody noticed the other four.
macro_rules! finite_by_channels {
    ($($t:ty),* $(,)?) => {
        $(impl AllFinite for $t {
            fn all_finite(&self) -> bool {
                crate::animation::Animatable::channels(self)
                    .iter()
                    .copied()
                    .all(f32::is_finite)
            }
        })*
    };
}

finite_by_channels!(
    crate::widgets::Color,
    crate::renderer::Shadow,
    crate::widgets::Padding,
    crate::widgets::Corners,
    crate::transform::Translate,
    crate::transform::Scale,
);

impl AllFinite for crate::widgets::LinearGradient {
    fn all_finite(&self) -> bool {
        self.start_color.all_finite() && self.end_color.all_finite()
    }
}

impl<T: AllFinite> AllFinite for Option<T> {
    /// An absent value cannot be a bad one.
    fn all_finite(&self) -> bool {
        self.as_ref().is_none_or(AllFinite::all_finite)
    }
}

impl AllFinite for crate::layout::Length {
    /// An extent that may carry a number, and two bounds that may be absent.
    ///
    /// The bounds are read back through `min()`/`max()` rather than off the
    /// fields, so the sentinels — finite on purpose — are answered as the
    /// absences they stand for rather than checked as the numbers they are.
    fn all_finite(&self) -> bool {
        [self.extent().declared_size(), self.min(), self.max()]
            .into_iter()
            .flatten()
            .all(f32::is_finite)
    }
}

impl AllFinite for crate::pivot::Pivot {
    /// An anchor is only a number when it is a `Percent` or a `Px`; the named
    /// ones are constants and cannot be anything else.
    fn all_finite(&self) -> bool {
        use crate::pivot::{HorizontalAnchor as H, VerticalAnchor as V};
        let horizontal = match self.horizontal {
            H::Percent(p) | H::Px(p) => p.is_finite(),
            _ => true,
        };
        let vertical = match self.vertical {
            V::Percent(p) | V::Px(p) => p.is_finite(),
            _ => true,
        };
        // Both arms, and both are watched: the enumeration test puts the bad
        // number in the horizontal anchor and the hit-testing one puts it in
        // the vertical, so neither can be deleted unnoticed.
        horizontal && vertical
    }
}