zng_wgt/
visibility_props.rs

1use crate::{event_property, node::VarEventNodeBuilder, prelude::*};
2
3use zng_app::widget::info::WIDGET_TREE_CHANGED_EVENT;
4
5/// Sets the widget visibility.
6///
7/// This property causes the widget to have the `visibility`, the widget actual visibility is computed, for example,
8/// widgets that don't render anything are considered `Hidden` even if the visibility property is not set, this property
9/// only forces the widget to layout and render according to the specified visibility.
10///
11/// To probe the visibility state of a widget in `when` clauses use [`is_visible`], [`is_hidden`] or [`is_collapsed`],
12/// to probe a widget state use [`WidgetInfo::visibility`].
13///
14/// [`is_visible`]: fn@is_visible
15/// [`is_hidden`]: fn@is_hidden
16/// [`is_collapsed`]: fn@is_collapsed
17/// [`WidgetInfo::visibility`]: zng_app::widget::info::WidgetInfo::visibility
18#[property(CONTEXT, default(true))]
19pub fn visibility(child: impl IntoUiNode, visibility: impl IntoVar<Visibility>) -> UiNode {
20    let visibility = visibility.into_var();
21    let mut prev_vis = Visibility::Visible;
22
23    match_node(child, move |child, op| match op {
24        UiNodeOp::Init => {
25            WIDGET.sub_var(&visibility);
26            prev_vis = visibility.get();
27        }
28        UiNodeOp::Update { .. } => {
29            if let Some(vis) = visibility.get_new() {
30                use Visibility::*;
31                match (prev_vis, vis) {
32                    (Collapsed, Visible) | (Visible, Collapsed) => {
33                        WIDGET.layout().render();
34                    }
35                    (Hidden, Visible) | (Visible, Hidden) => {
36                        WIDGET.render();
37                    }
38                    (Collapsed, Hidden) | (Hidden, Collapsed) => {
39                        WIDGET.layout();
40                    }
41                    _ => {}
42                }
43                prev_vis = vis;
44            }
45        }
46
47        UiNodeOp::Measure { wm, desired_size } => {
48            if Visibility::Collapsed != visibility.get() {
49                *desired_size = child.measure(wm);
50            } else {
51                child.delegated();
52            }
53        }
54        UiNodeOp::Layout { wl, final_size } => {
55            if Visibility::Collapsed != visibility.get() {
56                *final_size = child.layout(wl);
57            } else {
58                wl.collapse();
59                child.delegated();
60            }
61        }
62
63        UiNodeOp::Render { frame } => match visibility.get() {
64            Visibility::Visible => child.render(frame),
65            Visibility::Hidden => frame.hide(|frame| child.render(frame)),
66            Visibility::Collapsed => {
67                child.delegated();
68                #[cfg(debug_assertions)]
69                {
70                    tracing::error!(
71                        "collapsed {} rendered, to fix, layout the widget, or `WidgetLayout::collapse_child` the widget",
72                        WIDGET.trace_id()
73                    )
74                }
75            }
76        },
77        UiNodeOp::RenderUpdate { update } => match visibility.get() {
78            Visibility::Visible => child.render_update(update),
79            Visibility::Hidden => update.hidden(|update| child.render_update(update)),
80            Visibility::Collapsed => {
81                child.delegated();
82                #[cfg(debug_assertions)]
83                {
84                    tracing::error!(
85                        "collapsed {} render-updated, to fix, layout the widget, or `WidgetLayout::collapse_child` the widget",
86                        WIDGET.trace_id()
87                    )
88                }
89            }
90        },
91        _ => {}
92    })
93}
94
95fn visibility_eq_state(child: impl IntoUiNode, state: impl IntoVar<bool>, expected: Visibility) -> UiNode {
96    let state = state.into_var();
97    match_node(child, move |_, op| {
98        if let UiNodeOp::Init = op {
99            let w_id = WINDOW.id();
100            let id = WIDGET.id();
101            WIDGET.push_var_handle(WIDGET_TREE_CHANGED_EVENT.var_bind(&state, move |a| {
102                if a.tree.window_id() == w_id
103                    && let Some(w) = a.tree.get(id)
104                {
105                    Some(w.visibility() == expected)
106                } else {
107                    None
108                }
109            }));
110        }
111    })
112}
113/// If the widget is [`Visible`](Visibility::Visible).
114#[property(CONTEXT)]
115pub fn is_visible(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
116    visibility_eq_state(child, state, Visibility::Visible)
117}
118/// If the widget is [`Hidden`](Visibility::Hidden).
119#[property(CONTEXT)]
120pub fn is_hidden(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
121    visibility_eq_state(child, state, Visibility::Hidden)
122}
123/// If the widget is [`Collapsed`](Visibility::Collapsed).
124#[property(CONTEXT)]
125pub fn is_collapsed(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
126    visibility_eq_state(child, state, Visibility::Collapsed)
127}
128
129/// Defines if the widget only renders if it's bounds intersects with the viewport auto-hide rectangle.
130///
131/// The auto-hide rect is usually `(1.vw(), 1.vh())` of extra space around the viewport, so only widgets that transform
132/// themselves very far need to set this, disabling auto-hide for a widget does not disable it for descendants.
133///
134/// # Examples
135///
136/// The example demonstrates a container that is fixed in the scroll viewport, it sets the `x` and `y` properties
137/// to always stay in frame. Because the container is layout out of view and just transformed back into view it
138/// auto-hides while visible, the example uses `auto_hide = false;` to fix the issue.
139///
140/// ```
141/// # macro_rules! Container { ($($tt:tt)*) => { UiNode::nil() }}
142/// # use zng_app::widget::node::*;
143/// fn center_viewport(msg: impl IntoUiNode) -> UiNode {
144///     Container! {
145///         layout::x = merge_var!(SCROLL.horizontal_offset(), SCROLL.zoom_scale(), |&h, &s| h.0.fct_l()
146///             - 1.vw() / s * h);
147///         layout::y = merge_var!(SCROLL.vertical_offset(), SCROLL.zoom_scale(), |&v, &s| v.0.fct_l() - 1.vh() / s * v);
148///         layout::scale = SCROLL.zoom_scale().map(|&fct| 1.fct() / fct);
149///         layout::transform_origin = 0;
150///         widget::auto_hide = false;
151///         layout::max_size = (1.vw(), 1.vh());
152///
153///         child_align = Align::CENTER;
154///         child = msg;
155///     }
156/// }
157/// ```
158#[property(CONTEXT, default(true))]
159pub fn auto_hide(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
160    let enabled = enabled.into_var();
161
162    match_node(child, move |_, op| match op {
163        UiNodeOp::Init => {
164            WIDGET.sub_var(&enabled);
165        }
166        UiNodeOp::Update { .. } => {
167            if let Some(new) = enabled.get_new()
168                && WIDGET.bounds().can_auto_hide() != new
169            {
170                WIDGET.layout().render();
171            }
172        }
173        UiNodeOp::Layout { wl, .. } => {
174            wl.allow_auto_hide(enabled.get());
175        }
176        _ => {}
177    })
178}
179
180macro_rules! visibility_var_event_source {
181    (|$visibility:ident| $map:expr, $default:expr) => {
182        $crate::node::VarEventNodeBuilder::new(|| {
183            let win_id = WINDOW.id();
184            let wgt_id = WIDGET.id();
185            WIDGET_TREE_CHANGED_EVENT.var_map(
186                move |a| {
187                    if a.tree.window_id() == win_id
188                        && let Some(w) = a.tree.get(wgt_id)
189                    {
190                        Some({
191                            let $visibility = w.visibility();
192                            $map
193                        })
194                    } else {
195                        None
196                    }
197                },
198                || $default,
199            )
200        })
201    };
202}
203
204event_property! {
205    /// Widget global inner transform changed.
206    #[property(EVENT)]
207    pub fn on_transform_changed(child: impl IntoUiNode, handler: Handler<PxTransform>) -> UiNode {
208        VarEventNodeBuilder::new(|| {
209            let win_id = WINDOW.id();
210            let wgt_id = WIDGET.id();
211            WIDGET_TREE_CHANGED_EVENT.var_map(
212                move |a| {
213                    if a.tree.window_id() == win_id
214                        && let Some(w) = a.tree.get(wgt_id)
215                    {
216                        Some(w.inner_transform())
217                    } else {
218                        None
219                    }
220                },
221                PxTransform::identity,
222            )
223        })
224        .build::<false>(child, handler)
225    }
226
227    /// Widget visibility changed.
228    ///
229    /// Note that there are multiple specific events for visibility changes, [`on_visible`], [`on_hidden`] and [`on_collapsed`].
230    ///
231    /// [`on_visible`]: fn@on_visible
232    /// [`on_hidden`]: fn@on_hidden
233    /// [`on_collapsed`]: fn@on_collapsed
234    #[property(EVENT)]
235    pub fn on_visibility_changed(child: impl IntoUiNode, handler: Handler<Visibility>) -> UiNode {
236        visibility_var_event_source!(|v| v, Visibility::Visible).build::<false>(child, handler)
237    }
238
239    /// Widget visibility changed to visible.
240    ///
241    /// See [`on_visibility_changed`] for a more general visibility event.
242    ///
243    /// Note that widgets are visible by default, so this will not notify on init.
244    ///
245    /// [`on_visibility_changed`]: fn@on_visibility_changed
246    #[property(EVENT)]
247    pub fn on_visible(child: impl IntoUiNode, handler: Handler<()>) -> UiNode {
248        visibility_var_event_source!(|v| v.is_visible(), true)
249            .filter(|| |v| *v)
250            .map_args(|_| ())
251            .build::<false>(child, handler)
252    }
253
254    /// Widget visibility changed to hidden.
255    ///
256    /// See [`on_visibility_changed`] for a more general visibility event.
257    ///
258    /// [`on_visibility_changed`]: fn@on_visibility_changed
259    #[property(EVENT)]
260    pub fn on_hidden(child: impl IntoUiNode, handler: Handler<()>) -> UiNode {
261        visibility_var_event_source!(|v| v.is_hidden(), true)
262            .filter(|| |v| *v)
263            .map_args(|_| ())
264            .build::<false>(child, handler)
265    }
266
267    /// Widget visibility changed to collapsed.
268    ///
269    /// See [`on_visibility_changed`] for a more general visibility event.
270    ///
271    /// [`on_visibility_changed`]: fn@on_visibility_changed
272    #[property(EVENT)]
273    pub fn on_collapsed(child: impl IntoUiNode, handler: Handler<()>) -> UiNode {
274        visibility_var_event_source!(|v| v.is_collapsed(), true)
275            .filter(|| |v| *v)
276            .map_args(|_| ())
277            .build::<false>(child, handler)
278    }
279}