graphix-package-tui 0.9.0

A dataflow language for UIs and network programming, TUI package
Documentation
use super::{
    into_borrowed_lines, validate, AlignmentV, LinesV, ScrollV, StyleV, TRef, TuiW,
    TuiWidget,
};
use anyhow::{Context, Result};
use arcstr::ArcStr;
use async_trait::async_trait;
use crossterm::event::Event;
use graphix_compiler::expr::ExprId;
use graphix_rt::{GXExt, GXHandle};
use netidx::publisher::Value;
use ratatui::{
    layout::Rect,
    widgets::{Paragraph, Wrap},
    Frame,
};
use tokio::try_join;

pub(super) struct ParagraphW<X: GXExt> {
    alignment: TRef<X, Option<AlignmentV>>,
    lines: TRef<X, LinesV>,
    scroll: TRef<X, ScrollV>,
    style: TRef<X, StyleV>,
    trim: TRef<X, bool>,
    last_warned_scroll_x: Option<i64>,
    last_warned_scroll_y: Option<i64>,
}

impl<X: GXExt> ParagraphW<X> {
    pub(super) async fn compile(gx: GXHandle<X>, source: Value) -> Result<TuiW> {
        let [(_, alignment), (_, lines), (_, scroll), (_, style), (_, trim)] =
            source.cast_to::<[(ArcStr, u64); 5]>().context("paragraph flds")?;
        let (alignment, lines, scroll, style, trim) = try_join! {
            gx.compile_ref(alignment),
            gx.compile_ref(lines),
            gx.compile_ref(scroll),
            gx.compile_ref(style),
            gx.compile_ref(trim)
        }?;
        let alignment: TRef<X, Option<AlignmentV>> =
            TRef::new(alignment).context("paragraph tref alignment")?;
        let lines: TRef<X, LinesV> = TRef::new(lines).context("paragraph tref lines")?;
        let scroll: TRef<X, ScrollV> =
            TRef::new(scroll).context("paragraph tref scroll")?;
        let style: TRef<X, StyleV> = TRef::new(style).context("paragraph tref style")?;
        let trim: TRef<X, bool> = TRef::new(trim).context("paragraph tref trim")?;
        Ok(Box::new(Self {
            alignment,
            lines,
            scroll,
            style,
            trim,
            last_warned_scroll_x: None,
            last_warned_scroll_y: None,
        }))
    }
}

#[async_trait]
impl<X: GXExt> TuiWidget for ParagraphW<X> {
    fn draw(&mut self, frame: &mut Frame, rect: Rect) -> Result<()> {
        let lines = self.lines.t.as_ref().map(|l| &l.0[..]).unwrap_or(&[]);
        let mut p = Paragraph::new(into_borrowed_lines(lines));
        if let Some(Some(a)) = self.alignment.t {
            p = p.alignment(a.0);
        }
        if let Some(s) = self.style.t {
            p = p.style(s.0);
        }
        if let Some(trim) = self.trim.t {
            p = p.wrap(Wrap { trim });
        }
        if let Some(s) = self.scroll.t {
            // ScrollV stores (y, x) as i64 pair; ratatui wants (u16, u16).
            // Out-of-range values clamp to the visual cap with one warn
            // per distinct bad value.
            let y = validate::clamp_u16(
                "paragraph",
                "scroll.y",
                &mut self.last_warned_scroll_y,
                s.0.0,
            );
            let x = validate::clamp_u16(
                "paragraph",
                "scroll.x",
                &mut self.last_warned_scroll_x,
                s.0.1,
            );
            p = p.scroll((y, x))
        }
        frame.render_widget(p, rect);
        Ok(())
    }

    async fn handle_event(&mut self, _: Event, _: Value) -> Result<()> {
        Ok(())
    }

    async fn handle_update(&mut self, id: ExprId, v: Value) -> Result<()> {
        let Self {
            alignment,
            lines,
            scroll,
            style,
            trim,
            last_warned_scroll_x: _,
            last_warned_scroll_y: _,
        } = self;
        alignment.update(id, &v).context("paragraph update alignment")?;
        lines.update(id, &v).context("paragraph update lines")?;
        scroll.update(id, &v).context("paragraph update scroll")?;
        style.update(id, &v).context("paragraph update style")?;
        trim.update(id, &v).context("paragraph update trim")?;
        Ok(())
    }
}