Skip to main content

iced_widget/
progress_bar.rs

1//! Progress bars visualize the progression of an extended computer operation, such as a download, file transfer, or installation.
2//!
3//! # Example
4//! ```no_run
5//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
6//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
7//! #
8//! use iced::widget::progress_bar;
9//!
10//! struct State {
11//!    progress: f32,
12//! }
13//!
14//! enum Message {
15//!     // ...
16//! }
17//!
18//! fn view(state: &State) -> Element<'_, Message> {
19//!     progress_bar(0.0..=100.0, state.progress).into()
20//! }
21//! ```
22use crate::core::border::{self, Border};
23use crate::core::layout;
24use crate::core::mouse;
25use crate::core::renderer;
26use crate::core::widget::Tree;
27use crate::core::widget::operation::Operation;
28use crate::core::widget::operation::accessible::{Accessible, Live, Role, Value};
29use crate::core::{
30    self, Background, Color, Element, Layout, Length, Rectangle, Size, Theme, Widget,
31};
32
33use std::ops::RangeInclusive;
34
35/// A bar that displays progress.
36///
37/// # Example
38/// ```no_run
39/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
40/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
41/// #
42/// use iced::widget::progress_bar;
43///
44/// struct State {
45///    progress: f32,
46/// }
47///
48/// enum Message {
49///     // ...
50/// }
51///
52/// fn view(state: &State) -> Element<'_, Message> {
53///     progress_bar(0.0..=100.0, state.progress).into()
54/// }
55/// ```
56pub struct ProgressBar<'a, Theme = crate::Theme>
57where
58    Theme: Catalog,
59{
60    range: RangeInclusive<f32>,
61    value: f32,
62    length: Length,
63    girth: Length,
64    is_vertical: bool,
65    class: Theme::Class<'a>,
66}
67
68impl<'a, Theme> ProgressBar<'a, Theme>
69where
70    Theme: Catalog,
71{
72    /// The default girth of a [`ProgressBar`].
73    pub const DEFAULT_GIRTH: f32 = 30.0;
74
75    /// Creates a new [`ProgressBar`].
76    ///
77    /// It expects:
78    ///   * an inclusive range of possible values
79    ///   * the current value of the [`ProgressBar`]
80    pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
81        ProgressBar {
82            value: value.clamp(*range.start(), *range.end()),
83            range,
84            length: Length::Fill,
85            girth: Length::from(Self::DEFAULT_GIRTH),
86            is_vertical: false,
87            class: Theme::default(),
88        }
89    }
90
91    /// Sets the width of the [`ProgressBar`].
92    pub fn length(mut self, length: impl Into<Length>) -> Self {
93        self.length = length.into();
94        self
95    }
96
97    /// Sets the height of the [`ProgressBar`].
98    pub fn girth(mut self, girth: impl Into<Length>) -> Self {
99        self.girth = girth.into();
100        self
101    }
102
103    /// Turns the [`ProgressBar`] into a vertical [`ProgressBar`].
104    ///
105    /// By default, a [`ProgressBar`] is horizontal.
106    pub fn vertical(mut self) -> Self {
107        self.is_vertical = true;
108        self
109    }
110
111    /// Sets the style of the [`ProgressBar`].
112    #[must_use]
113    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
114    where
115        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
116    {
117        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
118        self
119    }
120
121    /// Sets the style class of the [`ProgressBar`].
122    #[cfg(feature = "advanced")]
123    #[must_use]
124    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
125        self.class = class.into();
126        self
127    }
128
129    fn width(&self) -> Length {
130        if self.is_vertical {
131            self.girth
132        } else {
133            self.length
134        }
135    }
136
137    fn height(&self) -> Length {
138        if self.is_vertical {
139            self.length
140        } else {
141            self.girth
142        }
143    }
144}
145
146impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for ProgressBar<'_, Theme>
147where
148    Theme: Catalog,
149    Renderer: core::Renderer,
150{
151    fn size(&self) -> Size<Length> {
152        Size {
153            width: self.width(),
154            height: self.height(),
155        }
156    }
157
158    fn layout(
159        &mut self,
160        _tree: &mut Tree,
161        _renderer: &Renderer,
162        limits: &layout::Limits,
163    ) -> layout::Node {
164        layout::atomic(limits, self.width(), self.height())
165    }
166
167    fn operate(
168        &mut self,
169        _tree: &mut Tree,
170        layout: Layout<'_>,
171        _renderer: &Renderer,
172        operation: &mut dyn Operation,
173    ) {
174        operation.accessible(
175            None,
176            layout.bounds(),
177            &Accessible {
178                role: Role::ProgressIndicator,
179                value: Some(Value::Numeric {
180                    current: self.value as f64,
181                    min: *self.range.start() as f64,
182                    max: *self.range.end() as f64,
183                    step: None,
184                }),
185                live: Some(Live::Polite),
186                ..Accessible::default()
187            },
188        );
189    }
190
191    fn draw(
192        &self,
193        _tree: &Tree,
194        renderer: &mut Renderer,
195        theme: &Theme,
196        _style: &renderer::Style,
197        layout: Layout<'_>,
198        _cursor: mouse::Cursor,
199        _viewport: &Rectangle,
200    ) {
201        let bounds = layout.bounds();
202        let (range_start, range_end) = self.range.clone().into_inner();
203
204        let length = if self.is_vertical {
205            bounds.height
206        } else {
207            bounds.width
208        };
209
210        let active_progress_length = if range_start >= range_end {
211            0.0
212        } else {
213            length * (self.value - range_start) / (range_end - range_start)
214        };
215
216        let style = theme.style(&self.class);
217
218        renderer.fill_quad(
219            renderer::Quad {
220                bounds: Rectangle { ..bounds },
221                border: style.border,
222                ..renderer::Quad::default()
223            },
224            style.background,
225        );
226
227        if active_progress_length > 0.0 {
228            let bounds = if self.is_vertical {
229                Rectangle {
230                    y: bounds.y + bounds.height - active_progress_length,
231                    height: active_progress_length,
232                    ..bounds
233                }
234            } else {
235                Rectangle {
236                    width: active_progress_length,
237                    ..bounds
238                }
239            };
240
241            renderer.fill_quad(
242                renderer::Quad {
243                    bounds,
244                    border: Border {
245                        color: Color::TRANSPARENT,
246                        ..style.border
247                    },
248                    ..renderer::Quad::default()
249                },
250                style.bar,
251            );
252        }
253    }
254}
255
256impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
257    for Element<'a, Message, Theme, Renderer>
258where
259    Message: 'a,
260    Theme: 'a + Catalog,
261    Renderer: 'a + core::Renderer,
262{
263    fn from(progress_bar: ProgressBar<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
264        Element::new(progress_bar)
265    }
266}
267
268/// The appearance of a progress bar.
269#[derive(Debug, Clone, Copy, PartialEq)]
270pub struct Style {
271    /// The [`Background`] of the progress bar.
272    pub background: Background,
273    /// The [`Background`] of the bar of the progress bar.
274    pub bar: Background,
275    /// The [`Border`] of the progress bar.
276    pub border: Border,
277}
278
279/// The theme catalog of a [`ProgressBar`].
280pub trait Catalog: Sized {
281    /// The item class of the [`Catalog`].
282    type Class<'a>;
283
284    /// The default class produced by the [`Catalog`].
285    fn default<'a>() -> Self::Class<'a>;
286
287    /// The [`Style`] of a class with the given status.
288    fn style(&self, class: &Self::Class<'_>) -> Style;
289}
290
291/// A styling function for a [`ProgressBar`].
292///
293/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
294pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
295
296impl Catalog for Theme {
297    type Class<'a> = StyleFn<'a, Self>;
298
299    fn default<'a>() -> Self::Class<'a> {
300        Box::new(primary)
301    }
302
303    fn style(&self, class: &Self::Class<'_>) -> Style {
304        class(self)
305    }
306}
307
308/// The primary style of a [`ProgressBar`].
309pub fn primary(theme: &Theme) -> Style {
310    let palette = theme.palette();
311
312    styled(palette.background.strong.color, palette.primary.base.color)
313}
314
315/// The secondary style of a [`ProgressBar`].
316pub fn secondary(theme: &Theme) -> Style {
317    let palette = theme.palette();
318
319    styled(
320        palette.background.strong.color,
321        palette.secondary.base.color,
322    )
323}
324
325/// The success style of a [`ProgressBar`].
326pub fn success(theme: &Theme) -> Style {
327    let palette = theme.palette();
328
329    styled(palette.background.strong.color, palette.success.base.color)
330}
331
332/// The warning style of a [`ProgressBar`].
333pub fn warning(theme: &Theme) -> Style {
334    let palette = theme.palette();
335
336    styled(palette.background.strong.color, palette.warning.base.color)
337}
338
339/// The danger style of a [`ProgressBar`].
340pub fn danger(theme: &Theme) -> Style {
341    let palette = theme.palette();
342
343    styled(palette.background.strong.color, palette.danger.base.color)
344}
345
346fn styled(background: impl Into<Background>, bar: impl Into<Background>) -> Style {
347    Style {
348        background: background.into(),
349        bar: bar.into(),
350        border: border::rounded(2),
351    }
352}