zng_wgt_progress/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Progress indicator widget.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14use zng_app::handler::FilterWidgetHandler;
15use zng_wgt::{base_color, prelude::*, visibility};
16use zng_wgt_container::{Container, child_out_bottom};
17use zng_wgt_fill::background_color;
18use zng_wgt_size_offset::{height, width, x};
19use zng_wgt_style::{Style, StyleMix, impl_style_fn, style_fn};
20
21pub use zng_task::Progress;
22
23/// Progress indicator widget.
24#[widget($crate::ProgressView {
25    ($progress:expr) => {
26        progress = $progress;
27    };
28})]
29pub struct ProgressView(StyleMix<WidgetBase>);
30impl ProgressView {
31    fn widget_intrinsic(&mut self) {
32        self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
33        widget_set! {
34            self;
35            style_base_fn = style_fn!(|_| DefaultStyle!());
36        }
37    }
38}
39impl_style_fn!(ProgressView);
40
41context_var! {
42    /// The progress status value in a [`ProgressView`](struct@ProgressView)
43    pub static PROGRESS_VAR: Progress = Progress::indeterminate();
44}
45
46/// The progress status to be displayed.
47///
48/// This property sets the [`PROGRESS_VAR`].
49#[property(CONTEXT, default(PROGRESS_VAR), widget_impl(ProgressView))]
50pub fn progress(child: impl UiNode, progress: impl IntoVar<Progress>) -> impl UiNode {
51    with_context_var(child, PROGRESS_VAR, progress)
52}
53
54/// Collapse visibility when [`Progress::is_complete`].
55#[property(CONTEXT, default(false), widget_impl(ProgressView))]
56pub fn collapse_complete(child: impl UiNode, collapse: impl IntoVar<bool>) -> impl UiNode {
57    let collapse = collapse.into_var();
58    visibility(
59        child,
60        expr_var! {
61            if #{PROGRESS_VAR}.is_complete() && *#{collapse} {
62                Visibility::Collapsed
63            } else {
64                Visibility::Visible
65            }
66        },
67    )
68}
69
70/// Event raised for each progress update, and once after info init.
71///
72/// This event works in any context that sets [`PROGRESS_VAR`].
73#[property(EVENT, widget_impl(ProgressView))]
74pub fn on_progress(child: impl UiNode, mut handler: impl WidgetHandler<Progress>) -> impl UiNode {
75    // copied from `on_info_init`
76    enum State {
77        WaitInfo,
78        InfoInited,
79        Done,
80    }
81    let mut state = State::WaitInfo;
82
83    match_node(child, move |c, op| match op {
84        UiNodeOp::Init => {
85            WIDGET.sub_var(&PROGRESS_VAR);
86            state = State::WaitInfo;
87        }
88        UiNodeOp::Info { .. } => {
89            if let State::WaitInfo = &state {
90                state = State::InfoInited;
91                WIDGET.update();
92            }
93        }
94        UiNodeOp::Update { updates } => {
95            c.update(updates);
96
97            match state {
98                State::Done => {
99                    if PROGRESS_VAR.is_new() {
100                        PROGRESS_VAR.with(|u| handler.event(u));
101                    } else {
102                        handler.update();
103                    }
104                }
105                State::InfoInited => {
106                    PROGRESS_VAR.with(|u| handler.event(u));
107                    state = State::Done;
108                }
109                State::WaitInfo => {}
110            }
111        }
112        _ => {}
113    })
114}
115
116/// Event raised when progress updates to a complete state or inits completed.
117///
118/// This event works in any context that sets [`PROGRESS_VAR`].
119#[property(EVENT, widget_impl(ProgressView))]
120pub fn on_complete(child: impl UiNode, handler: impl WidgetHandler<Progress>) -> impl UiNode {
121    let mut is_complete = false;
122    on_progress(
123        child,
124        FilterWidgetHandler::new(handler, move |u| {
125            let complete = u.is_complete();
126            if complete != is_complete {
127                is_complete = complete;
128                return is_complete;
129            }
130            false
131        }),
132    )
133}
134
135/// Getter property that is `true` when progress is indeterminate.
136///
137/// This event works in any context that sets [`PROGRESS_VAR`].
138#[property(EVENT, widget_impl(ProgressView))]
139pub fn is_indeterminate(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
140    bind_state(child, PROGRESS_VAR.map(|p| p.is_indeterminate()), state)
141}
142
143/// Progress view default style (progress bar with message text).
144#[widget($crate::DefaultStyle)]
145pub struct DefaultStyle(Style);
146impl DefaultStyle {
147    fn widget_intrinsic(&mut self) {
148        let indeterminate_x = var(Length::from(0));
149        let mut indeterminate_animation = None;
150        let indeterminate_width = 10.pct();
151        widget_set! {
152            self;
153            base_color = light_dark(rgb(0.82, 0.82, 0.82), rgb(0.18, 0.18, 0.18));
154
155            zng_wgt_container::child = Container! {
156                height = 5;
157                background_color = colors::BASE_COLOR_VAR.rgba();
158
159                clip_to_bounds = true;
160                child_align = Align::FILL_START;
161                child = zng_wgt::Wgt! {
162                    background_color = colors::ACCENT_COLOR_VAR.rgba();
163
164                    #[easing(200.ms())]
165                    width = PROGRESS_VAR.map(|p| Length::from(p.fct()));
166
167                    on_progress = hn!(indeterminate_x, |p: &Progress| {
168                        if p.is_indeterminate() {
169                            // only animates when actually indeterminate
170                            if indeterminate_animation.is_none() {
171                                let h = indeterminate_x.sequence(move |i| {
172                                    use zng_var::animation::easing;
173                                    i.set_ease(
174                                        -indeterminate_width,
175                                        100.pct(),
176                                        1.5.secs(),
177                                        |t| easing::ease_out(easing::quad, t),
178                                    )
179                                });
180                                indeterminate_animation = Some(h);
181                            }
182                        } else {
183                            indeterminate_animation = None;
184                        }
185                    });
186                    when *#{PROGRESS_VAR.map(|p| p.is_indeterminate())} {
187                        width = indeterminate_width;
188                        x = indeterminate_x;
189                    }
190                };
191            };
192
193            child_out_bottom = zng_wgt_text::Text! {
194                txt = PROGRESS_VAR.map(|p| p.msg());
195                zng_wgt::visibility = PROGRESS_VAR.map(|p| Visibility::from(!p.msg().is_empty()));
196                zng_wgt::align = Align::CENTER;
197            }, 6;
198        }
199    }
200}
201
202/// Progress view style that is only the progress bar, no message text.
203#[widget($crate::SimpleBarStyle)]
204pub struct SimpleBarStyle(DefaultStyle);
205impl SimpleBarStyle {
206    fn widget_intrinsic(&mut self) {
207        widget_set! {
208            self;
209            child_out_bottom = unset!;
210        }
211    }
212}