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 UiNode, visibility: impl IntoVar<Visibility>) -> impl 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 UiNode, state: impl IntoVar<bool>, expected: Visibility) -> impl 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 UiNode, state: impl IntoVar<bool>) -> impl 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 UiNode, state: impl IntoVar<bool>) -> impl 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 UiNode, state: impl IntoVar<bool>) -> impl 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)*) => { NilUiNode }}
137/// # use zng_app::widget::node::*;
138/// fn center_viewport(msg: impl UiNode) -> impl UiNode {
139///     Container! {
140///         layout::x = merge_var!(SCROLL.horizontal_offset(), SCROLL.zoom_scale(), |&h, &s| h.0.fct_l() - 1.vw() / s * h);
141///         layout::y = merge_var!(SCROLL.vertical_offset(), SCROLL.zoom_scale(), |&v, &s| v.0.fct_l() - 1.vh() / s * v);
142///         layout::scale = SCROLL.zoom_scale().map(|&fct| 1.fct() / fct);
143///         layout::transform_origin = 0;
144///         widget::auto_hide = false;
145///         layout::max_size = (1.vw(), 1.vh());
146///
147///         child_align = Align::CENTER;
148///         child = msg;
149///     }
150/// }
151/// ```
152#[property(CONTEXT, default(true))]
153pub fn auto_hide(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
154    let enabled = enabled.into_var();
155
156    match_node(child, move |_, op| match op {
157        UiNodeOp::Init => {
158            WIDGET.sub_var(&enabled);
159        }
160        UiNodeOp::Update { .. } => {
161            if let Some(new) = enabled.get_new() {
162                if WIDGET.bounds().can_auto_hide() != new {
163                    WIDGET.layout().render();
164                }
165            }
166        }
167        UiNodeOp::Layout { wl, .. } => {
168            wl.allow_auto_hide(enabled.get());
169        }
170        _ => {}
171    })
172}
173
174event_property! {
175    /// Widget global inner transform changed.
176    pub fn transform_changed {
177        event: info::TRANSFORM_CHANGED_EVENT,
178        args: info::TransformChangedArgs,
179    }
180
181    /// Widget global position changed.
182    pub fn move {
183        event: info::TRANSFORM_CHANGED_EVENT,
184        args: info::TransformChangedArgs,
185        filter: |a| a.offset(WIDGET.id()).unwrap_or_default() != PxVector::zero(),
186    }
187
188    /// Widget visibility changed.
189    pub fn visibility_changed {
190        event: info::VISIBILITY_CHANGED_EVENT,
191        args: info::VisibilityChangedArgs,
192    }
193
194    /// Widget visibility changed to collapsed.
195    pub fn collapse {
196        event: info::VISIBILITY_CHANGED_EVENT,
197        args: info::VisibilityChangedArgs,
198        filter: |a| a.is_collapse(WIDGET.id()),
199    }
200
201    /// Widget visibility changed to hidden.
202    pub fn hide {
203        event: info::VISIBILITY_CHANGED_EVENT,
204        args: info::VisibilityChangedArgs,
205        filter: |a| a.is_hide(WIDGET.id()),
206    }
207
208    /// Widget visibility changed to visible.
209    ///
210    /// Note that widgets are **already marked visible** before the first render so this event does not fire on init.
211    pub fn show {
212        event: info::VISIBILITY_CHANGED_EVENT,
213        args: info::VisibilityChangedArgs,
214        filter: |a| a.is_show(WIDGET.id()),
215    }
216}