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}