sequoia-elements 0.1.1

Sequoia GUI framework — built-in UI elements
Documentation
use std::time::Instant;

use sequoia_core::{Color, Length, Rect};
use sequoia_layout::LayoutStyle;
use sequoia_reactive::Signal;
use sequoia_view::{AnyView, PaintCtx, Panel, Pulse, Unit, View};

#[derive(Clone)]
pub struct ProgressBar {
    value: Signal<f32>,
    height: f32,
    track: Option<Color>,
    fill: Option<Color>,
}

impl ProgressBar {
    pub fn new(value: Signal<f32>) -> Self {
        Self {
            value,
            height: 6.0,
            track: None,
            fill: None,
        }
    }

    pub fn height(mut self, h: f32) -> Self {
        if h.is_finite() && h > 0.0 {
            self.height = h;
        }
        self
    }

    pub fn track(mut self, color: Color) -> Self {
        self.track = Some(color);
        self
    }

    pub fn fill(mut self, color: Color) -> Self {
        self.fill = Some(color);
        self
    }

    fn clamped(&self) -> f32 {
        self.value.get_untracked().clamp(0.0, 1.0)
    }
}

impl View for ProgressBar {
    fn body(&self) -> impl View + 'static {
        Unit
    }

    fn layout_style(&self) -> LayoutStyle {
        LayoutStyle {
            width: Length::Fill(1.0),
            height: Length::Px(self.height),
            ..LayoutStyle::default()
        }
    }

    fn paint_self(&self, ctx: &mut PaintCtx) {
        let _ = self.value.get_untracked();
        let theme = ctx.theme();
        let track = self.track.unwrap_or(theme.surface);
        let fill = self.fill.unwrap_or(theme.accent);
        let r = ctx.rect;
        ctx.quad(r, track, r.size.height * 0.5);
        let w = r.size.width * self.clamped();
        if w > 0.5 {
            ctx.quad(
                Rect::from_xywh(r.left(), r.top(), w, r.size.height),
                fill,
                r.size.height * 0.5,
            );
        }
    }
}

#[derive(Clone)]
pub struct Spinner {
    size: f32,
    color: Option<Color>,
    period_secs: f32,
}

impl Spinner {
    pub fn new() -> Self {
        Self {
            size: 16.0,
            color: None,
            period_secs: 1.0,
        }
    }

    pub fn size(mut self, px: f32) -> Self {
        if px.is_finite() && px > 0.0 {
            self.size = px;
        }
        self
    }

    pub fn color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }

    pub fn period_secs(mut self, secs: f32) -> Self {
        if secs.is_finite() && secs > 0.0 {
            self.period_secs = secs;
        }
        self
    }
}

impl Default for Spinner {
    fn default() -> Self {
        Self::new()
    }
}

impl View for Spinner {
    fn body(&self) -> impl View + 'static {
        Unit
    }

    fn layout_style(&self) -> LayoutStyle {
        LayoutStyle {
            width: Length::Px(self.size),
            height: Length::Px(self.size),
            ..LayoutStyle::default()
        }
    }

    fn paint_self(&self, ctx: &mut PaintCtx) {
        let theme = ctx.theme();
        let base = self.color.unwrap_or(theme.accent);
        let r = ctx.rect;
        let cx = r.left() + r.size.width * 0.5;
        let cy = r.top() + r.size.height * 0.5;
        let radius = r.size.width.min(r.size.height) * 0.38;
        let dot = (r.size.width.min(r.size.height) * 0.09).max(1.0);
        let elapsed = START.elapsed().as_secs_f32();
        let phase = (elapsed / self.period_secs) % 1.0;
        for i in 0..8 {
            let a = (i as f32 / 8.0) * std::f32::consts::TAU;

            let dist = ((i as f32 / 8.0) - phase).rem_euclid(1.0);
            let alpha = 1.0 - dist * 0.85;
            ctx.quad(
                Rect::from_xywh(
                    cx + radius * a.cos() - dot,
                    cy + radius * a.sin() - dot,
                    dot * 2.0,
                    dot * 2.0,
                ),
                base.with_alpha(alpha),
                dot,
            );
        }
        ctx.request_frame();
    }
}

static START: std::sync::LazyLock<Instant> = std::sync::LazyLock::new(Instant::now);

#[derive(Clone)]
pub struct Skeleton {
    width: Length,
    height: f32,
}

impl Default for Skeleton {
    fn default() -> Self {
        Self::new()
    }
}

impl Skeleton {
    pub fn new() -> Self {
        Self {
            width: Length::Fill(1.0),
            height: 16.0,
        }
    }

    pub fn width(mut self, width: impl Into<Length>) -> Self {
        self.width = width.into();
        self
    }

    pub fn height(mut self, h: f32) -> Self {
        if h.is_finite() && h > 0.0 {
            self.height = h;
        }
        self
    }
}

impl View for Skeleton {
    fn body(&self) -> impl View + 'static {
        Unit
    }

    fn children_override(&self) -> Option<Vec<AnyView>> {
        Some(vec![AnyView::new(Pulse::new(
            Panel::new()
                .width(self.width)
                .height(Length::Px(self.height)),
        ))])
    }

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