Skip to main content

graphix_package_gui/widgets/
toggle.rs

1use super::{GuiW, GuiWidget, IcedElement, Message};
2use crate::types::LengthV;
3use anyhow::{Context, Result};
4use arcstr::ArcStr;
5use graphix_compiler::expr::ExprId;
6use graphix_rt::{Callable, GXExt, GXHandle, Ref, TRef};
7use iced_widget as widget;
8use netidx::{protocol::valarray::ValArray, publisher::Value};
9use tokio::try_join;
10
11/// Generate the struct, compile(), and handle_update helper for a boolean
12/// toggle widget (Checkbox or Toggler). The `$state` ident is the name
13/// of the boolean field (e.g. `is_checked` or `is_toggled`). The trait
14/// impl with view() is written separately for each widget.
15macro_rules! toggle_widget {
16    ($name:ident, $label:literal, $state:ident) => {
17        pub(crate) struct $name<X: GXExt> {
18            gx: GXHandle<X>,
19            disabled: TRef<X, bool>,
20            $state: TRef<X, bool>,
21            label: TRef<X, String>,
22            on_toggle: Ref<X>,
23            on_toggle_callable: Option<Callable<X>>,
24            width: TRef<X, LengthV>,
25            size: TRef<X, Option<f64>>,
26            spacing: TRef<X, Option<f64>>,
27        }
28
29        impl<X: GXExt> $name<X> {
30            pub(crate) async fn compile(gx: GXHandle<X>, source: Value) -> Result<GuiW<X>> {
31                let [(_, disabled), (_, $state), (_, label), (_, on_toggle), (_, size), (_, spacing), (_, width)] =
32                    source.cast_to::<[(ArcStr, u64); 7]>().context(concat!($label, " flds"))?;
33                let (disabled, $state, label, on_toggle, size, spacing, width) = try_join! {
34                    gx.compile_ref(disabled),
35                    gx.compile_ref($state),
36                    gx.compile_ref(label),
37                    gx.compile_ref(on_toggle),
38                    gx.compile_ref(size),
39                    gx.compile_ref(spacing),
40                    gx.compile_ref(width),
41                }?;
42                let callable =
43                    compile_callable!(gx, on_toggle, concat!($label, " on_toggle"));
44                Ok(Box::new(Self {
45                    gx: gx.clone(),
46                    disabled: TRef::new(disabled).context(concat!($label, " tref disabled"))?,
47                    $state: TRef::new($state).context(concat!($label, " tref ", stringify!($state)))?,
48                    label: TRef::new(label).context(concat!($label, " tref label"))?,
49                    on_toggle,
50                    on_toggle_callable: callable,
51                    width: TRef::new(width).context(concat!($label, " tref width"))?,
52                    size: TRef::new(size).context(concat!($label, " tref size"))?,
53                    spacing: TRef::new(spacing).context(concat!($label, " tref spacing"))?,
54                }))
55            }
56
57            fn do_update(
58                &mut self,
59                rt: &tokio::runtime::Handle,
60                id: ExprId,
61                v: &Value,
62            ) -> Result<bool> {
63                let mut changed = false;
64                changed |= self.disabled.update(id, v).context(concat!($label, " update disabled"))?.is_some();
65                changed |= self.$state.update(id, v).context(concat!($label, " update ", stringify!($state)))?.is_some();
66                changed |= self.label.update(id, v).context(concat!($label, " update label"))?.is_some();
67                changed |= self.width.update(id, v).context(concat!($label, " update width"))?.is_some();
68                changed |= self.size.update(id, v).context(concat!($label, " update size"))?.is_some();
69                changed |= self.spacing.update(id, v).context(concat!($label, " update spacing"))?.is_some();
70                update_callable!(self, rt, id, v, on_toggle, on_toggle_callable, concat!($label, " on_toggle recompile"));
71                Ok(changed)
72            }
73        }
74    };
75}
76
77toggle_widget!(CheckboxW, "checkbox", is_checked);
78
79impl<X: GXExt> GuiWidget<X> for CheckboxW<X> {
80    fn handle_update(
81        &mut self,
82        rt: &tokio::runtime::Handle,
83        id: ExprId,
84        v: &Value,
85    ) -> Result<bool> {
86        self.do_update(rt, id, v)
87    }
88
89    fn view(&self) -> IcedElement<'_> {
90        let label = self.label.t.as_deref().unwrap_or("");
91        let checked = self.is_checked.t.unwrap_or(false);
92        let mut cb = widget::Checkbox::new(checked).label(label);
93        if !self.disabled.t.unwrap_or(false) {
94            if let Some(callable) = &self.on_toggle_callable {
95                let id = callable.id();
96                cb = cb.on_toggle(move |b| {
97                    Message::Call(id, ValArray::from_iter([Value::from(b)]))
98                });
99            }
100        }
101        if let Some(w) = self.width.t.as_ref() {
102            cb = cb.width(w.0);
103        }
104        if let Some(Some(sz)) = self.size.t {
105            cb = cb.size(sz as f32);
106        }
107        if let Some(Some(sp)) = self.spacing.t {
108            cb = cb.spacing(sp as f32);
109        }
110        cb.into()
111    }
112}
113
114toggle_widget!(TogglerW, "toggler", is_toggled);
115
116impl<X: GXExt> GuiWidget<X> for TogglerW<X> {
117    fn handle_update(
118        &mut self,
119        rt: &tokio::runtime::Handle,
120        id: ExprId,
121        v: &Value,
122    ) -> Result<bool> {
123        self.do_update(rt, id, v)
124    }
125
126    fn view(&self) -> IcedElement<'_> {
127        let label = self.label.t.as_deref().unwrap_or("");
128        let toggled = self.is_toggled.t.unwrap_or(false);
129        let mut tg = widget::Toggler::new(toggled);
130        if !label.is_empty() {
131            tg = tg.label(label);
132        }
133        if !self.disabled.t.unwrap_or(false) {
134            if let Some(callable) = &self.on_toggle_callable {
135                let id = callable.id();
136                tg = tg.on_toggle(move |b| {
137                    Message::Call(id, ValArray::from_iter([Value::from(b)]))
138                });
139            }
140        }
141        if let Some(w) = self.width.t.as_ref() {
142            tg = tg.width(w.0);
143        }
144        if let Some(Some(sz)) = self.size.t {
145            tg = tg.size(sz as f32);
146        }
147        if let Some(Some(sp)) = self.spacing.t {
148            tg = tg.spacing(sp as f32);
149        }
150        tg.into()
151    }
152}