zng_wgt_data_view/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
//!
//! Data view widget.
//!
//! # Crate
//!
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
#![warn(unused_extern_crates)]
#![warn(missing_docs)]

use parking_lot::Mutex;
use std::{any::TypeId, sync::Arc};

use zng_wgt::prelude::*;

/// Arguments for the [`DataView!`] widget.
///
/// [`DataView!`]: struct@DataView
#[derive(Clone)]
pub struct DataViewArgs<D: VarValue> {
    data: BoxedVar<D>,
    replace: Arc<Mutex<Option<BoxedUiNode>>>,
    is_nil: bool,
}
impl<D: VarValue> DataViewArgs<D> {
    /// Reference the data variable.
    ///
    /// Can be cloned and used in the [`set_view`] to avoid rebuilding the info tree for every update.
    ///
    /// [`set_view`]: Self::set_view
    pub fn data(&self) -> &BoxedVar<D> {
        &self.data
    }

    /// Get the current data value if [`view_is_nil`] or [`data`] is new.
    ///
    /// [`view_is_nil`]: Self::view_is_nil
    /// [`data`]: Self::data
    pub fn get_new(&self) -> Option<D> {
        if self.is_nil {
            Some(self.data.get())
        } else {
            self.data.get_new()
        }
    }

    /// If the current child is nil node.
    pub fn view_is_nil(&self) -> bool {
        self.is_nil
    }

    /// Replace the child node.
    ///
    /// If set the current child node will be deinited and dropped.
    pub fn set_view(&self, new_child: impl UiNode) {
        *self.replace.lock() = Some(new_child.boxed());
    }

    /// Set the view to [`NilUiNode`].
    ///
    /// [`NilUiNode`]: zng_wgt::prelude::NilUiNode
    pub fn unset_view(&self) {
        self.set_view(NilUiNode)
    }
}

/// Dynamically presents a data variable.
///
/// # Shorthand
///
/// The `DataView!` macro provides a shorthand init that sets `view` property directly.
///
/// ```
/// # zng_wgt::enable_widget_macros!();
/// # use zng_wgt_data_view::*;
/// # use zng_wgt::prelude::*;
/// # fn main() { }
/// # fn shorthand_demo<T: VarValue>(data: impl IntoVar<T>, update: impl WidgetHandler<DataViewArgs<T>>) -> impl UiNode {
/// DataView!(::<T>, data, update)
/// # }
/// ```
///
/// Note that the first argument is a *turbo-fish* that defines the data type and is required.
///
/// The shorthand is above expands to:
///
/// ```
/// # zng_wgt::enable_widget_macros!();
/// # use zng_wgt_data_view::*;
/// # use zng_wgt::prelude::*;
/// # fn main() { }
/// # fn shorthand_demo<T: VarValue>(data: impl IntoVar<T>, update: impl WidgetHandler<DataViewArgs<T>>) -> impl UiNode {
/// DataView! {
///     view::<T> = { data: data, update: update };
/// }
/// # }
/// ```
#[widget($crate::DataView {
    (::<$T:ty>, $data:expr, $update:expr $(,)?) => {
        view::<$T> = {
            data: $data,
            update: $update,
        };
    }
})]
pub struct DataView(WidgetBase);
impl DataView {
    widget_impl! {
        /// Spacing around content, inside the border.
        pub zng_wgt_container::padding(padding: impl IntoVar<SideOffsets>);

        /// Content alignment.
        pub zng_wgt_container::child_align(align: impl IntoVar<Align>);

        /// Content overflow clipping.
        pub zng_wgt::clip_to_bounds(clip: impl IntoVar<bool>);
    }
}

/// The view generator.
///
/// The `update` widget handler is used to generate the view from the `data`, it is called on init and
/// every time `data` or `update` are new. The view is set by calling [`DataViewArgs::set_view`] in the widget function
/// args, note that the data variable is available in [`DataViewArgs::data`], a good view will bind to the variable
/// to support some changes, only replacing the view for major changes.
///
/// [`DataView!`]: struct@DataView
#[property(CHILD, widget_impl(DataView))]
pub fn view<D: VarValue>(child: impl UiNode, data: impl IntoVar<D>, update: impl WidgetHandler<DataViewArgs<D>>) -> impl UiNode {
    let data = data.into_var().boxed();
    let mut update = update.cfg_boxed();
    let replace = Arc::new(Mutex::new(None));

    match_node(child.boxed(), move |c, op| match op {
        UiNodeOp::Init => {
            WIDGET.sub_var(&data);
            update.event(&DataViewArgs {
                data: data.clone(),
                replace: replace.clone(),
                is_nil: true,
            });
            if let Some(child) = replace.lock().take() {
                *c.child() = child;
            }
        }
        UiNodeOp::Deinit => {
            c.deinit();
            *c.child() = NilUiNode.boxed();
        }
        UiNodeOp::Update { .. } => {
            if data.is_new() {
                update.event(&DataViewArgs {
                    data: data.clone(),
                    replace: replace.clone(),
                    is_nil: c.child().actual_type_id() == TypeId::of::<NilUiNode>(),
                });
            }

            update.update();

            if let Some(child) = replace.lock().take() {
                // skip update if nil -> nil, otherwise updates
                if c.child().actual_type_id() != TypeId::of::<NilUiNode>() || child.actual_type_id() != TypeId::of::<NilUiNode>() {
                    c.child().deinit();
                    *c.child() = child;
                    c.child().init();
                    c.delegated();
                    WIDGET.update_info().layout().render();
                }
            }
        }
        _ => {}
    })
}