Skip to main content

zng_wgt_data/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Contextual [`DATA`] and validation.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use std::{
13    any::{Any, TypeId},
14    collections::HashMap,
15    fmt, mem,
16    num::NonZeroU8,
17    ops,
18    sync::Arc,
19};
20
21use zng_color::COLOR_SCHEME_VAR;
22use zng_var::{AnyVar, Var, contextual_var};
23use zng_wgt::prelude::*;
24
25use task::parking_lot::RwLock;
26
27/// Data context.
28///
29/// Sets the [`DATA`] context for this widget and descendants, replacing the parent's data.
30///
31/// Note that only one data context can be set at a time, the `data` will override the parent's
32/// data even if the type `T` does not match.
33#[property(CONTEXT - 1)]
34pub fn data<T: VarValue>(child: impl IntoUiNode, data: impl IntoVar<T>) -> UiNode {
35    with_context_local(child, &DATA_CTX, data.into_var())
36}
37
38/// Insert a data note in the context.
39///
40/// This properties synchronizes the `level` and `note` variables with an [`DATA.annotate`] entry. If
41/// the `note` is empty the data note is not inserted.
42///
43/// [`DATA.annotate`]: DATA::annotate
44#[property(CONTEXT, default(DataNoteLevel::INFO, ""))]
45pub fn data_note(child: impl IntoUiNode, level: impl IntoVar<DataNoteLevel>, note: impl IntoVar<Txt>) -> UiNode {
46    let level = level.into_var();
47    let note = note.into_var();
48    let mut _handle = DataNoteHandle::dummy();
49    match_node(child, move |_, op| match op {
50        UiNodeOp::Init => {
51            WIDGET.sub_var(&level).sub_var(&note);
52
53            let note = note.get();
54            if !note.is_empty() {
55                _handle = DATA.annotate(level.get(), note);
56            }
57        }
58        UiNodeOp::Deinit => {
59            _handle = DataNoteHandle::dummy();
60        }
61        UiNodeOp::Update { .. } if (level.is_new() || note.is_new()) => {
62            let note = note.get();
63            _handle = if note.is_empty() {
64                DataNoteHandle::dummy()
65            } else {
66                DATA.annotate(level.get(), note)
67            };
68        }
69        _ => {}
70    })
71}
72
73/// Insert a data [`INFO`] note in the context.
74///
75/// This properties synchronizes the `note` variable with an [`DATA.inform`] entry. If
76/// the `note` is empty the data note is not inserted.
77///
78/// [`DATA.inform`]: DATA::inform
79/// [`INFO`]: DataNoteLevel::INFO
80#[property(CONTEXT, default(""))]
81pub fn data_info(child: impl IntoUiNode, note: impl IntoVar<Txt>) -> UiNode {
82    data_note(child, DataNoteLevel::INFO, note)
83}
84
85/// Insert a data [`WARN`] note in the context.
86///
87/// This properties synchronizes the `note` variable with an [`DATA.warn`] entry. If
88/// the `note` is empty the data note is not inserted.
89///
90/// [`DATA.warn`]: DATA::warn
91/// [`WARN`]: DataNoteLevel::WARN
92#[property(CONTEXT, default(""))]
93pub fn data_warn(child: impl IntoUiNode, note: impl IntoVar<Txt>) -> UiNode {
94    data_note(child, DataNoteLevel::WARN, note)
95}
96
97/// Insert a data [`ERROR`] note in the context.
98///
99/// This properties synchronizes the `note` variable with an [`DATA.invalidate`] entry. If
100/// the `note` is empty the data note is not inserted.
101///
102/// [`DATA.invalidate`]: DATA::invalidate
103/// [`ERROR`]: DataNoteLevel::ERROR
104#[property(CONTEXT, default(""))]
105pub fn data_error(child: impl IntoUiNode, note: impl IntoVar<Txt>) -> UiNode {
106    data_note(child, DataNoteLevel::ERROR, note)
107}
108
109/// Get all data notes set on the context.
110#[property(CONTEXT - 1)]
111pub fn get_data_notes(child: impl IntoUiNode, notes: impl IntoVar<DataNotes>) -> UiNode {
112    let notes = notes.into_var();
113    with_data_notes(child, move |n| {
114        notes.set(n.clone());
115    })
116}
117
118/// Gets if any data notes are set on the context.
119#[property(CONTEXT - 1)]
120pub fn has_data_notes(child: impl IntoUiNode, any: impl IntoVar<bool>) -> UiNode {
121    let any = any.into_var();
122    with_data_notes(child, move |n| {
123        any.set(!n.is_empty());
124    })
125}
126
127/// Get all [`INFO`] data notes set on the context.
128///
129/// [`INFO`]: DataNoteLevel::INFO
130#[property(CONTEXT - 1)]
131pub fn get_data_info(child: impl IntoUiNode, notes: impl IntoVar<DataNotes>) -> UiNode {
132    let notes = notes.into_var();
133    with_data_notes(child, move |n| {
134        notes.set(n.clone_level(DataNoteLevel::INFO));
135    })
136}
137
138/// Write all [`INFO`] data notes set on the context to a text.
139///
140/// [`INFO`]: DataNoteLevel::INFO
141#[property(CONTEXT - 1)]
142pub fn get_data_info_txt(child: impl IntoUiNode, notes: impl IntoVar<Txt>) -> UiNode {
143    let notes = notes.into_var();
144    with_data_notes(child, move |n| {
145        notes.set(n.level_txt(DataNoteLevel::INFO));
146    })
147}
148
149/// Gets if any [`INFO`] data notes are set on the context.
150///
151/// [`INFO`]: DataNoteLevel::INFO
152#[property(CONTEXT - 1)]
153pub fn has_data_info(child: impl IntoUiNode, any: impl IntoVar<bool>) -> UiNode {
154    let any = any.into_var();
155    with_data_notes(child, move |n| {
156        any.set(n.iter().any(|n| n.level() == DataNoteLevel::INFO));
157    })
158}
159
160/// Get all [`WARN`] data notes set on the context.
161///
162/// [`WARN`]: DataNoteLevel::WARN
163#[property(CONTEXT - 1)]
164pub fn get_data_warn(child: impl IntoUiNode, notes: impl IntoVar<DataNotes>) -> UiNode {
165    let notes = notes.into_var();
166    with_data_notes(child, move |n| {
167        notes.set(n.clone_level(DataNoteLevel::WARN));
168    })
169}
170
171/// Write all [`WARN`] data notes set on the context to a text.
172///
173/// [`WARN`]: DataNoteLevel::WARN
174#[property(CONTEXT - 1)]
175pub fn get_data_warn_txt(child: impl IntoUiNode, notes: impl IntoVar<Txt>) -> UiNode {
176    let notes = notes.into_var();
177    with_data_notes(child, move |n| {
178        notes.set(n.level_txt(DataNoteLevel::WARN));
179    })
180}
181
182/// Gets if any [`WARN`] data notes are set on the context.
183///
184/// [`WARN`]: DataNoteLevel::WARN
185#[property(CONTEXT - 1)]
186pub fn has_data_warn(child: impl IntoUiNode, any: impl IntoVar<bool>) -> UiNode {
187    let any = any.into_var();
188    with_data_notes(child, move |n| {
189        any.set(n.iter().any(|n| n.level() == DataNoteLevel::WARN));
190    })
191}
192
193/// Get all [`ERROR`] data notes set on the context.
194///
195/// [`ERROR`]: DataNoteLevel::ERROR
196#[property(CONTEXT - 1)]
197pub fn get_data_error(child: impl IntoUiNode, notes: impl IntoVar<DataNotes>) -> UiNode {
198    let notes = notes.into_var();
199    with_data_notes(child, move |n| {
200        notes.set(n.clone_level(DataNoteLevel::ERROR));
201    })
202}
203
204/// Write all [`ERROR`] data notes set on the context to a text.
205///
206/// [`ERROR`]: DataNoteLevel::ERROR
207#[property(CONTEXT - 1)]
208pub fn get_data_error_txt(child: impl IntoUiNode, notes: impl IntoVar<Txt>) -> UiNode {
209    let notes = notes.into_var();
210    with_data_notes(child, move |n| {
211        notes.set(n.level_txt(DataNoteLevel::ERROR));
212    })
213}
214
215/// Gets if any [`ERROR`] data notes are set on the context.
216///
217/// [`ERROR`]: DataNoteLevel::ERROR
218#[property(CONTEXT - 1)]
219pub fn has_data_error(child: impl IntoUiNode, any: impl IntoVar<bool>) -> UiNode {
220    let any = any.into_var();
221    with_data_notes(child, move |n| {
222        any.set(n.iter().any(|n| n.level() == DataNoteLevel::ERROR));
223    })
224}
225
226/// Gets all the notes of highest data level set on the context.
227#[property(CONTEXT - 1)]
228pub fn get_data_notes_top(child: impl IntoUiNode, notes: impl IntoVar<DataNotes>) -> UiNode {
229    let notes = notes.into_var();
230    with_data_notes(child, move |n| {
231        notes.set(if let Some(top) = n.iter().map(|n| n.level()).max() {
232            n.clone_level(top)
233        } else {
234            DataNotes::default()
235        });
236    })
237}
238
239context_var! {
240    /// Color pairs for note levels.
241    ///
242    /// The colors can be used directly as text color.
243    ///
244    /// Defaults set only for the named levels.
245    pub static DATA_NOTE_COLORS_VAR: HashMap<DataNoteLevel, LightDark> = {
246        let mut map = HashMap::new();
247        // (dark, light)
248        map.insert(DataNoteLevel::INFO, LightDark::new(colors::AZURE, colors::AZURE));
249        map.insert(DataNoteLevel::WARN, LightDark::new(colors::ORANGE, colors::YELLOW));
250        map.insert(
251            DataNoteLevel::ERROR,
252            LightDark::new(colors::RED, colors::WHITE.with_alpha(20.pct()).mix_normal(colors::RED)),
253        );
254        map
255    };
256}
257
258/// Sets the data note level colors, the parent colors are fully replaced.
259///
260/// The colors will be used directly as text color.
261///
262/// This property sets the [`DATA_NOTE_COLORS_VAR`].
263#[property(CONTEXT, default(DATA_NOTE_COLORS_VAR))]
264pub fn replace_data_note_colors(child: impl IntoUiNode, colors: impl IntoVar<HashMap<DataNoteLevel, LightDark>>) -> UiNode {
265    with_context_var(child, DATA_NOTE_COLORS_VAR, colors)
266}
267
268/// Extend the data note level colors, the `colors` extend the parent colors, only entries of the same level are replaced.
269///
270/// The colors will be used directly as text color.
271///
272/// This property sets the [`DATA_NOTE_COLORS_VAR`].
273#[property(CONTEXT, default(HashMap::new()))]
274pub fn extend_data_note_colors(child: impl IntoUiNode, colors: impl IntoVar<HashMap<DataNoteLevel, LightDark>>) -> UiNode {
275    with_context_var(
276        child,
277        DATA_NOTE_COLORS_VAR,
278        merge_var!(DATA_NOTE_COLORS_VAR, colors.into_var(), |base, over| {
279            let mut base = base.clone();
280            base.extend(over);
281            base
282        }),
283    )
284}
285
286/// Node that inserts a data note color in [`DATA_NOTE_COLORS_VAR`].
287pub fn with_data_note_color(child: impl IntoUiNode, level: DataNoteLevel, color: impl IntoVar<LightDark>) -> UiNode {
288    with_context_var(
289        child,
290        DATA_NOTE_COLORS_VAR,
291        merge_var!(DATA_NOTE_COLORS_VAR, color.into_var(), move |base, over| {
292            let mut base = base.clone();
293            base.insert(level, *over);
294            base
295        }),
296    )
297}
298
299/// Set the data note [`INFO`] color.
300///
301/// The color will be used directly as text color.
302///
303/// [`INFO`]: DataNoteLevel::INFO
304#[property(CONTEXT)]
305pub fn data_info_color(child: impl IntoUiNode, color: impl IntoVar<LightDark>) -> UiNode {
306    with_data_note_color(child, DataNoteLevel::INFO, color)
307}
308
309/// Set the data note [`WARN`] color.
310///
311/// The color will be used directly as text color.
312///
313/// [`WARN`]: DataNoteLevel::WARN
314#[property(CONTEXT)]
315pub fn data_warn_color(child: impl IntoUiNode, color: impl IntoVar<LightDark>) -> UiNode {
316    with_data_note_color(child, DataNoteLevel::WARN, color)
317}
318
319/// Set the data note [`ERROR`] color.
320///
321/// The color will be used directly as text color.
322///
323/// [`ERROR`]: DataNoteLevel::ERROR
324#[property(CONTEXT)]
325pub fn data_error_color(child: impl IntoUiNode, color: impl IntoVar<LightDark>) -> UiNode {
326    with_data_note_color(child, DataNoteLevel::ERROR, color)
327}
328
329/// Data context and validation.
330///
331/// This service enables data flow from a context to descendants, and from descendants up-to contexts, like an anonymous context var.
332///
333/// Arbitrary data can be set on a context using the [`data`] property and retrieved using [`DATA.get`] or [`DATA.req`].
334/// Only one data entry and type can exist in a context, nested [`data`] properties override the parent data and type in their context.
335///
336/// Annotation on the data can be set back using [`DATA.annotate`] and can be retrieved using the [`get_data_notes`] property,
337/// annotations are classified by [`DataNoteLevel`], including `INFO`, `WARN` and `ERROR`. For each level there are specialized
338/// methods and properties, as an example, the [`DATA.invalidate`] is used to set an error note, and the [`get_data_error_txt`]
339/// property gets the error formatted for display. Data notes are aggregated from descendants up-to the context, continuing
340/// up to outer nested contexts too, this means that you can get data errors for a form field by setting [`get_data_error_txt`] on
341/// the field widget, and get all form errors from that field and others by also setting [`get_data_error_txt`] in the form widget.
342///
343/// [`data`]: fn@data
344/// [`get_data_notes`]: fn@get_data_notes
345/// [`get_data_error_txt`]: fn@get_data_error_txt
346/// [`DATA.get`]: DATA::get
347/// [`DATA.req`]: DATA::req
348/// [`DATA.annotate`]: DATA::annotate
349/// [`DATA.invalidate`]: DATA::invalidate
350pub struct DATA;
351impl DATA {
352    /// Require context data of type `T`.
353    ///
354    /// See [`get`] for capabilities of the returned var.
355    ///
356    /// # Panics
357    ///
358    /// Panics if the context data is not set to a variable of type `T` on the first usage of the returned variable.
359    ///
360    /// [`get`]: Self::get
361    pub fn req<T: VarValue>(&self) -> Var<T> {
362        self.get(|| {
363            #[cfg(feature = "var_type_names")]
364            panic!(
365                "expected DATA of type `{}`, but current context is `{}`",
366                std::any::type_name::<T>(),
367                DATA.value_type_name()
368            );
369
370            #[cfg(not(feature = "var_type_names"))]
371            panic!("expected DATA of a different type");
372        })
373    }
374
375    /// Gets a var of `T`, if the contextual DATA type is not `T` the `fallback` closure is called to produce a value.
376    ///
377    /// # Capabilities
378    ///
379    /// The returned var is [contextual], it will get the data var in the final context it is used not
380    /// on the context that calls this method.
381    ///
382    /// [contextual]: contextual_var
383    pub fn get<T: VarValue>(&self, fallback: impl Fn() -> T + Send + Sync + 'static) -> Var<T> {
384        contextual_var(move || DATA_CTX.get_clone().downcast::<T>().unwrap_or_else(|_| const_var(fallback())))
385    }
386
387    /// Get the current context variable.
388    ///
389    /// Note that this is different from [`get`], the return variable is "actualized" on the calling context, not on the first var use context.
390    ///
391    /// [`get`]: Self::get
392    pub fn get_any(&self) -> AnyVar {
393        DATA_CTX.get_clone()
394    }
395
396    /// Gets if the current context data type is `T`.
397    pub fn is<T: VarValue>(&self) -> bool {
398        self.value_type() == TypeId::of::<T>()
399    }
400
401    /// Gets the current context value type.
402    pub fn value_type(&self) -> TypeId {
403        DATA_CTX.get_clone().value_type()
404    }
405
406    /// Gets the current context value type name.
407    #[cfg(feature = "var_type_names")]
408    pub fn value_type_name(&self) -> &'static str {
409        DATA_CTX.get_clone().value_type_name()
410    }
411
412    /// Insert a data note in the current context.
413    ///
414    /// The note will stay in context until the context is unloaded or the handle is dropped.
415    pub fn annotate(&self, level: DataNoteLevel, note: impl DataNoteValue) -> DataNoteHandle {
416        if !DATA_NOTES_CTX.is_default() {
417            let (note, handle) = DataNote::new(WIDGET.id(), level, note);
418            let notes = DATA_NOTES_CTX.get();
419            let mut notes = notes.write();
420            notes.notes.notes.push(note);
421            notes.changed = true;
422            handle
423        } else {
424            DataNoteHandle::dummy()
425        }
426    }
427
428    /// Insert an `INFO` note in the current context.
429    ///
430    /// The note will stay in context until the context is unloaded or the handle is dropped.
431    pub fn inform(&self, note: impl DataNoteValue) -> DataNoteHandle {
432        self.annotate(DataNoteLevel::INFO, note)
433    }
434
435    /// Insert a `WARN` note in the current context.
436    ///
437    /// The note will stay in context until the context is unloaded or the handle is dropped.
438    pub fn warn(&self, note: impl DataNoteValue) -> DataNoteHandle {
439        self.annotate(DataNoteLevel::WARN, note)
440    }
441
442    /// Insert an `ERROR` note in the current context.
443    ///
444    /// The note will stay in context until the context is unloaded or the handle is dropped.
445    pub fn invalidate(&self, note: impl DataNoteValue) -> DataNoteHandle {
446        self.annotate(DataNoteLevel::ERROR, note)
447    }
448
449    /// Read-only variable that is the best color for the note level in the context of the current color scheme.
450    ///
451    /// If the `level` is not found, gets the nearest less than level, if no color is set in the context gets
452    /// the black/white for dark/light.
453    ///
454    /// The color can be used directly as text color, it probably needs mixing or desaturating to use as background.
455    pub fn note_color(&self, level: impl IntoVar<DataNoteLevel>) -> Var<Rgba> {
456        merge_var!(DATA_NOTE_COLORS_VAR, level.into_var(), COLOR_SCHEME_VAR, |map, level, scheme| {
457            let c = if let Some(c) = map.get(level) {
458                *c
459            } else {
460                let mut nearest = 0u8;
461                let mut color = None;
462
463                for (l, c) in map {
464                    if l.0.get() < level.0.get() && l.0.get() > nearest {
465                        nearest = l.0.get();
466                        color = Some(*c);
467                    }
468                }
469
470                color.unwrap_or_else(|| LightDark::new(colors::WHITE, colors::BLACK))
471            };
472            match scheme {
473                ColorScheme::Light => c.light,
474                ColorScheme::Dark => c.dark,
475                _ => c.light,
476            }
477        })
478    }
479
480    /// Read-only variable that is the best color for `INFO` notes in the context of the current color scheme.
481    ///
482    /// The color can be used directly as text color, it probably needs mixing or desaturating to use as background.
483    pub fn info_color(&self) -> Var<Rgba> {
484        self.note_color(DataNoteLevel::INFO)
485    }
486
487    /// Read-only variable that is the best color for `WARN` notes in the context of the current color scheme.
488    ///
489    /// The color can be used directly as text color, it probably needs mixing or desaturating to use as background.
490    pub fn warn_color(&self) -> Var<Rgba> {
491        self.note_color(DataNoteLevel::WARN)
492    }
493
494    /// Read-only variable that is the best color for `ERROR` notes in the context of the current color scheme.
495    ///
496    /// The color can be used directly as text color, it probably needs mixing or desaturating to use as background.
497    pub fn error_color(&self) -> Var<Rgba> {
498        self.note_color(DataNoteLevel::ERROR)
499    }
500}
501
502#[derive(Debug, PartialEq, Clone)]
503struct DataContextNotSet;
504
505context_local! {
506    static DATA_CTX: AnyVar = const_var(DataContextNotSet);
507    static DATA_NOTES_CTX: RwLock<DataNotesProbe> = RwLock::default();
508}
509
510/// Classifies the kind of information conveyed by a [`DataNote`].
511#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
512#[serde(transparent)]
513pub struct DataNoteLevel(pub NonZeroU8);
514impl DataNoteLevel {
515    /// Entry represents useful information.
516    pub const INFO: Self = Self(NonZeroU8::new(1).unwrap());
517    /// Entry represents a data validation warning.
518    pub const WARN: Self = Self(NonZeroU8::new(128).unwrap());
519    /// Entry represents a data validation error.
520    pub const ERROR: Self = Self(NonZeroU8::new(255).unwrap());
521
522    /// Gets the level name, if it is one of the `const` levels.
523    pub fn name(self) -> &'static str {
524        if self == Self::INFO {
525            "INFO"
526        } else if self == Self::WARN {
527            "WARN"
528        } else if self == Self::ERROR {
529            "ERROR"
530        } else {
531            ""
532        }
533    }
534}
535impl fmt::Debug for DataNoteLevel {
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        let name = self.name();
538        if name.is_empty() {
539            f.debug_tuple("DataNoteLevel").field(&self.0).finish()
540        } else {
541            if f.alternate() {
542                write!(f, "DataNoteLevel::")?;
543            }
544            write!(f, "{name}")
545        }
546    }
547}
548
549/// Represents an annotation set in a data context.
550///
551/// See [`DATA`] for more details.
552#[derive(Clone)]
553pub struct DataNote {
554    source: WidgetId,
555    level: DataNoteLevel,
556    value: std::sync::Weak<dyn DataNoteValue>,
557}
558impl fmt::Debug for DataNote {
559    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
560        f.debug_struct("DataNote")
561            .field("source", &self.source)
562            .field("level", &self.level)
563            .field("value", &self.value())
564            .finish()
565    }
566}
567impl fmt::Display for DataNote {
568    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569        if let Some(value) = self.value() {
570            write!(f, "{value}")
571        } else {
572            Ok(())
573        }
574    }
575}
576impl PartialEq for DataNote {
577    fn eq(&self, other: &Self) -> bool {
578        self.value.ptr_eq(&other.value) && self.source == other.source && self.level == other.level
579    }
580}
581impl DataNote {
582    /// New note.
583    pub fn new(source: WidgetId, level: DataNoteLevel, value: impl DataNoteValue + 'static) -> (Self, DataNoteHandle) {
584        let handle = Arc::new(value);
585        let value = Arc::downgrade(&handle);
586        (Self { source, level, value }, DataNoteHandle(Some(handle)))
587    }
588
589    /// Widget that set the annotation.
590    pub fn source(&self) -> WidgetId {
591        self.source
592    }
593
594    /// Annotation level.
595    pub fn level(&self) -> DataNoteLevel {
596        self.level
597    }
598
599    /// Annotation value.
600    ///
601    /// Is `None` if the note was dropped since last cleanup.
602    pub fn value(&self) -> Option<Arc<dyn DataNoteValue>> {
603        self.value.upgrade()
604    }
605
606    /// If the note is still valid.
607    pub fn retain(&self) -> bool {
608        self.value.strong_count() > 0
609    }
610}
611
612/// Handle for a [`DataNote`] in a context.
613#[must_use = "dropping the handle drops the data note"]
614pub struct DataNoteHandle(Option<Arc<dyn DataNoteValue>>);
615impl DataNoteHandle {
616    /// New dummy handle.
617    pub fn dummy() -> Self {
618        Self(None)
619    }
620
621    /// If this is a dummy handle.
622    pub fn is_dummy(&self) -> bool {
623        self.0.is_some()
624    }
625}
626
627/// Represents a [`DataNote`] value.
628///
629/// # Trait Alias
630///
631/// This trait is used like a type alias for traits and is
632/// already implemented for all types it applies to.
633#[diagnostic::on_unimplemented(note = "`DataNoteValue` is implemented for all `T: Debug + Display + Send + Sync + Any")]
634pub trait DataNoteValue: fmt::Debug + fmt::Display + Send + Sync + Any {
635    /// /// Access to `dyn Any` methods.
636    fn as_any(&self) -> &dyn Any;
637}
638impl<T: fmt::Debug + fmt::Display + Send + Sync + Any + 'static> DataNoteValue for T {
639    fn as_any(&self) -> &dyn Any {
640        self
641    }
642}
643
644/// Represents the data notes set in a context.
645#[derive(Debug, Clone, PartialEq, Default)]
646pub struct DataNotes {
647    notes: Vec<DataNote>,
648}
649impl ops::Deref for DataNotes {
650    type Target = [DataNote];
651
652    fn deref(&self) -> &Self::Target {
653        &self.notes
654    }
655}
656impl DataNotes {
657    /// Remove dropped notes.
658    pub fn cleanup(&mut self) -> bool {
659        let len = self.notes.len();
660        self.notes.retain(|n| n.retain());
661        len != self.notes.len()
662    }
663
664    /// Clone notes of the same `level`.
665    pub fn clone_level(&self, level: DataNoteLevel) -> Self {
666        let mut notes = vec![];
667        for note in &self.notes {
668            if note.level == level {
669                notes.push(note.clone())
670            }
671        }
672        Self { notes }
673    }
674
675    /// Write all notes of the level to a text.
676    ///
677    /// Multiple notes are placed each in a line.
678    pub fn level_txt(&self, level: DataNoteLevel) -> Txt {
679        let mut txt = Txt::from_string(String::new());
680        let mut sep = "";
681        for note in &self.notes {
682            if note.level == level
683                && let Some(value) = note.value()
684            {
685                use std::fmt::Write;
686                let _ = write!(&mut txt, "{sep}{value}");
687                sep = "\n";
688            }
689        }
690        txt.end_mut();
691        txt
692    }
693}
694impl fmt::Display for DataNotes {
695    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
696        let mut sep = "";
697        for note in &self.notes {
698            if let Some(value) = note.value() {
699                write!(f, "{sep}{value}")?;
700                sep = "\n";
701            }
702        }
703        Ok(())
704    }
705}
706
707#[derive(Default)]
708struct DataNotesProbe {
709    notes: DataNotes,
710    changed: bool,
711}
712
713/// Creates a note that samples [`DataNotes`] in a context.
714///
715/// The `on_changed` closure is called every time a note is inserted or removed in context. The closure
716/// can be called in any [`UiNodeOp`], it is always called after the `child` processed the operation. The
717/// notes always change to empty on deinit.
718///
719/// [`UiNodeOp`]: zng_wgt::prelude::UiNodeOp
720pub fn with_data_notes(child: impl IntoUiNode, mut on_changed: impl FnMut(&DataNotes) + Send + 'static) -> UiNode {
721    let mut notes = None;
722    match_node(child, move |c, op| {
723        let is_deinit = match &op {
724            UiNodeOp::Init => {
725                notes = Some(Arc::new(RwLock::new(DataNotesProbe::default())));
726                false
727            }
728            UiNodeOp::Deinit => true,
729            _ => false,
730        };
731
732        DATA_NOTES_CTX.with_context(&mut notes, || c.op(op));
733
734        if is_deinit {
735            let n = notes.take().unwrap();
736            let not_empty = !mem::take(&mut n.write().notes).is_empty();
737            if not_empty {
738                on_changed(&DataNotes::default());
739            }
740        } else {
741            let notes = notes.as_ref().unwrap();
742            let mut notes = notes.write();
743
744            let cleaned = notes.notes.cleanup();
745            if mem::take(&mut notes.changed) || cleaned {
746                let notes = task::parking_lot::lock_api::RwLockWriteGuard::downgrade(notes);
747                let notes = &notes.notes;
748
749                if !DATA_NOTES_CTX.is_default() {
750                    let parent = DATA_NOTES_CTX.get();
751                    let mut parent = parent.write();
752                    for note in notes.iter() {
753                        if parent.notes.iter().all(|n| n != note) {
754                            parent.notes.notes.push(note.clone());
755                            parent.changed = true;
756                        }
757                    }
758                }
759
760                on_changed(notes);
761            }
762        }
763    })
764}