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};
40
41use fyrox_core::uuid_provider;
42use fyrox_core::variable::InheritableVariable;
43use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
44use std::ops::{Deref, DerefMut};
45
46/// A set of messages that can be used to modify the state of a progress bar.
47#[derive(Debug, Clone, PartialEq)]
48pub enum ProgressBarMessage {
49 /// A message, that is used to set progress of the progress bar.
50 Progress(f32),
51}
52
53impl ProgressBarMessage {
54 define_constructor!(
55 /// Creates [`ProgressBarMessage::Progress`].
56 ProgressBarMessage:Progress => fn progress(f32), layout: false
57 );
58}
59
60/// Progress bar is used to show a bar that fills in from left to right according to the progress value. It is used to
61/// show progress for long actions.
62///
63/// ## Example
64///
65/// ```rust
66/// # use fyrox_ui::{
67/// # core::pool::Handle, progress_bar::ProgressBarBuilder, widget::WidgetBuilder, BuildContext,
68/// # UiNode,
69/// # };
70/// fn create_progress_bar(ctx: &mut BuildContext) -> Handle<UiNode> {
71/// ProgressBarBuilder::new(WidgetBuilder::new())
72/// // Keep mind, that the progress is "normalized", which means that it is defined on
73/// // [0..1] range, where 0 - no progress at all, 1 - maximum progress.
74/// .with_progress(0.25)
75/// .build(ctx)
76/// }
77/// ```
78///
79/// ## Style
80///
81/// It is possible to specify custom indicator (the part that show the progress) and the back of
82/// the progress bar. Use [`ProgressBarBuilder::with_indicator`] and [`ProgressBarBuilder::with_body`]
83/// methods respectively. These methods can accept any widget, but usually it i a
84/// [`crate::border::Border`], [`crate::image::Image`], [`crate::nine_patch::NinePatch`] widgets.
85///
86/// ## Changing progress
87///
88/// To change progress of a progress bar all you need is to send [`ProgressBarMessage::Progress`] to it:
89///
90/// ```rust
91/// # use fyrox_ui::{
92/// # core::pool::Handle, message::MessageDirection, progress_bar::ProgressBarMessage, UiNode,
93/// # UserInterface,
94/// # };
95/// fn change_progress(progress_bar: Handle<UiNode>, ui: &UserInterface) {
96/// ui.send_message(ProgressBarMessage::progress(
97/// progress_bar,
98/// MessageDirection::ToWidget,
99/// 0.33,
100/// ));
101/// }
102/// ```
103#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
104#[reflect(derived_type = "UiNode")]
105pub struct ProgressBar {
106 /// Base widget of the progress bar.
107 pub widget: Widget,
108 /// Current progress of the progress bar.
109 pub progress: InheritableVariable<f32>,
110 /// Handle of a widget that is used to show the progress.
111 pub indicator: InheritableVariable<Handle<UiNode>>,
112 /// Container widget of the bar of the progress bar.
113 pub body: InheritableVariable<Handle<UiNode>>,
114}
115
116impl ConstructorProvider<UiNode, UserInterface> for ProgressBar {
117 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
118 GraphNodeConstructor::new::<Self>()
119 .with_variant("Progress Bar", |ui| {
120 ProgressBarBuilder::new(WidgetBuilder::new().with_name("Progress Bar"))
121 .build(&mut ui.build_ctx())
122 .into()
123 })
124 .with_group("Visual")
125 }
126}
127
128crate::define_widget_deref!(ProgressBar);
129
130uuid_provider!(ProgressBar = "d6ebb853-d945-46bc-86db-4c8b5d5faf8e");
131
132impl Control for ProgressBar {
133 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
134 let size = self.widget.arrange_override(ui, final_size);
135
136 ui.send_message(WidgetMessage::width(
137 *self.indicator,
138 MessageDirection::ToWidget,
139 size.x * *self.progress,
140 ));
141
142 ui.send_message(WidgetMessage::height(
143 *self.indicator,
144 MessageDirection::ToWidget,
145 size.y,
146 ));
147
148 size
149 }
150
151 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
152 self.widget.handle_routed_message(ui, message);
153
154 if message.destination() == self.handle {
155 if let Some(&ProgressBarMessage::Progress(progress)) =
156 message.data::<ProgressBarMessage>()
157 {
158 if progress != *self.progress {
159 self.set_progress(progress);
160 self.invalidate_layout();
161 }
162 }
163 }
164 }
165}
166
167impl ProgressBar {
168 fn set_progress(&mut self, progress: f32) {
169 self.progress
170 .set_value_and_mark_modified(progress.clamp(0.0, 1.0));
171 }
172}
173
174/// Progress bar builder creates progress bar instances and adds them to the UI.
175pub struct ProgressBarBuilder {
176 widget_builder: WidgetBuilder,
177 body: Option<Handle<UiNode>>,
178 indicator: Option<Handle<UiNode>>,
179 progress: f32,
180}
181
182impl ProgressBarBuilder {
183 /// Creates new builder instance.
184 pub fn new(widget_builder: WidgetBuilder) -> Self {
185 Self {
186 widget_builder,
187 body: None,
188 indicator: None,
189 progress: 0.0,
190 }
191 }
192
193 /// Sets the desired body of the progress bar, which is used to wrap the indicator (bar).
194 pub fn with_body(mut self, body: Handle<UiNode>) -> Self {
195 self.body = Some(body);
196 self
197 }
198
199 /// Sets the desired indicator widget, that will be used to show the progress.
200 pub fn with_indicator(mut self, indicator: Handle<UiNode>) -> Self {
201 self.indicator = Some(indicator);
202 self
203 }
204
205 /// Sets the desired progress value. Input value will be clamped to `[0..1]` range.
206 pub fn with_progress(mut self, progress: f32) -> Self {
207 self.progress = progress.clamp(0.0, 1.0);
208 self
209 }
210
211 /// Finishes progress bar creation and adds the new instance to the user interface.
212 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
213 let body = self
214 .body
215 .unwrap_or_else(|| BorderBuilder::new(WidgetBuilder::new()).build(ctx));
216
217 let indicator = self.indicator.unwrap_or_else(|| {
218 BorderBuilder::new(
219 WidgetBuilder::new().with_background(ctx.style.property(Style::BRUSH_BRIGHTEST)),
220 )
221 .build(ctx)
222 });
223
224 let canvas = CanvasBuilder::new(WidgetBuilder::new().with_child(indicator)).build(ctx);
225
226 ctx.link(canvas, body);
227
228 let progress_bar = ProgressBar {
229 widget: self.widget_builder.with_child(body).build(ctx),
230 progress: self.progress.into(),
231 indicator: indicator.into(),
232 body: body.into(),
233 };
234
235 ctx.add_node(UiNode::new(progress_bar))
236 }
237}
238
239#[cfg(test)]
240mod test {
241 use crate::progress_bar::ProgressBarBuilder;
242 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
243
244 #[test]
245 fn test_deletion() {
246 test_widget_deletion(|ctx| ProgressBarBuilder::new(WidgetBuilder::new()).build(ctx));
247 }
248}