Skip to main content

graphix_package_gui/widgets/
table.rs

1use super::{compile, compile_children, GuiW, GuiWidget, IcedElement};
2use crate::types::{HAlignV, LengthV, VAlignV};
3use anyhow::{Context, Result};
4use arcstr::ArcStr;
5use graphix_compiler::expr::ExprId;
6use graphix_rt::{CallableId, GXExt, GXHandle, Ref, TRef};
7use iced_widget as widget;
8use netidx::publisher::Value;
9use smallvec::SmallVec;
10use tokio::try_join;
11
12struct CompiledColumn<X: GXExt> {
13    header_ref: Ref<X>,
14    header: GuiW<X>,
15    width: TRef<X, LengthV>,
16    halign: TRef<X, HAlignV>,
17    valign: TRef<X, VAlignV>,
18}
19
20pub(crate) struct TableW<X: GXExt> {
21    gx: GXHandle<X>,
22    columns_ref: Ref<X>,
23    columns: Vec<CompiledColumn<X>>,
24    rows_ref: Ref<X>,
25    cells: Vec<Vec<GuiW<X>>>,
26    width: TRef<X, LengthV>,
27    padding: TRef<X, Option<f64>>,
28    separator: TRef<X, Option<f64>>,
29}
30
31async fn compile_columns<X: GXExt>(
32    gx: &GXHandle<X>,
33    v: Value,
34) -> Result<Vec<CompiledColumn<X>>> {
35    let items = v.cast_to::<SmallVec<[Value; 8]>>()?;
36    let mut cols = Vec::with_capacity(items.len());
37    for item in items {
38        let [(_, halign_id), (_, header_id), (_, valign_id), (_, width_id)] =
39            item.cast_to::<[(ArcStr, u64); 4]>().context("table column flds")?;
40        let (halign, header_ref, valign, width) = try_join! {
41            gx.compile_ref(halign_id),
42            gx.compile_ref(header_id),
43            gx.compile_ref(valign_id),
44            gx.compile_ref(width_id),
45        }?;
46        let header = match header_ref.last.as_ref() {
47            None => Box::new(super::EmptyW) as GuiW<X>,
48            Some(v) => compile(gx.clone(), v.clone()).await.context("table column header")?,
49        };
50        cols.push(CompiledColumn {
51            header_ref,
52            header,
53            width: TRef::new(width).context("table column tref width")?,
54            halign: TRef::new(halign).context("table column tref halign")?,
55            valign: TRef::new(valign).context("table column tref valign")?,
56        });
57    }
58    Ok(cols)
59}
60
61async fn compile_rows<X: GXExt>(
62    gx: &GXHandle<X>,
63    v: Value,
64) -> Result<Vec<Vec<GuiW<X>>>> {
65    let rows = v.cast_to::<SmallVec<[Value; 8]>>()?;
66    let mut result = Vec::with_capacity(rows.len());
67    for row in rows {
68        let cells = compile_children(gx.clone(), row).await.context("table row")?;
69        result.push(cells);
70    }
71    Ok(result)
72}
73
74impl<X: GXExt> TableW<X> {
75    pub(crate) async fn compile(gx: GXHandle<X>, source: Value) -> Result<GuiW<X>> {
76        let [(_, columns), (_, padding), (_, rows), (_, separator), (_, width)] =
77            source.cast_to::<[(ArcStr, u64); 5]>().context("table flds")?;
78        let (columns_ref, padding, rows_ref, separator, width) = try_join! {
79            gx.compile_ref(columns),
80            gx.compile_ref(padding),
81            gx.compile_ref(rows),
82            gx.compile_ref(separator),
83            gx.compile_ref(width),
84        }?;
85        let compiled_columns = match columns_ref.last.as_ref() {
86            None => vec![],
87            Some(v) => compile_columns(&gx, v.clone()).await.context("table columns")?,
88        };
89        let compiled_rows = match rows_ref.last.as_ref() {
90            None => vec![],
91            Some(v) => compile_rows(&gx, v.clone()).await.context("table rows")?,
92        };
93        Ok(Box::new(Self {
94            gx: gx.clone(),
95            columns_ref,
96            columns: compiled_columns,
97            rows_ref,
98            cells: compiled_rows,
99            width: TRef::new(width).context("table tref width")?,
100            padding: TRef::new(padding).context("table tref padding")?,
101            separator: TRef::new(separator).context("table tref separator")?,
102        }))
103    }
104}
105
106impl<X: GXExt> GuiWidget<X> for TableW<X> {
107    fn handle_update(
108        &mut self,
109        rt: &tokio::runtime::Handle,
110        id: ExprId,
111        v: &Value,
112    ) -> Result<bool> {
113        let mut changed = false;
114        changed |= self.width.update(id, v).context("table update width")?.is_some();
115        changed |= self.padding.update(id, v).context("table update padding")?.is_some();
116        changed |=
117            self.separator.update(id, v).context("table update separator")?.is_some();
118        if id == self.columns_ref.id {
119            self.columns_ref.last = Some(v.clone());
120            self.columns = rt
121                .block_on(compile_columns(&self.gx, v.clone()))
122                .context("table columns recompile")?;
123            changed = true;
124        }
125        for col in &mut self.columns {
126            if id == col.header_ref.id {
127                col.header_ref.last = Some(v.clone());
128                col.header = rt
129                    .block_on(compile(self.gx.clone(), v.clone()))
130                    .context("table column header recompile")?;
131                changed = true;
132            }
133            changed |= col.header.handle_update(rt, id, v)?;
134            changed |=
135                col.width.update(id, v).context("table col update width")?.is_some();
136            changed |=
137                col.halign.update(id, v).context("table col update halign")?.is_some();
138            changed |=
139                col.valign.update(id, v).context("table col update valign")?.is_some();
140        }
141        if id == self.rows_ref.id {
142            self.rows_ref.last = Some(v.clone());
143            self.cells = rt
144                .block_on(compile_rows(&self.gx, v.clone()))
145                .context("table rows recompile")?;
146            changed = true;
147        }
148        for row in &mut self.cells {
149            for cell in row {
150                changed |= cell.handle_update(rt, id, v)?;
151            }
152        }
153        Ok(changed)
154    }
155
156    fn editor_action(
157        &mut self,
158        id: ExprId,
159        action: &iced_widget::text_editor::Action,
160    ) -> Option<(CallableId, Value)> {
161        for col in &mut self.columns {
162            if let some @ Some(_) = col.header.editor_action(id, action) {
163                return some;
164            }
165        }
166        for row in &mut self.cells {
167            for cell in row {
168                if let some @ Some(_) = cell.editor_action(id, action) {
169                    return some;
170                }
171            }
172        }
173        None
174    }
175
176    fn view(&self) -> IcedElement<'_> {
177        let num_rows = self.cells.len();
178        let cells = &self.cells;
179        let cols = self.columns.iter().enumerate().map(|(c, col)| {
180            let header = col.header.view();
181            let mut tc = widget::table::column(header, move |row: usize| {
182                if row < cells.len() && c < cells[row].len() {
183                    cells[row][c].view()
184                } else {
185                    iced_widget::Space::new().into()
186                }
187            });
188            if let Some(w) = col.width.t.as_ref() {
189                tc = tc.width(w.0);
190            }
191            if let Some(a) = col.halign.t.as_ref() {
192                tc = tc.align_x(a.0);
193            }
194            if let Some(a) = col.valign.t.as_ref() {
195                tc = tc.align_y(a.0);
196            }
197            tc
198        });
199        let mut t = widget::table::table(cols, 0..num_rows);
200        if let Some(w) = self.width.t.as_ref() {
201            t = t.width(w.0);
202        }
203        if let Some(Some(p)) = self.padding.t {
204            t = t.padding(p as f32);
205        }
206        if let Some(Some(s)) = self.separator.t {
207            t = t.separator(s as f32);
208        }
209        t.into()
210    }
211}