graphix-package-gui 0.8.0

A dataflow language for UIs and network programming, GUI package
Documentation
use super::{compile, compile_children, GuiW, GuiWidget, IcedElement};
use crate::types::{HAlignV, LengthV, VAlignV};
use anyhow::{Context, Result};
use arcstr::ArcStr;
use graphix_compiler::expr::ExprId;
use graphix_rt::{GXExt, GXHandle, Ref, TRef};
use iced_widget as widget;
use netidx::publisher::Value;
use smallvec::SmallVec;
use tokio::try_join;

struct CompiledColumn<X: GXExt> {
    header_ref: Ref<X>,
    header: GuiW<X>,
    width: TRef<X, LengthV>,
    halign: TRef<X, HAlignV>,
    valign: TRef<X, VAlignV>,
}

pub(crate) struct TableW<X: GXExt> {
    gx: GXHandle<X>,
    columns_ref: Ref<X>,
    columns: Vec<CompiledColumn<X>>,
    rows_ref: Ref<X>,
    cells: Vec<Vec<GuiW<X>>>,
    width: TRef<X, LengthV>,
    padding: TRef<X, Option<f64>>,
    separator: TRef<X, Option<f64>>,
}

async fn compile_columns<X: GXExt>(
    gx: &GXHandle<X>,
    v: Value,
) -> Result<Vec<CompiledColumn<X>>> {
    let items = v.cast_to::<SmallVec<[Value; 8]>>()?;
    let mut cols = Vec::with_capacity(items.len());
    for item in items {
        let [(_, halign_id), (_, header_id), (_, valign_id), (_, width_id)] =
            item.cast_to::<[(ArcStr, u64); 4]>().context("table column flds")?;
        let (halign, header_ref, valign, width) = try_join! {
            gx.compile_ref(halign_id),
            gx.compile_ref(header_id),
            gx.compile_ref(valign_id),
            gx.compile_ref(width_id),
        }?;
        let header = match header_ref.last.as_ref() {
            None => Box::new(super::EmptyW) as GuiW<X>,
            Some(v) => {
                compile(gx.clone(), v.clone()).await.context("table column header")?
            }
        };
        cols.push(CompiledColumn {
            header_ref,
            header,
            width: TRef::new(width).context("table column tref width")?,
            halign: TRef::new(halign).context("table column tref halign")?,
            valign: TRef::new(valign).context("table column tref valign")?,
        });
    }
    Ok(cols)
}

async fn compile_rows<X: GXExt>(gx: &GXHandle<X>, v: Value) -> Result<Vec<Vec<GuiW<X>>>> {
    let rows = v.cast_to::<SmallVec<[Value; 8]>>()?;
    let mut result = Vec::with_capacity(rows.len());
    for row in rows {
        let cells = compile_children(gx.clone(), row).await.context("table row")?;
        result.push(cells);
    }
    Ok(result)
}

impl<X: GXExt> TableW<X> {
    pub(crate) async fn compile(gx: GXHandle<X>, source: Value) -> Result<GuiW<X>> {
        let [(_, columns), (_, padding), (_, rows), (_, separator), (_, width)] =
            source.cast_to::<[(ArcStr, u64); 5]>().context("table flds")?;
        let (columns_ref, padding, rows_ref, separator, width) = try_join! {
            gx.compile_ref(columns),
            gx.compile_ref(padding),
            gx.compile_ref(rows),
            gx.compile_ref(separator),
            gx.compile_ref(width),
        }?;
        let compiled_columns = match columns_ref.last.as_ref() {
            None => vec![],
            Some(v) => compile_columns(&gx, v.clone()).await.context("table columns")?,
        };
        let compiled_rows = match rows_ref.last.as_ref() {
            None => vec![],
            Some(v) => compile_rows(&gx, v.clone()).await.context("table rows")?,
        };
        Ok(Box::new(Self {
            gx: gx.clone(),
            columns_ref,
            columns: compiled_columns,
            rows_ref,
            cells: compiled_rows,
            width: TRef::new(width).context("table tref width")?,
            padding: TRef::new(padding).context("table tref padding")?,
            separator: TRef::new(separator).context("table tref separator")?,
        }))
    }
}

impl<X: GXExt> GuiWidget<X> for TableW<X> {
    fn handle_update(
        &mut self,
        rt: &tokio::runtime::Handle,
        id: ExprId,
        v: &Value,
    ) -> Result<bool> {
        let mut changed = false;
        changed |= self.width.update(id, v).context("table update width")?.is_some();
        changed |= self.padding.update(id, v).context("table update padding")?.is_some();
        changed |=
            self.separator.update(id, v).context("table update separator")?.is_some();
        if id == self.columns_ref.id {
            self.columns_ref.last = Some(v.clone());
            self.columns = rt
                .block_on(compile_columns(&self.gx, v.clone()))
                .context("table columns recompile")?;
            changed = true;
        }
        for col in &mut self.columns {
            if id == col.header_ref.id {
                col.header_ref.last = Some(v.clone());
                col.header = rt
                    .block_on(compile(self.gx.clone(), v.clone()))
                    .context("table column header recompile")?;
                changed = true;
            }
            changed |= col.header.handle_update(rt, id, v)?;
            changed |=
                col.width.update(id, v).context("table col update width")?.is_some();
            changed |=
                col.halign.update(id, v).context("table col update halign")?.is_some();
            changed |=
                col.valign.update(id, v).context("table col update valign")?.is_some();
        }
        if id == self.rows_ref.id {
            self.rows_ref.last = Some(v.clone());
            self.cells = rt
                .block_on(compile_rows(&self.gx, v.clone()))
                .context("table rows recompile")?;
            changed = true;
        }
        for row in &mut self.cells {
            for cell in row {
                changed |= cell.handle_update(rt, id, v)?;
            }
        }
        Ok(changed)
    }

    fn on_message(
        &mut self,
        msg: &super::Message,
        shell: &mut super::MessageShell,
    ) -> bool {
        // `table` has two child groups (header row + data cells) that
        // don't fit a single `&mut [GuiW<X>]`, so we forward manually
        // instead of implementing `children_mut`.
        let mut changed = false;
        for col in &mut self.columns {
            changed |= col.header.on_message(msg, shell);
        }
        for row in &mut self.cells {
            for cell in row {
                changed |= cell.on_message(msg, shell);
            }
        }
        changed
    }

    fn view(&self) -> IcedElement<'_> {
        let num_rows = self.cells.len();
        let cells = &self.cells;
        let cols = self.columns.iter().enumerate().map(|(c, col)| {
            let header = col.header.view();
            let mut tc = widget::table::column(header, move |row: usize| {
                if row < cells.len() && c < cells[row].len() {
                    cells[row][c].view()
                } else {
                    iced_widget::Space::new().into()
                }
            });
            if let Some(w) = col.width.t.as_ref() {
                tc = tc.width(w.0);
            }
            if let Some(a) = col.halign.t.as_ref() {
                tc = tc.align_x(a.0);
            }
            if let Some(a) = col.valign.t.as_ref() {
                tc = tc.align_y(a.0);
            }
            tc
        });
        let mut t = widget::table::table(cols, 0..num_rows);
        if let Some(w) = self.width.t.as_ref() {
            t = t.width(w.0);
        }
        if let Some(Some(p)) = self.padding.t {
            t = t.padding(p as f32);
        }
        if let Some(Some(s)) = self.separator.t {
            t = t.separator(s as f32);
        }
        t.into()
    }
}