zng_wgt_data_view/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//! Data view widget.
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 parking_lot::Mutex;
13use std::{any::TypeId, sync::Arc};
14
15use zng_wgt::prelude::*;
16
17/// Arguments for the [`DataView!`] widget.
18///
19/// [`DataView!`]: struct@DataView
20#[derive(Clone)]
21pub struct DataViewArgs<D: VarValue> {
22 data: BoxedVar<D>,
23 replace: Arc<Mutex<Option<BoxedUiNode>>>,
24 is_nil: bool,
25}
26impl<D: VarValue> DataViewArgs<D> {
27 /// Reference the data variable.
28 ///
29 /// Can be cloned and used in the [`set_view`] to avoid rebuilding the info tree for every update.
30 ///
31 /// [`set_view`]: Self::set_view
32 pub fn data(&self) -> &BoxedVar<D> {
33 &self.data
34 }
35
36 /// Get the current data value if [`view_is_nil`] or [`data`] is new.
37 ///
38 /// [`view_is_nil`]: Self::view_is_nil
39 /// [`data`]: Self::data
40 pub fn get_new(&self) -> Option<D> {
41 if self.is_nil { Some(self.data.get()) } else { self.data.get_new() }
42 }
43
44 /// If the current child is nil node.
45 pub fn view_is_nil(&self) -> bool {
46 self.is_nil
47 }
48
49 /// Replace the child node.
50 ///
51 /// If set the current child node will be deinited and dropped.
52 pub fn set_view(&self, new_child: impl UiNode) {
53 *self.replace.lock() = Some(new_child.boxed());
54 }
55
56 /// Set the view to [`NilUiNode`].
57 ///
58 /// [`NilUiNode`]: zng_wgt::prelude::NilUiNode
59 pub fn unset_view(&self) {
60 self.set_view(NilUiNode)
61 }
62}
63
64/// Dynamically presents a data variable.
65///
66/// # Shorthand
67///
68/// The `DataView!` macro provides a shorthand init that sets `view` property directly.
69///
70/// ```
71/// # zng_wgt::enable_widget_macros!();
72/// # use zng_wgt_data_view::*;
73/// # use zng_wgt::prelude::*;
74/// # fn main() { }
75/// # fn shorthand_demo<T: VarValue>(data: impl IntoVar<T>, update: impl WidgetHandler<DataViewArgs<T>>) -> impl UiNode {
76/// DataView!(::<T>, data, update)
77/// # }
78/// ```
79///
80/// Note that the first argument is a *turbo-fish* that defines the data type and is required.
81///
82/// The shorthand is above expands to:
83///
84/// ```
85/// # zng_wgt::enable_widget_macros!();
86/// # use zng_wgt_data_view::*;
87/// # use zng_wgt::prelude::*;
88/// # fn main() { }
89/// # fn shorthand_demo<T: VarValue>(data: impl IntoVar<T>, update: impl WidgetHandler<DataViewArgs<T>>) -> impl UiNode {
90/// DataView! {
91/// view::<T> = { data: data, update: update };
92/// }
93/// # }
94/// ```
95#[widget($crate::DataView {
96 (::<$T:ty>, $data:expr, $update:expr $(,)?) => {
97 view::<$T> = {
98 data: $data,
99 update: $update,
100 };
101 }
102})]
103pub struct DataView(WidgetBase);
104impl DataView {
105 widget_impl! {
106 /// Spacing around content, inside the border.
107 pub zng_wgt_container::padding(padding: impl IntoVar<SideOffsets>);
108
109 /// Content alignment.
110 pub zng_wgt_container::child_align(align: impl IntoVar<Align>);
111
112 /// Content overflow clipping.
113 pub zng_wgt::clip_to_bounds(clip: impl IntoVar<bool>);
114 }
115}
116
117/// The view generator.
118///
119/// The `update` widget handler is used to generate the view from the `data`, it is called on init and
120/// every time `data` or `update` are new. The view is set by calling [`DataViewArgs::set_view`] in the widget function
121/// args, note that the data variable is available in [`DataViewArgs::data`], a good view will bind to the variable
122/// to support some changes, only replacing the view for major changes.
123///
124/// [`DataView!`]: struct@DataView
125#[property(CHILD, widget_impl(DataView))]
126pub fn view<D: VarValue>(child: impl UiNode, data: impl IntoVar<D>, update: impl WidgetHandler<DataViewArgs<D>>) -> impl UiNode {
127 let data = data.into_var().boxed();
128 let mut update = update.cfg_boxed();
129 let replace = Arc::new(Mutex::new(None));
130
131 match_node(child.boxed(), move |c, op| match op {
132 UiNodeOp::Init => {
133 WIDGET.sub_var(&data);
134 update.event(&DataViewArgs {
135 data: data.clone(),
136 replace: replace.clone(),
137 is_nil: true,
138 });
139 if let Some(child) = replace.lock().take() {
140 *c.child() = child;
141 }
142 }
143 UiNodeOp::Deinit => {
144 c.deinit();
145 *c.child() = NilUiNode.boxed();
146 }
147 UiNodeOp::Update { .. } => {
148 if data.is_new() {
149 update.event(&DataViewArgs {
150 data: data.clone(),
151 replace: replace.clone(),
152 is_nil: c.child().actual_type_id() == TypeId::of::<NilUiNode>(),
153 });
154 }
155
156 update.update();
157
158 if let Some(child) = replace.lock().take() {
159 // skip update if nil -> nil, otherwise updates
160 if c.child().actual_type_id() != TypeId::of::<NilUiNode>() || child.actual_type_id() != TypeId::of::<NilUiNode>() {
161 c.child().deinit();
162 *c.child() = child;
163 c.child().init();
164 c.delegated();
165 WIDGET.update_info().layout().render();
166 }
167 }
168 }
169 _ => {}
170 })
171}