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,
29    Widget,
30};
31
32use std::ops::RangeInclusive;
33
34/// A bar that displays progress.
35///
36/// # Example
37/// ```no_run
38/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
39/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
40/// #
41/// use iced::widget::progress_bar;
42///
43/// struct State {
44///    progress: f32,
45/// }
46///
47/// enum Message {
48///     // ...
49/// }
50///
51/// fn view(state: &State) -> Element<'_, Message> {
52///     progress_bar(0.0..=100.0, state.progress).into()
53/// }
54/// ```
55#[allow(missing_debug_implementations)]
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>
147    for ProgressBar<'_, Theme>
148where
149    Theme: Catalog,
150    Renderer: core::Renderer,
151{
152    fn size(&self) -> Size<Length> {
153        Size {
154            width: self.width(),
155            height: self.height(),
156        }
157    }
158
159    fn layout(
160        &self,
161        _tree: &mut Tree,
162        _renderer: &Renderer,
163        limits: &layout::Limits,
164    ) -> layout::Node {
165        layout::atomic(limits, self.width(), self.height())
166    }
167
168    fn draw(
169        &self,
170        _state: &Tree,
171        renderer: &mut Renderer,
172        theme: &Theme,
173        _style: &renderer::Style,
174        layout: Layout<'_>,
175        _cursor: mouse::Cursor,
176        _viewport: &Rectangle,
177    ) {
178        let bounds = layout.bounds();
179        let (range_start, range_end) = self.range.clone().into_inner();
180
181        let length = if self.is_vertical {
182            bounds.height
183        } else {
184            bounds.width
185        };
186
187        let active_progress_length = if range_start >= range_end {
188            0.0
189        } else {
190            length * (self.value - range_start) / (range_end - range_start)
191        };
192
193        let style = theme.style(&self.class);
194
195        renderer.fill_quad(
196            renderer::Quad {
197                bounds: Rectangle { ..bounds },
198                border: style.border,
199                ..renderer::Quad::default()
200            },
201            style.background,
202        );
203
204        if active_progress_length > 0.0 {
205            let bounds = if self.is_vertical {
206                Rectangle {
207                    y: bounds.y + bounds.height - active_progress_length,
208                    height: active_progress_length,
209                    ..bounds
210                }
211            } else {
212                Rectangle {
213                    width: active_progress_length,
214                    ..bounds
215                }
216            };
217
218            renderer.fill_quad(
219                renderer::Quad {
220                    bounds,
221                    border: Border {
222                        color: Color::TRANSPARENT,
223                        ..style.border
224                    },
225                    ..renderer::Quad::default()
226                },
227                style.bar,
228            );
229        }
230    }
231}
232
233impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
234    for Element<'a, Message, Theme, Renderer>
235where
236    Message: 'a,
237    Theme: 'a + Catalog,
238    Renderer: 'a + core::Renderer,
239{
240    fn from(
241        progress_bar: ProgressBar<'a, Theme>,
242    ) -> Element<'a, Message, Theme, Renderer> {
243        Element::new(progress_bar)
244    }
245}
246
247/// The appearance of a progress bar.
248#[derive(Debug, Clone, Copy, PartialEq)]
249pub struct Style {
250    /// The [`Background`] of the progress bar.
251    pub background: Background,
252    /// The [`Background`] of the bar of the progress bar.
253    pub bar: Background,
254    /// The [`Border`] of the progress bar.
255    pub border: Border,
256}
257
258/// The theme catalog of a [`ProgressBar`].
259pub trait Catalog: Sized {
260    /// The item class of the [`Catalog`].
261    type Class<'a>;
262
263    /// The default class produced by the [`Catalog`].
264    fn default<'a>() -> Self::Class<'a>;
265
266    /// The [`Style`] of a class with the given status.
267    fn style(&self, class: &Self::Class<'_>) -> Style;
268}
269
270/// A styling function for a [`ProgressBar`].
271///
272/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
273pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
274
275impl Catalog for Theme {
276    type Class<'a> = StyleFn<'a, Self>;
277
278    fn default<'a>() -> Self::Class<'a> {
279        Box::new(primary)
280    }
281
282    fn style(&self, class: &Self::Class<'_>) -> Style {
283        class(self)
284    }
285}
286
287/// The primary style of a [`ProgressBar`].
288pub fn primary(theme: &Theme) -> Style {
289    let palette = theme.extended_palette();
290
291    styled(palette.background.strong.color, palette.primary.base.color)
292}
293
294/// The secondary style of a [`ProgressBar`].
295pub fn secondary(theme: &Theme) -> Style {
296    let palette = theme.extended_palette();
297
298    styled(
299        palette.background.strong.color,
300        palette.secondary.base.color,
301    )
302}
303
304/// The success style of a [`ProgressBar`].
305pub fn success(theme: &Theme) -> Style {
306    let palette = theme.extended_palette();
307
308    styled(palette.background.strong.color, palette.success.base.color)
309}
310
311/// The warning style of a [`ProgressBar`].
312pub fn warning(theme: &Theme) -> Style {
313    let palette = theme.extended_palette();
314
315    styled(palette.background.strong.color, palette.warning.base.color)
316}
317
318/// The danger style of a [`ProgressBar`].
319pub fn danger(theme: &Theme) -> Style {
320    let palette = theme.extended_palette();
321
322    styled(palette.background.strong.color, palette.danger.base.color)
323}
324
325fn styled(
326    background: impl Into<Background>,
327    bar: impl Into<Background>,
328) -> Style {
329    Style {
330        background: background.into(),
331        bar: bar.into(),
332        border: border::rounded(2),
333    }
334}