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