fyrox_ui/
progress_bar.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Progress bar is used to show a bar that fills in from left to right according to the progress value. It is used to
22//! show progress for long actions. See [`ProgressBar`] widget docs for more info and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::style::resource::StyleResourceExt;
27use crate::style::Style;
28use crate::{
29    border::BorderBuilder,
30    canvas::CanvasBuilder,
31    core::{
32        algebra::Vector2, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
33        visitor::prelude::*,
34    },
35    define_constructor,
36    message::{MessageDirection, UiMessage},
37    widget::{Widget, WidgetBuilder, WidgetMessage},
38    BuildContext, Control, UiNode, UserInterface,
39};
40use fyrox_core::uuid_provider;
41use fyrox_core::variable::InheritableVariable;
42use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
43use std::ops::{Deref, DerefMut};
44
45/// A set of messages that can be used to modify the state of a progress bar.
46#[derive(Debug, Clone, PartialEq)]
47pub enum ProgressBarMessage {
48    /// A message, that is used to set progress of the progress bar.
49    Progress(f32),
50}
51
52impl ProgressBarMessage {
53    define_constructor!(
54        /// Creates [`ProgressBarMessage::Progress`].
55        ProgressBarMessage:Progress => fn progress(f32), layout: false
56    );
57}
58
59/// Progress bar is used to show a bar that fills in from left to right according to the progress value. It is used to
60/// show progress for long actions.
61///
62/// ## Example
63///
64/// ```rust
65/// # use fyrox_ui::{
66/// #     core::pool::Handle, progress_bar::ProgressBarBuilder, widget::WidgetBuilder, BuildContext,
67/// #     UiNode,
68/// # };
69/// fn create_progress_bar(ctx: &mut BuildContext) -> Handle<UiNode> {
70///     ProgressBarBuilder::new(WidgetBuilder::new())
71///         // Keep mind, that the progress is "normalized", which means that it is defined on
72///         // [0..1] range, where 0 - no progress at all, 1 - maximum progress.
73///         .with_progress(0.25)
74///         .build(ctx)
75/// }
76/// ```
77///
78/// ## Changing progress
79///
80/// To change progress of a progress bar all you need is to send [`ProgressBarMessage::Progress`] to it:
81///
82/// ```rust
83/// # use fyrox_ui::{
84/// #     core::pool::Handle, message::MessageDirection, progress_bar::ProgressBarMessage, UiNode,
85/// #     UserInterface,
86/// # };
87/// fn change_progress(progress_bar: Handle<UiNode>, ui: &UserInterface) {
88///     ui.send_message(ProgressBarMessage::progress(
89///         progress_bar,
90///         MessageDirection::ToWidget,
91///         0.33,
92///     ));
93/// }
94/// ```
95#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
96pub struct ProgressBar {
97    /// Base widget of the progress bar.
98    pub widget: Widget,
99    /// Current progress of the progress bar.
100    pub progress: InheritableVariable<f32>,
101    /// Handle of a widget that is used to show the progress.
102    pub indicator: InheritableVariable<Handle<UiNode>>,
103    /// Container widget of the bar of the progress bar.
104    pub body: InheritableVariable<Handle<UiNode>>,
105}
106
107impl ConstructorProvider<UiNode, UserInterface> for ProgressBar {
108    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
109        GraphNodeConstructor::new::<Self>()
110            .with_variant("Progress Bar", |ui| {
111                ProgressBarBuilder::new(WidgetBuilder::new().with_name("Progress Bar"))
112                    .build(&mut ui.build_ctx())
113                    .into()
114            })
115            .with_group("Visual")
116    }
117}
118
119crate::define_widget_deref!(ProgressBar);
120
121uuid_provider!(ProgressBar = "d6ebb853-d945-46bc-86db-4c8b5d5faf8e");
122
123impl Control for ProgressBar {
124    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
125        let size = self.widget.arrange_override(ui, final_size);
126
127        ui.send_message(WidgetMessage::width(
128            *self.indicator,
129            MessageDirection::ToWidget,
130            size.x * *self.progress,
131        ));
132
133        ui.send_message(WidgetMessage::height(
134            *self.indicator,
135            MessageDirection::ToWidget,
136            size.y,
137        ));
138
139        size
140    }
141
142    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
143        self.widget.handle_routed_message(ui, message);
144
145        if message.destination() == self.handle {
146            if let Some(&ProgressBarMessage::Progress(progress)) =
147                message.data::<ProgressBarMessage>()
148            {
149                if progress != *self.progress {
150                    self.set_progress(progress);
151                    self.invalidate_layout();
152                }
153            }
154        }
155    }
156}
157
158impl ProgressBar {
159    fn set_progress(&mut self, progress: f32) {
160        self.progress
161            .set_value_and_mark_modified(progress.clamp(0.0, 1.0));
162    }
163}
164
165/// Progress bar builder creates progress bar instances and adds them to the UI.
166pub struct ProgressBarBuilder {
167    widget_builder: WidgetBuilder,
168    body: Option<Handle<UiNode>>,
169    indicator: Option<Handle<UiNode>>,
170    progress: f32,
171}
172
173impl ProgressBarBuilder {
174    /// Creates new builder instance.
175    pub fn new(widget_builder: WidgetBuilder) -> Self {
176        Self {
177            widget_builder,
178            body: None,
179            indicator: None,
180            progress: 0.0,
181        }
182    }
183
184    /// Sets the desired body of the progress bar, which is used to wrap the indicator (bar).
185    pub fn with_body(mut self, body: Handle<UiNode>) -> Self {
186        self.body = Some(body);
187        self
188    }
189
190    /// Sets the desired indicator widget, that will be used to show the progress.
191    pub fn with_indicator(mut self, indicator: Handle<UiNode>) -> Self {
192        self.indicator = Some(indicator);
193        self
194    }
195
196    /// Sets the desired progress value. Input value will be clamped to `[0..1]` range.
197    pub fn with_progress(mut self, progress: f32) -> Self {
198        self.progress = progress.clamp(0.0, 1.0);
199        self
200    }
201
202    /// Finishes progress bar creation and adds the new instance to the user interface.
203    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
204        let body = self
205            .body
206            .unwrap_or_else(|| BorderBuilder::new(WidgetBuilder::new()).build(ctx));
207
208        let indicator = self.indicator.unwrap_or_else(|| {
209            BorderBuilder::new(
210                WidgetBuilder::new().with_background(ctx.style.property(Style::BRUSH_BRIGHTEST)),
211            )
212            .build(ctx)
213        });
214
215        let canvas = CanvasBuilder::new(WidgetBuilder::new().with_child(indicator)).build(ctx);
216
217        ctx.link(canvas, body);
218
219        let progress_bar = ProgressBar {
220            widget: self.widget_builder.with_child(body).build(ctx),
221            progress: self.progress.into(),
222            indicator: indicator.into(),
223            body: body.into(),
224        };
225
226        ctx.add_node(UiNode::new(progress_bar))
227    }
228}
229
230#[cfg(test)]
231mod test {
232    use crate::progress_bar::ProgressBarBuilder;
233    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
234
235    #[test]
236    fn test_deletion() {
237        test_widget_deletion(|ctx| ProgressBarBuilder::new(WidgetBuilder::new()).build(ctx));
238    }
239}