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();
}
}
}
_ => {}
})
}