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