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}