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}