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