graphix-package-tui 0.9.0

A dataflow language for UIs and network programming, TUI package
Documentation
use super::{into_borrowed_line, validate, DirectionV, LineV, StyleV, TuiW, TuiWidget};
use anyhow::{Context, Result};
use arcstr::ArcStr;
use async_trait::async_trait;
use crossterm::event::Event;
use futures::future::{self, try_join_all};
use graphix_compiler::expr::ExprId;
use graphix_rt::{GXExt, GXHandle, Ref, TRef};
use netidx::publisher::Value;
use ratatui::{
    layout::Rect,
    widgets::{Bar, BarChart, BarGroup},
    Frame,
};
use smallvec::{smallvec, SmallVec};
use tokio::try_join;

struct BarW<X: GXExt> {
    label: TRef<X, Option<LineV>>,
    style: TRef<X, Option<StyleV>>,
    text_value: TRef<X, Option<ArcStr>>,
    value: TRef<X, i64>,
    value_style: TRef<X, Option<StyleV>>,
    last_warned_value: Option<i64>,
}

impl<X: GXExt> BarW<X> {
    async fn compile(gx: &GXHandle<X>, v: Value) -> Result<Self> {
        let [(_, label), (_, style), (_, text_value), (_, value), (_, value_style)] =
            v.cast_to::<[(ArcStr, u64); 5]>()?;
        let (label, style, text_value, value, value_style) = try_join! {
            gx.compile_ref(label),
            gx.compile_ref(style),
            gx.compile_ref(text_value),
            gx.compile_ref(value),
            gx.compile_ref(value_style)
        }?;
        Ok(Self {
            label: TRef::new(label)?,
            style: TRef::new(style)?,
            text_value: TRef::new(text_value)?,
            value: TRef::new(value)?,
            value_style: TRef::new(value_style)?,
            last_warned_value: None,
        })
    }

    fn update(&mut self, id: ExprId, v: &Value) -> Result<()> {
        self.label.update(id, v).context("bar label update")?;
        self.style.update(id, v).context("bar style update")?;
        self.text_value.update(id, v).context("bar text_value update")?;
        self.value.update(id, v).context("bar value update")?;
        self.value_style.update(id, v).context("bar value_style update")?;
        Ok(())
    }

    fn build<'a>(&'a mut self) -> Bar<'a> {
        let raw = self.value.t.unwrap_or(0);
        let v = validate::clamp_u64("bar", "value", &mut self.last_warned_value, raw);
        let mut bar = Bar::default().value(v);
        if let Some(Some(LineV(l))) = &self.label.t {
            bar = bar.label(into_borrowed_line(l));
        }
        if let Some(Some(s)) = &self.style.t {
            bar = bar.style(s.0);
        }
        if let Some(Some(s)) = &self.value_style.t {
            bar = bar.value_style(s.0);
        }
        if let Some(Some(tv)) = &self.text_value.t {
            bar = bar.text_value(tv.to_string());
        }
        bar
    }
}

struct BarGroupW<X: GXExt> {
    label: Option<LineV>,
    bars: Vec<BarW<X>>,
}

impl<X: GXExt> BarGroupW<X> {
    async fn compile(gx: &GXHandle<X>, v: Value) -> Result<Self> {
        let [(_, bars), (_, label)] =
            v.cast_to::<[(ArcStr, Value); 2]>().context("bargroup fields")?;
        let label = label.cast_to::<Option<LineV>>()?;
        let bars = bars
            .cast_to::<SmallVec<[Value; 8]>>()?
            .into_iter()
            .map(|b| BarW::compile(gx, b));
        let bars = future::try_join_all(bars).await?;
        Ok(Self { label, bars })
    }
}

pub(super) struct BarChartW<X: GXExt> {
    gx: GXHandle<X>,
    data_ref: Ref<X>,
    data: Vec<BarGroupW<X>>,
    bar_gap: TRef<X, Option<i64>>,
    bar_style: TRef<X, Option<StyleV>>,
    bar_width: TRef<X, Option<i64>>,
    direction: TRef<X, Option<DirectionV>>,
    group_gap: TRef<X, Option<i64>>,
    label_style: TRef<X, Option<StyleV>>,
    max: TRef<X, Option<i64>>,
    style: TRef<X, Option<StyleV>>,
    value_style: TRef<X, Option<StyleV>>,
    last_warned_bar_gap: Option<i64>,
    last_warned_bar_width: Option<i64>,
    last_warned_group_gap: Option<i64>,
    last_warned_max: Option<i64>,
}

