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::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: Var<D>,
23 replace: Arc<Mutex<(bool, UiNode)>>,
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) -> &Var<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 IntoUiNode) {
53 *self.replace.lock() = (true, new_child.into_node());
54 }
55
56 /// Set the view to [`UiNode::nil`].
57 pub fn unset_view(&self) {
58 self.set_view(UiNode::nil())
59 }
60}
61
62/// Dynamically presents a data variable.
63///
64/// # Shorthand
65///
66/// The `DataView!` macro provides a shorthand init that sets `view` property directly.
67///
68/// ```
69/// # zng_wgt::enable_widget_macros!();
70/// # use zng_wgt_data_view::*;
71/// # use zng_wgt::prelude::*;
72/// # fn main() { }
73/// # fn shorthand_demo<T: VarValue>(data: impl IntoVar<T>, update: Handler<DataViewArgs<T>>) -> UiNode {
74/// DataView!(::<T>, data, update)
75/// # }
76/// ```
77///
78/// Note that the first argument is a *turbo-fish* that defines the data type and is required.
79///
80/// The shorthand is above expands to:
81///
82/// ```
83/// # zng_wgt::enable_widget_macros!();
84/// # use zng_wgt_data_view::*;
85/// # use zng_wgt::prelude::*;
86/// # fn main() { }
87/// # fn shorthand_demo<T: VarValue>(data: impl IntoVar<T>, update: Handler<DataViewArgs<T>>) -> UiNode {
88/// DataView! {
89/// view::<T> = {
90/// data: data,
91/// update: update,
92/// };
93/// }
94/// # }
95/// ```
96#[widget($crate::DataView {
97 (::<$T:ty>, $data:expr, $update:expr $(,)?) => {
98 view::<$T> = {
99 data: $data,
100 update: $update,
101 };
102 }
103})]
104pub struct DataView(WidgetBase);
105impl DataView {
106 widget_impl! {
107 /// Spacing around content, inside the border.
108 pub zng_wgt_container::padding(padding: impl IntoVar<SideOffsets>);
109
110 /// Content alignment.
111 pub zng_wgt_container::child_align(align: impl IntoVar<Align>);
112
113 /// Content overflow clipping.
114 pub zng_wgt::clip_to_bounds(clip: impl IntoVar<bool>);
115 }
116}
117
118/// The view generator.
119///
120/// The `update` widget handler is used to generate the view from the `data`, it is called on init and
121/// every time `data` or `update` are new. The view is set by calling [`DataViewArgs::set_view`] in the widget function
122/// args, note that the data variable is available in [`DataViewArgs::data`], a good view will bind to the variable
123/// to support some changes, only replacing the view for major changes.
124///
125/// [`DataView!`]: struct@DataView
126#[property(CHILD, widget_impl(DataView))]
127pub fn view<D: VarValue>(child: impl IntoUiNode, data: impl IntoVar<D>, update: Handler<DataViewArgs<D>>) -> UiNode {
128 let data = data.into_var();
129 let mut update = update.into_wgt_runner();
130 let replace = Arc::new(Mutex::new((false, UiNode::nil())));
131
132 match_node(child, move |c, op| match op {
133 UiNodeOp::Init => {
134 WIDGET.sub_var(&data);
135 update.event(&DataViewArgs {
136 data: data.clone(),
137 replace: replace.clone(),
138 is_nil: true,
139 });
140 let child = std::mem::replace(&mut *replace.lock(), (false, UiNode::nil()));
141 if child.0 {
142 // replaced
143 *c.node() = child.1;
144 }
145 }
146 UiNodeOp::Deinit => {
147 c.deinit();
148 *c.node() = UiNode::nil();
149 update.deinit();
150 }
151 UiNodeOp::Update { .. } => {
152 if data.is_new() {
153 update.event(&DataViewArgs {
154 data: data.clone(),
155 replace: replace.clone(),
156 is_nil: c.node().is_nil(),
157 });
158 }
159
160 update.update();
161
162 let child = std::mem::replace(&mut *replace.lock(), (false, UiNode::nil()));
163 if child.0 {
164 // replaced
165 // skip update if nil -> nil, otherwise updates
166 if !c.node().is_nil() || !child.1.is_nil() {
167 c.node().deinit();
168 *c.node() = child.1;
169 c.node().init();
170 c.delegated();
171 WIDGET.update_info().layout().render();
172 }
173 }
174 }
175 _ => {}
176 })
177}