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 { .. } 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 pub static DATA_NOTE_COLORS_VAR: HashMap<DataNoteLevel, LightDark> = {
246 let mut map = HashMap::new();
247 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#[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#[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
286pub 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#[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#[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#[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
329pub struct DATA;
351impl DATA {
352 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 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 pub fn get_any(&self) -> AnyVar {
393 DATA_CTX.get_clone()
394 }
395
396 pub fn is<T: VarValue>(&self) -> bool {
398 self.value_type() == TypeId::of::<T>()
399 }
400
401 pub fn value_type(&self) -> TypeId {
403 DATA_CTX.get_clone().value_type()
404 }
405
406 #[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 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 pub fn inform(&self, note: impl DataNoteValue) -> DataNoteHandle {
432 self.annotate(DataNoteLevel::INFO, note)
433 }
434
435 pub fn warn(&self, note: impl DataNoteValue) -> DataNoteHandle {
439 self.annotate(DataNoteLevel::WARN, note)
440 }
441
442 pub fn invalidate(&self, note: impl DataNoteValue) -> DataNoteHandle {
446 self.annotate(DataNoteLevel::ERROR, note)
447 }
448
449 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 pub fn info_color(&self) -> Var<Rgba> {
484 self.note_color(DataNoteLevel::INFO)
485 }
486
487 pub fn warn_color(&self) -> Var<Rgba> {
491 self.note_color(DataNoteLevel::WARN)
492 }
493
494 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#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
512#[serde(transparent)]
513pub struct DataNoteLevel(pub NonZeroU8);
514impl DataNoteLevel {
515 pub const INFO: Self = Self(NonZeroU8::new(1).unwrap());
517 pub const WARN: Self = Self(NonZeroU8::new(128).unwrap());
519 pub const ERROR: Self = Self(NonZeroU8::new(255).unwrap());
521
522 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#[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 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 pub fn source(&self) -> WidgetId {
591 self.source
592 }
593
594 pub fn level(&self) -> DataNoteLevel {
596 self.level
597 }
598
599 pub fn value(&self) -> Option<Arc<dyn DataNoteValue>> {
603 self.value.upgrade()
604 }
605
606 pub fn retain(&self) -> bool {
608 self.value.strong_count() > 0
609 }
610}
611
612#[must_use = "dropping the handle drops the data note"]
614pub struct DataNoteHandle(Option<Arc<dyn DataNoteValue>>);
615impl DataNoteHandle {
616 pub fn dummy() -> Self {
618 Self(None)
619 }
620
621 pub fn is_dummy(&self) -> bool {
623 self.0.is_some()
624 }
625}
626
627#[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 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#[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 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 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 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
713pub 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 = ¬es.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}