graphix_package_gui/widgets/
toggle.rs1use 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
11macro_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}