zng_wgt/
visibility_props.rs

1use crate::prelude::*;
2
3use zng_app::widget::info;
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    event_state(
97        child,
98        state,
99        expected == Visibility::Visible,
100        info::VISIBILITY_CHANGED_EVENT,
101        move |a| {
102            let vis = a.tree.get(WIDGET.id()).map(|w| w.visibility()).unwrap_or(Visibility::Visible);
103
104            Some(vis == expected)
105        },
106    )
107}
108/// If the widget is [`Visible`](Visibility::Visible).
109#[property(CONTEXT)]
110pub fn is_visible(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
111    visibility_eq_state(child, state, Visibility::Visible)
112}
113/// If the widget is [`Hidden`](Visibility::Hidden).
114#[property(CONTEXT)]
115pub fn is_hidden(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
116    visibility_eq_state(child, state, Visibility::Hidden)
117}
118/// If the widget is [`Collapsed`](Visibility::Collapsed).
119#[property(CONTEXT)]
120pub fn is_collapsed(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
121    visibility_eq_state(child, state, Visibility::Collapsed)
122}
123
124/// Defines if the widget only renders if it's bounds intersects with the viewport auto-hide rectangle.
125///
126/// The auto-hide rect is usually `(1.vw(), 1.vh())` of extra space around the viewport, so only widgets that transform
127/// themselves very far need to set this, disabling auto-hide for a widget does not disable it for descendants.
128///
129/// # Examples
130///
131/// The example demonstrates a container that is fixed in the scroll viewport, it sets the `x` and `y` properties
132/// to always stay in frame. Because the container is layout out of view and just transformed back into view it
133/// auto-hides while visible, the example uses `auto_hide = false;` to fix the issue.
134///
135/// ```
136/// # macro_rules! Container { ($($tt:tt)*) => { UiNode::nil() }}
137/// # use zng_app::widget::node::*;
138/// fn center_viewport(msg: impl IntoUiNode) -> UiNode {
139///     Container! {
140///         layout::x = merge_var!(SCROLL.horizontal_offset(), SCROLL.zoom_scale(), |&h, &s| h.0.fct_l()
141///             - 1.vw() / s * h);
142///         layout::y = merge_var!(SCROLL.vertical_offset(), SCROLL.zoom_scale(), |&v, &s| v.0.fct_l() - 1.vh() / s * v);
143///         layout::scale = SCROLL.zoom_scale().map(|&fct| 1.fct() / fct);
144///         layout::transform_origin = 0;
145///         widget::auto_hide = false;
146///         layout::max_size = (1.vw(), 1.vh());
147///
148///         child_align = Align::CENTER;
149///         child = msg;
150///     }
151/// }
152/// ```
153#[property(CONTEXT, default(true))]
154pub fn auto_hide(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
155    let enabled = enabled.into_var();
156
157    match_node(child, move |_, op| match op {
158        UiNodeOp::Init => {
159            WIDGET.sub_var(&enabled);
160        }
161        UiNodeOp::Update { .. } => {
162            if let Some(new) = enabled.get_new()
163                && WIDGET.bounds().can_auto_hide() != new
164            {
165                WIDGET.layout().render();
166            }
167        }
168        UiNodeOp::Layout { wl, .. } => {
169            wl.allow_auto_hide(enabled.get());
170        }
171        _ => {}
172    })
173}
174
175event_property! {
176    /// Widget global inner transform changed.
177    pub fn transform_changed {
178        event: info::TRANSFORM_CHANGED_EVENT,
179        args: info::TransformChangedArgs,
180    }
181
182    /// Widget global position changed.
183    pub fn move {
184        event: info::TRANSFORM_CHANGED_EVENT,
185        args: info::TransformChangedArgs,
186        filter: |a| a.offset(WIDGET.id()).unwrap_or_default() != PxVector::zero(),
187    }
188
189    /// Widget visibility changed.
190    pub fn visibility_changed {
191        event: info::VISIBILITY_CHANGED_EVENT,
192        args: info::VisibilityChangedArgs,
193    }
194
195    /// Widget visibility changed to collapsed.
196    pub fn collapse {
197        event: info::VISIBILITY_CHANGED_EVENT,
198        args: info::VisibilityChangedArgs,
199        filter: |a| a.is_collapse(WIDGET.id()),
200    }
201
202    /// Widget visibility changed to hidden.
203    pub fn hide {
204        event: info::VISIBILITY_CHANGED_EVENT,
205        args: info::VisibilityChangedArgs,
206        filter: |a| a.is_hide(WIDGET.id()),
207    }
208
209    /// Widget visibility changed to visible.
210    ///
211    /// Note that widgets are **already marked visible** before the first render so this event does not fire on init.
212    pub fn show {
213        event: info::VISIBILITY_CHANGED_EVENT,
214        args: info::VisibilityChangedArgs,
215        filter: |a| a.is_show(WIDGET.id()),
216    }
217}