zng_wgt_data/
lib.rs

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