impl<X: GXExt> BarChartW<X> {
    pub(super) async fn compile(gx: GXHandle<X>, v: Value) -> Result<TuiW> {
        let flds = v.cast_to::<[(ArcStr, u64); 10]>().context("barchart fields")?;
        let [(_, bar_gap), (_, bar_style), (_, bar_width), (_, data), (_, direction), (_, group_gap), (_, label_style), (_, max), (_, style), (_, value_style)] =
            flds;
        let (
            bar_gap,
            bar_style,
            bar_width,
            data_ref,
            direction,
            group_gap,
            label_style,
            max,
            style,
            value_style,
        ) = try_join! {
            gx.compile_ref(bar_gap),
            gx.compile_ref(bar_style),
            gx.compile_ref(bar_width),
            gx.compile_ref(data),
            gx.compile_ref(direction),
            gx.compile_ref(group_gap),
            gx.compile_ref(label_style),
            gx.compile_ref(max),
            gx.compile_ref(style),
            gx.compile_ref(value_style)
        }?;
        let bar_gap =
            TRef::<X, Option<i64>>::new(bar_gap).context("barchart tref bar_gap")?;
        let bar_style = TRef::<X, Option<StyleV>>::new(bar_style)
            .context("barchart tref bar_style")?;
        let bar_width =
            TRef::<X, Option<i64>>::new(bar_width).context("barchart tref bar_width")?;
        let direction = TRef::<X, Option<DirectionV>>::new(direction)
            .context("barchart tref direction")?;
        let group_gap =
            TRef::<X, Option<i64>>::new(group_gap).context("barchart tref group_gap")?;
        let label_style = TRef::<X, Option<StyleV>>::new(label_style)
            .context("barchart tref label_style")?;
        let max = TRef::<X, Option<i64>>::new(max).context("barchart tref max")?;
        let style =
            TRef::<X, Option<StyleV>>::new(style).context("barchart tref style")?;
        let value_style = TRef::<X, Option<StyleV>>::new(value_style)
            .context("barchart tref value_style")?;
        let mut t = Self {
            gx: gx.clone(),
            data_ref,
            data: vec![],
            bar_gap,
            bar_style,
            bar_width,
            direction,
            group_gap,
            label_style,
            max,
            style,
            value_style,
            last_warned_bar_gap: None,
            last_warned_bar_width: None,
            last_warned_group_gap: None,
            last_warned_max: None,
        };
        if let Some(v) = t.data_ref.last.take() {
            t.set_data(v).await?;
        }
        Ok(Box::new(t))
    }

    async fn set_data(&mut self, v: Value) -> Result<()> {
        let groups = v
            .cast_to::<SmallVec<[Value; 8]>>()?
            .into_iter()
            .map(|g| BarGroupW::compile(&self.gx, g));
        Ok(self.data = try_join_all(groups).await?)
    }
}

#[async_trait]
impl<X: GXExt> TuiWidget for BarChartW<X> {
    async fn handle_event(&mut self, _e: Event, _v: Value) -> Result<()> {
        Ok(())
    }

    async fn handle_update(&mut self, id: ExprId, v: Value) -> Result<()> {
        let Self {
            gx: _,
            data_ref,
            data: _,
            bar_gap,
            bar_style,
            bar_width,
            direction,
            group_gap,
            label_style,
            max,
            style,
            value_style,
            last_warned_bar_gap: _,
            last_warned_bar_width: _,
            last_warned_group_gap: _,
            last_warned_max: _,
        } = self;
        bar_gap.update(id, &v).context("barchart update bar_gap")?;
        bar_style.update(id, &v).context("barchart update bar_style")?;
        bar_width.update(id, &v).context("barchart update bar_width")?;
        direction.update(id, &v).context("barchart update direction")?;
        group_gap.update(id, &v).context("barchart update group_gap")?;
        label_style.update(id, &v).context("barchart update label_style")?;
        max.update(id, &v).context("barchart update max")?;
        style.update(id, &v).context("barchart update style")?;
        value_style.update(id, &v).context("barchart update value_style")?;
        if data_ref.id == id {
            self.set_data(v.clone()).await?;
        }
        for g in self.data.iter_mut() {
            for b in &mut g.bars {
                b.update(id, &v)?;
            }
        }
        Ok(())
    }

    fn draw(&mut self, frame: &mut Frame, rect: Rect) -> Result<()> {
        let Self {
            gx: _,
            data_ref: _,
            data,
            bar_gap,
            bar_style,
            bar_width,
            direction,
            group_gap,
            label_style,
            max,
            style,
            value_style,
            last_warned_bar_gap,
            last_warned_bar_width,
            last_warned_group_gap,
            last_warned_max,
        } = self;
        let mut chart = BarChart::default();
        if let Some(Some(g)) = bar_gap.t {
            chart = chart.bar_gap(validate::clamp_u16(
                "bar_chart",
                "bar_gap",
                last_warned_bar_gap,
                g,
            ));
        }
        if let Some(Some(w)) = bar_width.t {
            chart = chart.bar_width(validate::clamp_u16(
                "bar_chart",
                "bar_width",
                last_warned_bar_width,
                w,
            ));
        }
        if let Some(Some(s)) = &bar_style.t {
            chart = chart.bar_style(s.0);
        }
        if let Some(Some(s)) = &value_style.t {
            chart = chart.value_style(s.0);
        }
        if let Some(Some(s)) = &label_style.t {
            chart = chart.label_style(s.0);
        }
        if let Some(Some(s)) = &style.t {
            chart = chart.style(s.0);
        }
        if let Some(Some(m)) = max.t {
            chart =
                chart.max(validate::clamp_u64("bar_chart", "max", last_warned_max, m));
        }
        if let Some(Some(d)) = direction.t {
            chart = chart.direction(d.0);
        }
        if let Some(Some(gap)) = group_gap.t {
            chart = chart.group_gap(validate::clamp_u16(
                "bar_chart",
                "group_gap",
                last_warned_group_gap,
                gap,
            ));
        }
        for group in data.iter_mut() {
            let mut bars: SmallVec<[Bar; 8]> = smallvec![];
            let mut g = BarGroup::default();
            if let Some(LineV(l)) = &group.label {
                g = g.label(into_borrowed_line(l));
            }
            for bar in group.bars.iter_mut() {
                bars.push(bar.build());
            }
            let g = g.bars(&bars);
            chart = chart.data(g);
        }
        frame.render_widget(chart, rect);
        Ok(())
    }
}