graphix_package_gui/widgets/
text_editor.rs1use super::{GuiW, GuiWidget, IcedElement, Message};
2use crate::types::{FontV, PaddingV};
3use anyhow::{Context, Result};
4use arcstr::ArcStr;
5use graphix_compiler::expr::ExprId;
6use graphix_rt::{Callable, CallableId, GXExt, GXHandle, Ref, TRef};
7use iced_widget::{self as widget, text_editor};
8use netidx::publisher::Value;
9use tokio::try_join;
10
11pub(crate) struct TextEditorW<X: GXExt> {
13 gx: GXHandle<X>,
14 disabled: TRef<X, bool>,
15 content: text_editor::Content,
16 content_ref: TRef<X, String>,
17 on_edit: Ref<X>,
18 on_edit_callable: Option<Callable<X>>,
19 last_set_text: Option<String>,
22 placeholder: TRef<X, String>,
23 width: TRef<X, Option<f64>>,
24 height: TRef<X, Option<f64>>,
25 padding: TRef<X, PaddingV>,
26 font: TRef<X, Option<FontV>>,
27 size: TRef<X, Option<f64>>,
28}
29
30impl<X: GXExt> TextEditorW<X> {
31 pub(crate) async fn compile(gx: GXHandle<X>, source: Value) -> Result<GuiW<X>> {
32 let [(_, content), (_, disabled), (_, font), (_, height), (_, on_edit), (_, padding), (_, placeholder), (_, size), (_, width)] =
33 source.cast_to::<[(ArcStr, u64); 9]>().context("text_editor flds")?;
34 let (content, disabled, font, height, on_edit, padding, placeholder, size, width) =
35 try_join! {
36 gx.compile_ref(content),
37 gx.compile_ref(disabled),
38 gx.compile_ref(font),
39 gx.compile_ref(height),
40 gx.compile_ref(on_edit),
41 gx.compile_ref(padding),
42 gx.compile_ref(placeholder),
43 gx.compile_ref(size),
44 gx.compile_ref(width),
45 }?;
46 let on_edit_callable =
47 compile_callable!(gx, on_edit, "text_editor on_edit");
48 let content_tref: TRef<X, String> =
49 TRef::new(content).context("text_editor tref content")?;
50 let initial_text = content_tref.t.as_deref().unwrap_or("");
51 let editor_content = text_editor::Content::with_text(initial_text);
52 Ok(Box::new(Self {
53 gx: gx.clone(),
54 disabled: TRef::new(disabled).context("text_editor tref disabled")?,
55 content: editor_content,
56 content_ref: content_tref,
57 on_edit,
58 on_edit_callable,
59 last_set_text: None,
60 placeholder: TRef::new(placeholder)
61 .context("text_editor tref placeholder")?,
62 width: TRef::new(width).context("text_editor tref width")?,
63 height: TRef::new(height).context("text_editor tref height")?,
64 padding: TRef::new(padding).context("text_editor tref padding")?,
65 font: TRef::new(font).context("text_editor tref font")?,
66 size: TRef::new(size).context("text_editor tref size")?,
67 }))
68 }
69}
70
71impl<X: GXExt> GuiWidget<X> for TextEditorW<X> {
72 fn handle_update(
73 &mut self,
74 rt: &tokio::runtime::Handle,
75 id: ExprId,
76 v: &Value,
77 ) -> Result<bool> {
78 let mut changed = false;
79 changed |=
80 self.disabled.update(id, v).context("text_editor update disabled")?.is_some();
81 if let Some(new_text) =
82 self.content_ref.update(id, v).context("text_editor update content")?
83 {
84 if self.last_set_text.take().as_ref() != Some(new_text) {
87 self.content = text_editor::Content::with_text(new_text.as_str());
88 changed = true;
89 }
90 }
91 changed |= self
92 .placeholder
93 .update(id, v)
94 .context("text_editor update placeholder")?
95 .is_some();
96 changed |=
97 self.width.update(id, v).context("text_editor update width")?.is_some();
98 changed |=
99 self.height.update(id, v).context("text_editor update height")?.is_some();
100 changed |=
101 self.padding.update(id, v).context("text_editor update padding")?.is_some();
102 changed |= self.font.update(id, v).context("text_editor update font")?.is_some();
103 changed |= self.size.update(id, v).context("text_editor update size")?.is_some();
104 update_callable!(self, rt, id, v, on_edit, on_edit_callable, "text_editor on_edit recompile");
105 Ok(changed)
106 }
107
108 fn view(&self) -> IcedElement<'_> {
109 let mut te = widget::TextEditor::new(&self.content);
110 if !self.disabled.t.unwrap_or(false) && self.on_edit_callable.is_some() {
111 let content_id = self.content_ref.r.id;
112 te = te.on_action(move |a| Message::EditorAction(content_id, a));
113 }
114 let placeholder = self.placeholder.t.as_deref().unwrap_or("");
115 if !placeholder.is_empty() {
116 te = te.placeholder(placeholder);
117 }
118 if let Some(Some(w)) = self.width.t {
119 te = te.width(w as f32);
120 }
121 if let Some(Some(h)) = self.height.t {
122 te = te.height(h as f32);
123 }
124 if let Some(p) = self.padding.t.as_ref() {
125 te = te.padding(p.0);
126 }
127 if let Some(Some(f)) = self.font.t.as_ref() {
128 te = te.font(f.0);
129 }
130 if let Some(Some(sz)) = self.size.t {
131 te = te.size(sz as f32);
132 }
133 te.into()
134 }
135
136 fn editor_action(
137 &mut self,
138 id: ExprId,
139 action: &text_editor::Action,
140 ) -> Option<(CallableId, Value)> {
141 if id != self.content_ref.r.id {
142 return None;
143 }
144 self.content.perform(action.clone());
145 if action.is_edit() {
146 if let Some(callable) = &self.on_edit_callable {
147 let text = self.content.text();
148 self.last_set_text = Some(text.clone());
149 return Some((callable.id(), Value::String(text.into())));
150 }
151 }
152 None
153 }
154}