sequoia-view 0.1.0

Sequoia GUI framework — view tree and runtime
Documentation
#![forbid(unsafe_code)]

use sequoia_core::Length;
use sequoia_layout::LayoutStyle;

use crate::any::AnyView;
use crate::live::{AnimSpec, Live, Prop, SpringSpec};
use crate::runtime::{ElementId, Runtime};
use crate::view::View;

#[derive(Clone)]
pub struct AnimatedSize {
    child: AnyView,
    width: Option<Live<f32>>,
    height: Option<Live<f32>>,
}

impl AnimatedSize {
    pub fn new<V: View + 'static>(
        child: V,
        width: Option<Live<f32>>,
        height: Option<Live<f32>>,
    ) -> Self {
        Self {
            child: AnyView::new(child),
            width,
            height,
        }
    }

    pub fn width<V: View + 'static>(child: V, width: impl Into<Live<f32>>) -> Self {
        Self::new(child, Some(width.into()), None)
    }

    pub fn height<V: View + 'static>(child: V, height: impl Into<Live<f32>>) -> Self {
        Self::new(child, None, Some(height.into()))
    }

    pub fn size<V: View + 'static>(
        child: V,
        width: impl Into<Live<f32>>,
        height: impl Into<Live<f32>>,
    ) -> Self {
        Self::new(child, Some(width.into()), Some(height.into()))
    }

    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
        let spec = spec.into();
        if let Some(w) = &mut self.width {
            w.animate_mut(spec);
        }
        if let Some(h) = &mut self.height {
            h.animate_mut(spec);
        }
        self
    }

    pub fn spring(mut self, spec: SpringSpec) -> Self {
        if let Some(w) = &mut self.width {
            w.spring_mut(spec);
        }
        if let Some(h) = &mut self.height {
            h.spring_mut(spec);
        }
        self
    }

    fn px_or_fit(live: &Option<Live<f32>>) -> Length {
        match live {
            Some(l) => Length::Px(l.target_untracked().max(0.0)),
            None => Length::Fit,
        }
    }
}

impl View for AnimatedSize {
    fn body(&self) -> impl View + 'static {
        crate::views::Unit
    }

    fn children_override(&self) -> Option<Vec<AnyView>> {
        if let Some(w) = &self.width {
            let _ = w.target();
        }
        if let Some(h) = &self.height {
            let _ = h.target();
        }
        Some(vec![self.child.clone()])
    }

    fn layout_style(&self) -> LayoutStyle {
        LayoutStyle {
            width: Self::px_or_fit(&self.width),
            height: Self::px_or_fit(&self.height),
            ..LayoutStyle::default()
        }
    }

    fn layout_style_ctx(&self, rt: &Runtime, el: ElementId) -> LayoutStyle {
        let width = match &self.width {
            Some(live) => Length::Px(rt.resolve_live_f32(el, Prop::WIDTH, live).max(0.0)),
            None => Length::Fit,
        };
        let height = match &self.height {
            Some(live) => Length::Px(rt.resolve_live_f32(el, Prop::HEIGHT, live).max(0.0)),
            None => Length::Fit,
        };
        LayoutStyle {
            width,
            height,
            ..LayoutStyle::default()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::live::SignalLiveExt;
    use sequoia_reactive::Signal;
    use std::time::Duration;

    #[test]
    fn static_snapshot_without_runtime() {
        let w = AnimatedSize::width(crate::views::Unit, 120.0f32);
        let s = w.layout_style();
        assert_eq!(s.width, Length::Px(120.0));
        assert_eq!(s.height, Length::Fit);
    }

    #[test]
    fn tracks_signal_target() {
        let sig = Signal::new(80.0f32);
        let w = AnimatedSize::width(crate::views::Unit, sig.clone().live());
        let kids = w.children_override().expect("dziecko");
        assert_eq!(kids.len(), 1);
        sig.set(200.0);
        let s = w.layout_style();
        assert_eq!(s.width, Length::Px(200.0));
    }

    #[test]
    fn runtime_anchors_then_animates() {
        let rt = Runtime::new();
        let el = ElementId {
            index: 91,
            generation: 0,
        };
        let spec = AnimSpec::from_duration(
            Duration::from_secs_f32(10.0),
            sequoia_animation::Easing::Linear,
        );
        let live = Live::fixed(80.0f32).animate(spec);
        let w = AnimatedSize::width(crate::views::Unit, live);

        let s1 = w.layout_style_ctx(&rt, el);
        assert_eq!(s1.width, Length::Px(80.0));

        let live2 = Live::fixed(200.0f32).animate(spec);
        let w2 = AnimatedSize::width(crate::views::Unit, live2);
        let s2 = w2.layout_style_ctx(&rt, el);
        assert_eq!(s2.width, Length::Px(80.0));
        assert_eq!(rt.live_anim_count(), 1);
    }

    #[test]
    fn size_sets_both_axes() {
        let w = AnimatedSize::size(crate::views::Unit, 100.0f32, 40.0f32);
        let s = w.layout_style();
        assert_eq!(s.width, Length::Px(100.0));
        assert_eq!(s.height, Length::Px(40.0));
        let w = w.animate(AnimSpec::FAST).spring(SpringSpec::BOUNCY);
        let s = w.layout_style();
        assert_eq!(s.width, Length::Px(100.0));
    }
}