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/// ```
55pub struct ProgressBar<'a, Theme = crate::Theme>
56where
57    Theme: Catalog,
58{
59    range: RangeInclusive<f32>,
60    value: f32,
61    length: Length,
62    girth: Length,
63    is_vertical: bool,
64    class: Theme::Class<'a>,
65}
66
67impl<'a, Theme> ProgressBar<'a, Theme>
68where
69    Theme: Catalog,
70{
71    /// The default girth of a [`ProgressBar`].
72    pub const DEFAULT_GIRTH: 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            length: Length::Fill,
84            girth: Length::from(Self::DEFAULT_GIRTH),
85            is_vertical: false,
86            class: Theme::default(),
87        }
88    }
89
90    /// Sets the width of the [`ProgressBar`].
91    pub fn length(mut self, length: impl Into<Length>) -> Self {
92        self.length = length.into();
93        self
94    }
95
96    /// Sets the height of the [`ProgressBar`].
97    pub fn girth(mut self, girth: impl Into<Length>) -> Self {
98        self.girth = girth.into();
99        self
100    }
101
102    /// Turns the [`ProgressBar`] into a vertical [`ProgressBar`].
103    ///
104    /// By default, a [`ProgressBar`] is horizontal.
105    pub fn vertical(mut self) -> Self {
106        self.is_vertical = true;
107        self
108    }
109
110    /// Sets the style of the [`ProgressBar`].
111    #[must_use]
112    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
113    where
114        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
115    {
116        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
117        self
118    }
119
120    /// Sets the style class of the [`ProgressBar`].
121    #[cfg(feature = "advanced")]
122    #[must_use]
123    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
124        self.class = class.into();
125        self
126    }
127
128    fn width(&self) -> Length {
129        if self.is_vertical {
130            self.girth
131        } else {
132            self.length
133        }
134    }
135
136    fn height(&self) -> Length {
137        if self.is_vertical {
138            self.length
139        } else {
140            self.girth
141        }
142    }
143}
144
145impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
146    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 draw(
168        &self,
169        _state: &Tree,
170        renderer: &mut Renderer,
171        theme: &Theme,
172        _style: &renderer::Style,
173        layout: Layout<'_>,
174        _cursor: mouse::Cursor,
175        _viewport: &Rectangle,
176    ) {
177        let bounds = layout.bounds();
178        let (range_start, range_end) = self.range.clone().into_inner();
179
180        let length = if self.is_vertical {
181            bounds.height
182        } else {
183            bounds.width
184        };
185
186        let active_progress_length = if range_start >= range_end {
187            0.0
188        } else {
189            length * (self.value - range_start) / (range_end - range_start)
190        };
191
192        let style = theme.style(&self.class);
193
194        renderer.fill_quad(
195            renderer::Quad {
196                bounds: Rectangle { ..bounds },
197                border: style.border,
198                ..renderer::Quad::default()
199            },
200            style.background,
201        );
202
203        if active_progress_length > 0.0 {
204            let bounds = if self.is_vertical {
205                Rectangle {
206                    y: bounds.y + bounds.height - active_progress_length,
207                    height: active_progress_length,
208                    ..bounds
209                }
210            } else {
211                Rectangle {
212                    width: active_progress_length,
213                    ..bounds
214                }
215            };
216
217            renderer.fill_quad(
218                renderer::Quad {
219                    bounds,
220                    border: Border {
221                        color: Color::TRANSPARENT,
222                        ..style.border
223                    },
224                    ..renderer::Quad::default()
225                },
226                style.bar,
227            );
228        }
229    }
230}
231
232impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
233    for Element<'a, Message, Theme, Renderer>
234where
235    Message: 'a,
236    Theme: 'a + Catalog,
237    Renderer: 'a + core::Renderer,
238{
239    fn from(
240        progress_bar: ProgressBar<'a, Theme>,
241    ) -> Element<'a, Message, Theme, Renderer> {
242        Element::new(progress_bar)
243    }
244}
245
246/// The appearance of a progress bar.
247#[derive(Debug, Clone, Copy, PartialEq)]
248pub struct Style {
249    /// The [`Background`] of the progress bar.
250    pub background: Background,
251    /// The [`Background`] of the bar of the progress bar.
252    pub bar: Background,
253    /// The [`Border`] of the progress bar.
254    pub border: Border,
255}
256
257/// The theme catalog of a [`ProgressBar`].
258pub trait Catalog: Sized {
259    /// The item class of the [`Catalog`].
260    type Class<'a>;
261
262    /// The default class produced by the [`Catalog`].
263    fn default<'a>() -> Self::Class<'a>;
264
265    /// The [`Style`] of a class with the given status.
266    fn style(&self, class: &Self::Class<'_>) -> Style;
267}
268
269/// A styling function for a [`ProgressBar`].
270///
271/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
272pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
273
274impl Catalog for Theme {
275    type Class<'a> = StyleFn<'a, Self>;
276
277    fn default<'a>() -> Self::Class<'a> {
278        Box::new(primary)
279    }
280
281    fn style(&self, class: &Self::Class<'_>) -> Style {
282        class(self)
283    }
284}
285
286/// The primary style of a [`ProgressBar`].
287pub fn primary(theme: &Theme) -> Style {
288    let palette = theme.extended_palette();
289
290    styled(palette.background.strong.color, palette.primary.base.color)
291}
292
293/// The secondary style of a [`ProgressBar`].
294pub fn secondary(theme: &Theme) -> Style {
295    let palette = theme.extended_palette();
296
297    styled(
298        palette.background.strong.color,
299        palette.secondary.base.color,
300    )
301}
302
303/// The success style of a [`ProgressBar`].
304pub fn success(theme: &Theme) -> Style {
305    let palette = theme.extended_palette();
306
307    styled(palette.background.strong.color, palette.success.base.color)
308}
309
310/// The warning style of a [`ProgressBar`].
311pub fn warning(theme: &Theme) -> Style {
312    let palette = theme.extended_palette();
313
314    styled(palette.background.strong.color, palette.warning.base.color)
315}
316
317/// The danger style of a [`ProgressBar`].
318pub fn danger(theme: &Theme) -> Style {
319    let palette = theme.extended_palette();
320
321    styled(palette.background.strong.color, palette.danger.base.color)
322}
323
324fn styled(
325    background: impl Into<Background>,
326    bar: impl Into<Background>,
327) -> Style {
328    Style {
329        background: background.into(),
330        bar: bar.into(),
331        border: border::rounded(2),
332    }
333}