zng_wgt/func.rs
1use std::{any::TypeId, fmt, ops, sync::Arc};
2
3use crate::prelude::*;
4
5use zng_app::event::{CommandMetaVar, CommandMetaVarId};
6use zng_var::AnyVar;
7#[doc(hidden)]
8pub use zng_wgt::prelude::clmv as __clmv;
9
10type BoxedWgtFn<D> = Box<dyn Fn(D) -> BoxedUiNode + Send + Sync>;
11
12/// Boxed shared closure that generates a widget for a given data.
13///
14/// You can also use the [`wgt_fn!`] macro do instantiate.
15///
16/// See `presenter` for a way to quickly use the widget function in the UI.
17pub struct WidgetFn<D: ?Sized>(Option<Arc<BoxedWgtFn<D>>>);
18impl<D> Clone for WidgetFn<D> {
19 fn clone(&self) -> Self {
20 WidgetFn(self.0.clone())
21 }
22}
23impl<D> fmt::Debug for WidgetFn<D> {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "WidgetFn<{}>", pretty_type_name::pretty_type_name::<D>())
26 }
27}
28impl<D> PartialEq for WidgetFn<D> {
29 fn eq(&self, other: &Self) -> bool {
30 match (&self.0, &other.0) {
31 (None, None) => true,
32 (Some(a), Some(b)) => Arc::ptr_eq(a, b),
33 _ => false,
34 }
35 }
36}
37impl<D> Default for WidgetFn<D> {
38 /// `nil`.
39 fn default() -> Self {
40 Self::nil()
41 }
42}
43impl<D> WidgetFn<D> {
44 /// New from a closure that generates a node from data.
45 pub fn new<U: UiNode>(func: impl Fn(D) -> U + Send + Sync + 'static) -> Self {
46 WidgetFn(Some(Arc::new(Box::new(move |data| func(data).boxed()))))
47 }
48
49 /// Function that always produces the [`NilUiNode`].
50 ///
51 /// No heap allocation happens to create this value.
52 ///
53 /// [`NilUiNode`]: zng_app::widget::node::NilUiNode
54 pub const fn nil() -> Self {
55 WidgetFn(None)
56 }
57
58 /// If this is the [`nil`] function.
59 ///
60 /// If `true` the function always generates a node that is [`UiNode::is_nil`], if
61 /// `false` the function may still return a nil node some of the time.
62 ///
63 /// See [`call_checked`] for more details.
64 ///
65 /// [`nil`]: WidgetFn::nil
66 /// [`call_checked`]: Self::call_checked
67 /// [`UiNode::is_nil`]: zng_app::widget::node::UiNode::is_nil
68 pub fn is_nil(&self) -> bool {
69 self.0.is_none()
70 }
71
72 /// Calls the function with `data` argument.
73 ///
74 /// Note that you can call the widget function directly where `D: 'static`:
75 ///
76 /// ```
77 /// # use zng_wgt::WidgetFn;
78 /// fn foo(func: &WidgetFn<bool>) {
79 /// let a = func.call(true);
80 /// let b = func(true);
81 /// }
82 /// ```
83 ///
84 /// In the example above `a` and `b` are both calls to the widget function.
85 pub fn call(&self, data: D) -> BoxedUiNode {
86 if let Some(g) = &self.0 { g(data) } else { NilUiNode.boxed() }
87 }
88
89 /// Calls the function with `data` argument and only returns a node if is not nil.
90 ///
91 /// Returns `None` if [`is_nil`] or [`UiNode::is_nil`].
92 ///
93 /// [`is_nil`]: Self::is_nil
94 /// [`UiNode::is_nil`]: zng_app::widget::node::UiNode::is_nil
95 pub fn call_checked(&self, data: D) -> Option<BoxedUiNode> {
96 let r = self.0.as_ref()?(data);
97 if r.is_nil() { None } else { Some(r) }
98 }
99
100 /// New widget function that returns the same `widget` for every call.
101 ///
102 /// The `widget` is wrapped in an [`ArcNode`] and every function call returns an [`ArcNode::take_on_init`] node.
103 /// Note that `take_on_init` is not always the `widget` on init as it needs to wait for it to deinit first if
104 /// it is already in use, this could have an effect if the widget function caller always expects a full widget.
105 ///
106 /// [`ArcNode`]: zng_app::widget::node::ArcNode
107 /// [`ArcNode::take_on_init`]: zng_app::widget::node::ArcNode::take_on_init
108 pub fn singleton(widget: impl UiNode) -> Self {
109 let widget = ArcNode::new(widget);
110 Self::new(move |_| widget.take_on_init())
111 }
112
113 /// Creates a [`WeakWidgetFn<D>`] reference to this function.
114 pub fn downgrade(&self) -> WeakWidgetFn<D> {
115 match &self.0 {
116 Some(f) => WeakWidgetFn(Arc::downgrade(f)),
117 None => WeakWidgetFn::nil(),
118 }
119 }
120}
121impl<D: 'static> ops::Deref for WidgetFn<D> {
122 type Target = dyn Fn(D) -> BoxedUiNode;
123
124 fn deref(&self) -> &Self::Target {
125 match self.0.as_ref() {
126 Some(f) => &**f,
127 None => &nil_call::<D>,
128 }
129 }
130}
131fn nil_call<D>(_: D) -> BoxedUiNode {
132 NilUiNode.boxed()
133}
134
135/// Weak reference to a [`WidgetFn<D>`].
136pub struct WeakWidgetFn<D>(std::sync::Weak<BoxedWgtFn<D>>);
137impl<D> WeakWidgetFn<D> {
138 /// New weak reference to nil.
139 pub const fn nil() -> Self {
140 WeakWidgetFn(std::sync::Weak::new())
141 }
142
143 /// If this weak reference only upgrades to a nil function.
144 pub fn is_nil(&self) -> bool {
145 self.0.strong_count() == 0
146 }
147
148 /// Upgrade to strong reference if it still exists or nil.
149 pub fn upgrade(&self) -> WidgetFn<D> {
150 match self.0.upgrade() {
151 Some(f) => WidgetFn(Some(f)),
152 None => WidgetFn::nil(),
153 }
154 }
155}
156
157/// <span data-del-macro-root></span> Declares a widget function closure.
158///
159/// The output type is a [`WidgetFn`], the closure is [`clmv!`].
160///
161/// # Syntax
162///
163/// * `wgt_fn!(cloned, |_args| Wgt!())` - Clone-move closure, the same syntax as [`clmv!`] you can
164/// list the cloned values before the closure.
165/// * `wgt_fn!(path::to::func)` - The macro also accepts unction, the signature must receive the args and return
166/// a widget.
167/// * `wgt_fn!()` - An empty call generates the [`WidgetFn::nil()`] value.
168///
169/// # Examples
170///
171/// Declares a basic widget function that ignores the argument and does not capture any value:
172///
173/// ```
174/// # zng_wgt::enable_widget_macros!();
175/// # use zng_wgt::{prelude::*, Wgt, on_init};
176/// #
177/// # fn main() {
178/// # let wgt: WidgetFn<bool> =
179/// wgt_fn!(|_| Wgt! {
180/// on_init = hn!(|_| println!("generated widget init"));
181/// });
182/// # ; }
183/// ```
184///
185/// The macro is clone-move, meaning you can use the same syntax as [`clmv!`] to capture clones of values:
186///
187/// ```
188/// # zng_wgt::enable_widget_macros!();
189/// # use zng_wgt::{prelude::*, Wgt};
190/// # fn main() {
191/// let moved_var = var('a');
192/// let cloned_var = var('b');
193///
194/// # let wgt: WidgetFn<bool> =
195/// wgt_fn!(cloned_var, |args| {
196/// println!(
197/// "wgt_fn, args: {:?}, moved_var: {}, cloned_var: {}",
198/// args,
199/// moved_var.get(),
200/// cloned_var.get()
201/// );
202/// Wgt!()
203/// });
204/// # ; }
205/// ```
206///
207/// [`clmv!`]: zng_clone_move::clmv
208#[macro_export]
209macro_rules! wgt_fn {
210 ($fn:path) => {
211 $crate::WidgetFn::new($fn)
212 };
213 ($($tt:tt)+) => {
214 $crate::WidgetFn::new($crate::__clmv! {
215 $($tt)+
216 })
217 };
218 () => {
219 $crate::WidgetFn::nil()
220 };
221}
222
223/// Service that provides editor widgets for a given variable.
224///
225/// Auto generating widgets such as a settings list or a properties list can use this
226/// service to instantiate widgets for each item.
227///
228/// The main crate registers some common editors.
229pub struct EDITORS;
230impl EDITORS {
231 /// Register an `editor` handler.
232 ///
233 /// The handler must return [`NilUiNode`] if it cannot handle the request. Later added handlers are called first.
234 pub fn register(&self, editor: WidgetFn<EditorRequestArgs>) {
235 if !editor.is_nil() {
236 UPDATES
237 .run(async move {
238 EDITORS_SV.write().push(editor);
239 })
240 .perm();
241 }
242 }
243
244 /// Register an `editor` handler to be called if none of the `register` editors can handle the value.
245 ///
246 /// The handler must return [`NilUiNode`] if it cannot handle the request. Later added handlers are called last.
247 pub fn register_fallback(&self, editor: WidgetFn<EditorRequestArgs>) {
248 if !editor.is_nil() {
249 UPDATES
250 .run(async move {
251 EDITORS_SV.write().push_fallback(editor);
252 })
253 .perm();
254 }
255 }
256
257 /// Instantiate an editor for the `value`.
258 ///
259 /// Returns [`NilUiNode`] if no registered editor can handle the value type.
260 pub fn get(&self, value: impl AnyVar) -> BoxedUiNode {
261 EDITORS_SV.read().get(EditorRequestArgs { value: Box::new(value) })
262 }
263
264 /// Same as [`get`], but also logs an error is there are no available editor for the type.
265 ///
266 /// [`get`]: Self::get
267 pub fn req<T: VarValue>(&self, value: impl Var<T>) -> BoxedUiNode {
268 let e = self.get(value);
269 if e.is_nil() {
270 tracing::error!("no editor available for `{}`", std::any::type_name::<T>())
271 }
272 e
273 }
274}
275
276/// Service that provides icon drawing widgets.
277///
278/// This service enables widgets to use icons in an optional way, without needing to bundle icon resources. It
279/// also enables app wide icon theming.
280pub struct ICONS;
281impl ICONS {
282 /// Register an `icon` handler.
283 ///
284 /// The handler must return [`NilUiNode`] if it cannot handle the request. Later added handlers are called first.
285 pub fn register(&self, icon: WidgetFn<IconRequestArgs>) {
286 if !icon.is_nil() {
287 UPDATES
288 .run(async move {
289 ICONS_SV.write().push(icon);
290 })
291 .perm();
292 }
293 }
294
295 /// Register an `icon` handler to be called if none of the `register` handlers can handle request.
296 ///
297 /// The handler must return [`NilUiNode`] if it cannot handle the request. Later added handlers are called last.
298 pub fn register_fallback(&self, icon: WidgetFn<IconRequestArgs>) {
299 if !icon.is_nil() {
300 UPDATES
301 .run(async move {
302 ICONS_SV.write().push_fallback(icon);
303 })
304 .perm();
305 }
306 }
307
308 /// Instantiate an icon drawing widget for the `icon_name`.
309 ///
310 /// Returns [`NilUiNode`] if no registered handler can provide an icon.
311 pub fn get(&self, icon_name: impl IconNames) -> BoxedUiNode {
312 self.get_impl(&mut icon_name.names())
313 }
314 fn get_impl(&self, names: &mut dyn Iterator<Item = Txt>) -> BoxedUiNode {
315 let sv = ICONS_SV.read();
316 for name in names {
317 let node = sv.get(IconRequestArgs { name });
318 if !node.is_nil() {
319 return node;
320 }
321 }
322 NilUiNode.boxed()
323 }
324
325 /// Instantiate an icon drawing widget for the `icon_name` or call `fallback` to do it
326 /// if no handler can handle the request.
327 pub fn get_or<U: UiNode>(&self, icon_name: impl IconNames, fallback: impl FnOnce() -> U) -> BoxedUiNode {
328 let i = self.get(icon_name);
329 if i.is_nil() { fallback().boxed() } else { i }
330 }
331
332 /// Same as [`get`], but also logs an error is there are no available icon for any of the names.
333 ///
334 /// [`get`]: Self::get
335 pub fn req(&self, icon_name: impl IconNames) -> BoxedUiNode {
336 self.req_impl(&mut icon_name.names())
337 }
338 fn req_impl(&self, names: &mut dyn Iterator<Item = Txt>) -> BoxedUiNode {
339 let sv = ICONS_SV.read();
340 let mut missing = vec![];
341 for name in names {
342 let node = sv.get(IconRequestArgs { name: name.clone() });
343 if !node.is_nil() {
344 return node;
345 } else {
346 missing.push(name);
347 }
348 }
349 tracing::error!("no icon available for {missing:?}");
350 NilUiNode.boxed()
351 }
352
353 //// Same as [`get_or`], but also logs an error is there are no available icon for any of the names.
354 ///
355 /// [`get_or`]: Self::get_or
356 pub fn req_or<U: UiNode>(&self, icon_name: impl IconNames, fallback: impl FnOnce() -> U) -> BoxedUiNode {
357 let i = self.req(icon_name);
358 if i.is_nil() { fallback().boxed() } else { i }
359 }
360}
361
362/// Adapter for [`ICONS`] queries.
363///
364/// Can be `"name"` or `["name", "fallback-name1"]` names.
365pub trait IconNames {
366 /// Iterate over names, from most wanted to least.
367 fn names(self) -> impl Iterator<Item = Txt>;
368}
369impl IconNames for &'static str {
370 fn names(self) -> impl Iterator<Item = Txt> {
371 [Txt::from(self)].into_iter()
372 }
373}
374impl IconNames for Txt {
375 fn names(self) -> impl Iterator<Item = Txt> {
376 [self].into_iter()
377 }
378}
379impl IconNames for Vec<Txt> {
380 fn names(self) -> impl Iterator<Item = Txt> {
381 self.into_iter()
382 }
383}
384impl IconNames for &[Txt] {
385 fn names(self) -> impl Iterator<Item = Txt> {
386 self.iter().cloned()
387 }
388}
389impl IconNames for &[&'static str] {
390 fn names(self) -> impl Iterator<Item = Txt> {
391 self.iter().copied().map(Txt::from)
392 }
393}
394impl<const N: usize> IconNames for [&'static str; N] {
395 fn names(self) -> impl Iterator<Item = Txt> {
396 self.into_iter().map(Txt::from)
397 }
398}
399
400/// Adds the [`icon`](CommandIconExt::icon) command metadata.
401///
402/// The value is an [`WidgetFn<()>`] that can generate any icon widget, the [`ICONS`] service is recommended.
403///
404/// [`WidgetFn<()>`]: WidgetFn
405pub trait CommandIconExt {
406 /// Gets a read-write variable that is the icon for the command.
407 fn icon(self) -> CommandMetaVar<WidgetFn<()>>;
408
409 /// Sets the initial icon if it is not set.
410 fn init_icon(self, icon: WidgetFn<()>) -> Self;
411}
412static_id! {
413 static ref COMMAND_ICON_ID: CommandMetaVarId<WidgetFn<()>>;
414}
415impl CommandIconExt for Command {
416 fn icon(self) -> CommandMetaVar<WidgetFn<()>> {
417 self.with_meta(|m| m.get_var_or_default(*COMMAND_ICON_ID))
418 }
419
420 fn init_icon(self, icon: WidgetFn<()>) -> Self {
421 self.with_meta(|m| m.init_var(*COMMAND_ICON_ID, icon));
422 self
423 }
424}
425
426/// Arguments for [`EDITORS.register`].
427///
428/// Note that the handler is usually called in the widget context that will host the editor, so context
429/// variables and services my also be available to inform the editor preferences.
430///
431/// [`EDITORS.register`]: EDITORS::register
432#[derive(Clone)]
433pub struct EditorRequestArgs {
434 value: Box<dyn AnyVar>,
435}
436impl EditorRequestArgs {
437 /// The value variable.
438 pub fn value_any(&self) -> &dyn AnyVar {
439 &self.value
440 }
441
442 /// Try to downcast the value variable to `T`.
443 pub fn value<T: VarValue>(&self) -> Option<BoxedVar<T>> {
444 if self.value.var_type_id() == TypeId::of::<T>() {
445 let value = *self.value.clone_any().double_boxed_any().downcast::<BoxedVar<T>>().ok()?;
446 return Some(value);
447 }
448 None
449 }
450}
451
452/// Arguments for [`ICONS.register`].
453///
454/// Note that the handler is usually called in the widget context that will host the editor, so context
455/// variables and services my also be available to inform the editor preferences.
456///
457/// [`ICONS.register`]: ICONS::register
458#[derive(Clone)]
459pub struct IconRequestArgs {
460 name: Txt,
461}
462impl IconRequestArgs {
463 /// Icon unique name,
464 pub fn name(&self) -> &str {
465 &self.name
466 }
467}
468
469app_local! {
470 static EDITORS_SV: WidgetProviderService<EditorRequestArgs> = const { WidgetProviderService::new() };
471 static ICONS_SV: WidgetProviderService<IconRequestArgs> = const { WidgetProviderService::new() };
472}
473struct WidgetProviderService<A> {
474 handlers: Vec<WidgetFn<A>>,
475 fallback: Vec<WidgetFn<A>>,
476}
477impl<A: Clone + 'static> WidgetProviderService<A> {
478 const fn new() -> Self {
479 Self {
480 handlers: vec![],
481 fallback: vec![],
482 }
483 }
484
485 fn push(&mut self, handler: WidgetFn<A>) {
486 self.handlers.push(handler);
487 }
488
489 fn push_fallback(&mut self, handler: WidgetFn<A>) {
490 self.fallback.push(handler);
491 }
492
493 fn get(&self, args: A) -> BoxedUiNode {
494 for handler in self.handlers.iter().rev() {
495 let editor = handler(args.clone());
496 if !editor.is_nil() {
497 return editor;
498 }
499 }
500 for handler in self.fallback.iter() {
501 let editor = handler(args.clone());
502 if !editor.is_nil() {
503 return editor;
504 }
505 }
506 NilUiNode.boxed()
507 }
508}