1use anyhow::{bail, Context, Result};
2use arcstr::ArcStr;
3use compact_str::CompactString;
4use graphix_compiler::expr::ExprId;
5use graphix_rt::{CallableId, GXExt, GXHandle};
6use netidx::{protocol::valarray::ValArray, publisher::Value};
7use poolshark::local::LPooled;
8use smallvec::SmallVec;
9use std::{future::Future, pin::Pin};
10
11use crate::types::{HAlignV, LengthV, PaddingV, VAlignV};
12
13macro_rules! compile_callable {
15 ($gx:expr, $ref:ident, $label:expr) => {
16 match $ref.last.as_ref() {
17 Some(v) => Some($gx.compile_callable(v.clone()).await.context($label)?),
18 None => None,
19 }
20 };
21}
22
23macro_rules! update_callable {
25 ($self:ident, $rt:ident, $id:ident, $v:ident, $field:ident, $callable:ident, $label:expr) => {
26 if $id == $self.$field.id {
27 $self.$field.last = Some($v.clone());
28 $self.$callable = Some(
29 $rt.block_on($self.gx.compile_callable($v.clone())).context($label)?,
30 );
31 }
32 };
33}
34
35macro_rules! compile_child {
37 ($gx:expr, $ref:ident, $label:expr) => {
38 match $ref.last.as_ref() {
39 None => Box::new(super::EmptyW) as GuiW<X>,
40 Some(v) => compile($gx.clone(), v.clone()).await.context($label)?,
41 }
42 };
43}
44
45macro_rules! update_child {
48 ($self:ident, $rt:ident, $id:ident, $v:ident, $changed:ident, $ref:ident, $child:ident, $label:expr) => {
49 if $id == $self.$ref.id {
50 $self.$ref.last = Some($v.clone());
51 $self.$child =
52 $rt.block_on(compile($self.gx.clone(), $v.clone())).context($label)?;
53 $changed = true;
54 }
55 $changed |= $self.$child.handle_update($rt, $id, $v)?;
56 };
57}
58
59pub mod button;
60pub mod canvas;
61pub mod chart;
62pub mod combo_box;
63pub mod container;
64pub mod context_menu;
65pub mod context_menu_widget;
66pub mod data_table;
67pub mod grid;
68pub mod iced_keyboard_area;
69pub mod image;
70pub mod keyboard_area;
71pub mod markdown;
72pub mod menu_bar;
73pub mod menu_bar_widget;
74pub mod mouse_area;
75pub mod pick_list;
76pub mod progress_bar;
77pub mod qr_code;
78pub mod radio;
79pub mod rule;
80pub mod scrollable;
81pub mod slider;
82pub mod space;
83pub mod stack;
84pub mod table;
85pub mod text;
86pub mod text_editor;
87pub mod text_input;
88pub mod toggle;
89pub mod tooltip;
90
91pub type Renderer = iced_renderer::Renderer;
94
95pub type IcedElement<'a> =
97 iced_core::Element<'a, Message, crate::theme::GraphixTheme, Renderer>;
98
99#[derive(Debug, Clone)]
101pub enum Message {
102 Nop,
103 Call(CallableId, ValArray),
104 EditorAction(ExprId, iced_widget::text_editor::Action),
105 Scroll(f32, f32, f32, f32),
108 CellClick(usize, ArcStr),
113 CellEdit(usize, ArcStr),
115 CellEditInput(CompactString),
119 CellEditSubmit,
121 CellEditCancel,
123 ColumnResizeStart(usize),
125 ColumnResizeMove(f32),
130 ColumnResizeEnd,
132 TableKey(TableKeyAction),
134}
135
136#[derive(Debug, Clone)]
138pub enum TableKeyAction {
139 Up,
140 Down,
141 Left,
142 Right,
143 Enter,
145 Space,
147 Escape,
149}
150
151pub struct MessageShell {
156 pub cursor_position: iced_core::Point,
157 pub out: LPooled<Vec<Message>>,
158}
159
160impl MessageShell {
161 pub fn new(cursor_position: iced_core::Point) -> Self {
162 Self { cursor_position, out: LPooled::take() }
163 }
164
165 pub fn publish(&mut self, msg: Message) {
166 self.out.push(msg);
167 }
168}
169
170pub trait GuiWidget<X: GXExt>: Send + 'static {
174 fn handle_update(
178 &mut self,
179 rt: &tokio::runtime::Handle,
180 id: ExprId,
181 v: &Value,
182 ) -> Result<bool>;
183
184 fn view(&self) -> IcedElement<'_>;
186
187 fn children_mut(&mut self) -> &mut [GuiW<X>] {
194 &mut []
195 }
196
197 fn children(&self) -> &[GuiW<X>] {
198 &[]
199 }
200
201 fn on_message(&mut self, msg: &Message, shell: &mut MessageShell) -> bool {
207 let mut changed = false;
208 for child in self.children_mut() {
209 changed |= child.on_message(msg, shell);
210 }
211 changed
212 }
213
214 fn is_column_resizing(&self) -> bool {
218 self.children().iter().any(|c| c.is_column_resizing())
219 }
220
221 #[cfg(test)]
224 fn data_table_snapshot(&self) -> Option<DataTableSnapshot> {
225 None
226 }
227
228 #[cfg(test)]
232 fn as_any(&self) -> &dyn std::any::Any {
233 unimplemented!("as_any not implemented for this widget")
234 }
235
236 #[cfg(test)]
237 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
238 unimplemented!("as_any_mut not implemented for this widget")
239 }
240
241 fn before_view(&mut self) -> bool {
248 let mut changed = false;
249 for child in self.children_mut() {
250 changed |= child.before_view();
251 }
252 changed
253 }
254}
255
256pub type GuiW<X> = Box<dyn GuiWidget<X>>;
257
258#[cfg(test)]
260#[derive(Debug, Clone, PartialEq)]
261pub struct DataTableSnapshot {
262 pub col_names: Vec<String>,
263 pub row_basenames: Vec<String>,
264 pub grid: Vec<Vec<String>>,
265 pub is_value_mode: bool,
266 pub selection: Vec<String>,
267}
268
269pub type CompileFut<X> = Pin<Box<dyn Future<Output = Result<GuiW<X>>> + Send + 'static>>;
271
272pub struct EmptyW;
274
275impl<X: GXExt> GuiWidget<X> for EmptyW {
276 fn handle_update(
277 &mut self,
278 _rt: &tokio::runtime::Handle,
279 _id: ExprId,
280 _v: &Value,
281 ) -> Result<bool> {
282 Ok(false)
283 }
284
285 fn view(&self) -> IcedElement<'_> {
286 iced_widget::Space::new().into()
287 }
288}
289
290macro_rules! flex_widget {
293 ($name:ident, $label:literal,
294 $spacing:ident, $padding:ident, $width:ident, $height:ident,
295 $align_ty:ty, $align:ident, $Widget:ident, $align_set:ident,
296 [$($f:ident),+]) => {
297 pub(crate) struct $name<X: GXExt> {
298 gx: GXHandle<X>,
299 $spacing: graphix_rt::TRef<X, f64>,
300 $padding: graphix_rt::TRef<X, PaddingV>,
301 $width: graphix_rt::TRef<X, LengthV>,
302 $height: graphix_rt::TRef<X, LengthV>,
303 $align: graphix_rt::TRef<X, $align_ty>,
304 children_ref: graphix_rt::Ref<X>,
305 children: Vec<GuiW<X>>,
306 }
307
308 impl<X: GXExt> $name<X> {
309 pub(crate) async fn compile(gx: GXHandle<X>, source: Value) -> Result<GuiW<X>> {
310 let [(_, children), $((_, $f)),+] =
311 source.cast_to::<[(ArcStr, u64); 6]>()
312 .context(concat!($label, " flds"))?;
313 let (children_ref, $($f),+) = tokio::try_join!(
314 gx.compile_ref(children),
315 $(gx.compile_ref($f)),+
316 )?;
317 let compiled_children = match children_ref.last.as_ref() {
318 None => vec![],
319 Some(v) => compile_children(gx.clone(), v.clone()).await
320 .context(concat!($label, " children"))?,
321 };
322 Ok(Box::new(Self {
323 gx: gx.clone(),
324 $spacing: graphix_rt::TRef::new($spacing)
325 .context(concat!($label, " tref spacing"))?,
326 $padding: graphix_rt::TRef::new($padding)
327 .context(concat!($label, " tref padding"))?,
328 $width: graphix_rt::TRef::new($width)
329 .context(concat!($label, " tref width"))?,
330 $height: graphix_rt::TRef::new($height)
331 .context(concat!($label, " tref height"))?,
332 $align: graphix_rt::TRef::new($align)
333 .context(concat!($label, " tref ", stringify!($align)))?,
334 children_ref,
335 children: compiled_children,
336 }))
337 }
338 }
339
340 impl<X: GXExt> GuiWidget<X> for $name<X> {
341 fn children_mut(&mut self) -> &mut [GuiW<X>] {
342 &mut self.children
343 }
344
345 fn children(&self) -> &[GuiW<X>] {
346 &self.children
347 }
348
349 fn handle_update(
350 &mut self,
351 rt: &tokio::runtime::Handle,
352 id: ExprId,
353 v: &Value,
354 ) -> Result<bool> {
355 let mut changed = false;
356 changed |= self.$spacing.update(id, v)
357 .context(concat!($label, " update spacing"))?.is_some();
358 changed |= self.$padding.update(id, v)
359 .context(concat!($label, " update padding"))?.is_some();
360 changed |= self.$width.update(id, v)
361 .context(concat!($label, " update width"))?.is_some();
362 changed |= self.$height.update(id, v)
363 .context(concat!($label, " update height"))?.is_some();
364 changed |= self.$align.update(id, v)
365 .context(concat!($label, " update ", stringify!($align)))?.is_some();
366 if id == self.children_ref.id {
367 self.children_ref.last = Some(v.clone());
368 self.children = rt.block_on(
369 compile_children(self.gx.clone(), v.clone())
370 ).context(concat!($label, " children recompile"))?;
371 changed = true;
372 }
373 for child in &mut self.children {
374 changed |= child.handle_update(rt, id, v)?;
375 }
376 Ok(changed)
377 }
378
379 fn view(&self) -> IcedElement<'_> {
380 let mut w = iced_widget::$Widget::new();
381 if let Some(sp) = self.$spacing.t {
382 w = w.spacing(sp as f32);
383 }
384 if let Some(p) = self.$padding.t.as_ref() {
385 w = w.padding(p.0);
386 }
387 if let Some(wi) = self.$width.t.as_ref() {
388 w = w.width(wi.0);
389 }
390 if let Some(h) = self.$height.t.as_ref() {
391 w = w.height(h.0);
392 }
393 if let Some(a) = self.$align.t.as_ref() {
394 w = w.$align_set(a.0);
395 }
396 for child in &self.children {
397 w = w.push(child.view());
398 }
399 w.into()
400 }
401 }
402 };
403}
404
405flex_widget!(
406 RowW,
407 "row",
408 spacing,
409 padding,
410 width,
411 height,
412 VAlignV,
413 valign,
414 Row,
415 align_y,
416 [height, padding, spacing, valign, width]
417);
418
419flex_widget!(
420 ColumnW,
421 "column",
422 spacing,
423 padding,
424 width,
425 height,
426 HAlignV,
427 halign,
428 Column,
429 align_x,
430 [halign, height, padding, spacing, width]
431);
432
433pub fn compile<X: GXExt>(gx: GXHandle<X>, source: Value) -> CompileFut<X> {
436 Box::pin(async move {
437 let (s, v) = source.cast_to::<(ArcStr, Value)>()?;
438 match s.as_str() {
439 "Text" => text::TextW::compile(gx, v).await,
440 "Column" => ColumnW::compile(gx, v).await,
441 "Row" => RowW::compile(gx, v).await,
442 "Container" => container::ContainerW::compile(gx, v).await,
443 "Grid" => grid::GridW::compile(gx, v).await,
444 "Button" => button::ButtonW::compile(gx, v).await,
445 "Space" => space::SpaceW::compile(gx, v).await,
446 "TextInput" => text_input::TextInputW::compile(gx, v).await,
447 "Checkbox" => toggle::CheckboxW::compile(gx, v).await,
448 "Toggler" => toggle::TogglerW::compile(gx, v).await,
449 "Slider" => slider::SliderW::compile(gx, v).await,
450 "ProgressBar" => progress_bar::ProgressBarW::compile(gx, v).await,
451 "Scrollable" => scrollable::ScrollableW::compile(gx, v).await,
452 "HorizontalRule" => rule::HorizontalRuleW::compile(gx, v).await,
453 "VerticalRule" => rule::VerticalRuleW::compile(gx, v).await,
454 "Tooltip" => tooltip::TooltipW::compile(gx, v).await,
455 "PickList" => pick_list::PickListW::compile(gx, v).await,
456 "Stack" => stack::StackW::compile(gx, v).await,
457 "Radio" => radio::RadioW::compile(gx, v).await,
458 "VerticalSlider" => slider::VerticalSliderW::compile(gx, v).await,
459 "ComboBox" => combo_box::ComboBoxW::compile(gx, v).await,
460 "TextEditor" => text_editor::TextEditorW::compile(gx, v).await,
461 "KeyboardArea" => keyboard_area::KeyboardAreaW::compile(gx, v).await,
462 "MouseArea" => mouse_area::MouseAreaW::compile(gx, v).await,
463 "Image" => image::ImageW::compile(gx, v).await,
464 "Canvas" => canvas::CanvasW::compile(gx, v).await,
465 "ContextMenu" => context_menu::ContextMenuW::compile(gx, v).await,
466 "Chart" => chart::ChartW::compile(gx, v).await,
467 "Markdown" => markdown::MarkdownW::compile(gx, v).await,
468 "MenuBar" => menu_bar::MenuBarW::compile(gx, v).await,
469 "QrCode" => qr_code::QrCodeW::compile(gx, v).await,
470 "Table" => table::TableW::compile(gx, v).await,
471 "DataTable" => data_table::DataTableW::compile(gx, v).await,
472 _ => bail!("invalid gui widget type `{s}({v})"),
473 }
474 })
475}
476
477pub async fn compile_children<X: GXExt>(
479 gx: GXHandle<X>,
480 v: Value,
481) -> Result<Vec<GuiW<X>>> {
482 let items = v.cast_to::<SmallVec<[Value; 8]>>()?;
483 let futs: Vec<_> = items.into_iter().map(|item| compile(gx.clone(), item)).collect();
484 futures::future::try_join_all(futs).await
485}