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