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#![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#[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#[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(¬e);
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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 pub static DATA_NOTE_COLORS_VAR: HashMap<DataNoteLevel, LightDark> = {
248 let mut map = HashMap::new();
249 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#[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#[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
288pub 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#[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#[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#[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
331pub struct DATA;
353impl DATA {
354 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 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 pub fn get_any(&self) -> AnyVar {
395 DATA_CTX.get_clone()
396 }
397
398 pub fn is<T: VarValue>(&self) -> bool {
400 self.value_type() == TypeId::of::<T>()
401 }
402
403 pub fn value_type(&self) -> TypeId {
405 DATA_CTX.get_clone().value_type()
406 }
407
408 #[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 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 pub fn inform(&self, note: impl DataNoteValue) -> DataNoteHandle {
434 self.annotate(DataNoteLevel::INFO, note)
435 }
436
437 pub fn warn(&self, note: impl DataNoteValue) -> DataNoteHandle {
441 self.annotate(DataNoteLevel::WARN, note)
442 }
443
444 pub fn invalidate(&self, note: impl DataNoteValue) -> DataNoteHandle {
448 self.annotate(DataNoteLevel::ERROR, note)
449 }
450
451 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 pub fn info_color(&self) -> Var<Rgba> {
486 self.note_color(DataNoteLevel::INFO)
487 }
488
489 pub fn warn_color(&self) -> Var<Rgba> {
493 self.note_color(DataNoteLevel::WARN)
494 }
495
496 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#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
514#[serde(transparent)]
515pub struct DataNoteLevel(pub NonZeroU8);
516impl DataNoteLevel {
517 pub const INFO: Self = Self(NonZeroU8::new(1).unwrap());
519 pub const WARN: Self = Self(NonZeroU8::new(128).unwrap());
521 pub const ERROR: Self = Self(NonZeroU8::new(255).unwrap());
523
524 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#[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 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 pub fn source(&self) -> WidgetId {
593 self.source
594 }
595
596 pub fn level(&self) -> DataNoteLevel {
598 self.level
599 }
600
601 pub fn value(&self) -> Option<Arc<dyn DataNoteValue>> {
605 self.value.upgrade()
606 }
607
608 pub fn retain(&self) -> bool {
610 self.value.strong_count() > 0
611 }
612}
613
614#[must_use = "dropping the handle drops the data note"]
616pub struct DataNoteHandle(Option<Arc<dyn DataNoteValue>>);
617impl DataNoteHandle {
618 pub fn dummy() -> Self {
620 Self(None)
621 }
622
623 pub fn is_dummy(&self) -> bool {
625 self.0.is_some()
626 }
627}
628
629#[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 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#[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 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 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 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
715pub 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 = ¬es.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}