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    width: Length,
63    height: Option<Length>,
64    class: Theme::Class<'a>,
65}
66
67impl<'a, Theme> ProgressBar<'a, Theme>
68where
69    Theme: Catalog,
70{
71    /// The default height of a [`ProgressBar`].
72    pub const DEFAULT_HEIGHT: f32 = 30.0;
73
74    /// Creates a new [`ProgressBar`].
75    ///
76    /// It expects:
77    ///   * an inclusive range of possible values
78    ///   * the current value of the [`ProgressBar`]
79    pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
80        ProgressBar {
81            value: value.clamp(*range.start(), *range.end()),
82            range,
83            width: Length::Fill,
84            height: None,
85            class: Theme::default(),
86        }
87    }
88
89    /// Sets the width of the [`ProgressBar`].
90    pub fn width(mut self, width: impl Into<Length>) -> Self {
91        self.width = width.into();
92        self
93    }
94
95    /// Sets the height of the [`ProgressBar`].
96    pub fn height(mut self, height: impl Into<Length>) -> Self {
97        self.height = Some(height.into());
98        self
99    }
100
101    /// Sets the style of the [`ProgressBar`].
102    #[must_use]
103    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
104    where
105        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
106    {
107        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
108        self
109    }
110
111    /// Sets the style class of the [`ProgressBar`].
112    #[cfg(feature = "advanced")]
113    #[must_use]
114    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
115        self.class = class.into();
116        self
117    }
118}
119
120impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
121    for ProgressBar<'a, Theme>
122where
123    Theme: Catalog,
124    Renderer: core::Renderer,
125{
126    fn size(&self) -> Size<Length> {
127        Size {
128            width: self.width,
129            height: self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)),
130        }
131    }
132
133    fn layout(
134        &self,
135        _tree: &mut Tree,
136        _renderer: &Renderer,
137        limits: &layout::Limits,
138    ) -> layout::Node {
139        layout::atomic(
140            limits,
141            self.width,
142            self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)),
143        )
144    }
145
146    fn draw(
147        &self,
148        _state: &Tree,
149        renderer: &mut Renderer,
150        theme: &Theme,
151        _style: &renderer::Style,
152        layout: Layout<'_>,
153        _cursor: mouse::Cursor,
154        _viewport: &Rectangle,
155    ) {
156        let bounds = layout.bounds();
157        let (range_start, range_end) = self.range.clone().into_inner();
158
159        let active_progress_width = if range_start >= range_end {
160            0.0
161        } else {
162            bounds.width * (self.value - range_start)
163                / (range_end - range_start)
164        };
165
166        let style = theme.style(&self.class);
167
168        renderer.fill_quad(
169            renderer::Quad {
170                bounds: Rectangle { ..bounds },
171                border: style.border,
172                ..renderer::Quad::default()
173            },
174            style.background,
175        );
176
177        if active_progress_width > 0.0 {
178            renderer.fill_quad(
179                renderer::Quad {
180                    bounds: Rectangle {
181                        width: active_progress_width,
182                        ..bounds
183                    },
184                    border: Border {
185                        color: Color::TRANSPARENT,
186                        ..style.border
187                    },
188                    ..renderer::Quad::default()
189                },
190                style.bar,
191            );
192        }
193    }
194}
195
196impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
197    for Element<'a, Message, Theme, Renderer>
198where
199    Message: 'a,
200    Theme: 'a + Catalog,
201    Renderer: 'a + core::Renderer,
202{
203    fn from(
204        progress_bar: ProgressBar<'a, Theme>,
205    ) -> Element<'a, Message, Theme, Renderer> {
206        Element::new(progress_bar)
207    }
208}
209
210/// The appearance of a progress bar.
211#[derive(Debug, Clone, Copy, PartialEq)]
212pub struct Style {
213    /// The [`Background`] of the progress bar.
214    pub background: Background,
215    /// The [`Background`] of the bar of the progress bar.
216    pub bar: Background,
217    /// The [`Border`] of the progress bar.
218    pub border: Border,
219}
220
221/// The theme catalog of a [`ProgressBar`].
222pub trait Catalog: Sized {
223    /// The item class of the [`Catalog`].
224    type Class<'a>;
225
226    /// The default class produced by the [`Catalog`].
227    fn default<'a>() -> Self::Class<'a>;
228
229    /// The [`Style`] of a class with the given status.
230    fn style(&self, class: &Self::Class<'_>) -> Style;
231}
232
233/// A styling function for a [`ProgressBar`].
234///
235/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
236pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
237
238impl Catalog for Theme {
239    type Class<'a> = StyleFn<'a, Self>;
240
241    fn default<'a>() -> Self::Class<'a> {
242        Box::new(primary)
243    }
244
245    fn style(&self, class: &Self::Class<'_>) -> Style {
246        class(self)
247    }
248}
249
250/// The primary style of a [`ProgressBar`].
251pub fn primary(theme: &Theme) -> Style {
252    let palette = theme.extended_palette();
253
254    styled(
255        palette.background.strong.color,
256        palette.primary.strong.color,
257    )
258}
259
260/// The secondary style of a [`ProgressBar`].
261pub fn secondary(theme: &Theme) -> Style {
262    let palette = theme.extended_palette();
263
264    styled(
265        palette.background.strong.color,
266        palette.secondary.base.color,
267    )
268}
269
270/// The success style of a [`ProgressBar`].
271pub fn success(theme: &Theme) -> Style {
272    let palette = theme.extended_palette();
273
274    styled(palette.background.strong.color, palette.success.base.color)
275}
276
277/// The danger style of a [`ProgressBar`].
278pub fn danger(theme: &Theme) -> Style {
279    let palette = theme.extended_palette();
280
281    styled(palette.background.strong.color, palette.danger.base.color)
282}
283
284fn styled(
285    background: impl Into<Background>,
286    bar: impl Into<Background>,
287) -> Style {
288    Style {
289        background: background.into(),
290        bar: bar.into(),
291        border: border::rounded(2),
292    }
293}