cosmic/widget/progress_bar/
linear.rs

1//! Show a linear progress indicator.
2use super::animation::{Animation, Progress};
3use super::style::StyleSheet;
4use iced::advanced::layout;
5use iced::advanced::renderer;
6use iced::advanced::widget::tree::{self, Tree};
7use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
8use iced::mouse;
9use iced::window;
10use iced::{Background, Element, Event, Length, Rectangle, Size};
11
12use std::time::Duration;
13
14const MIN_LENGTH: f32 = 0.15;
15const WRAP_LENGTH: f32 = 0.618; // avoids animation repetition
16
17#[must_use]
18pub struct Linear<Theme>
19where
20    Theme: StyleSheet,
21{
22    width: Length,
23    girth: Length,
24    style: Theme::Style,
25    cycle_duration: Duration,
26    period: Duration,
27    progress: Option<f32>,
28}
29
30impl<Theme> Linear<Theme>
31where
32    Theme: StyleSheet,
33{
34    /// Creates a new [`Linear`] with the given content.
35    pub fn new() -> Self {
36        Linear {
37            width: Length::Fixed(100.0),
38            girth: Length::Fixed(4.0),
39            style: Theme::Style::default(),
40            cycle_duration: Duration::from_millis(1500),
41            period: Duration::from_secs(2),
42            progress: None,
43        }
44    }
45
46    /// Sets the width of the [`Linear`].
47    pub fn width(mut self, width: impl Into<Length>) -> Self {
48        self.width = width.into();
49        self
50    }
51
52    /// Sets the girth of the [`Linear`].
53    pub fn girth(mut self, girth: impl Into<Length>) -> Self {
54        self.girth = girth.into();
55        self
56    }
57
58    /// Sets the style variant of this [`Linear`].
59    pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
60        self.style = style.into();
61        self
62    }
63
64    /// Sets the cycle duration of this [`Linear`].
65    pub fn cycle_duration(mut self, duration: Duration) -> Self {
66        self.cycle_duration = duration / 2;
67        self
68    }
69
70    /// Sets the base period of this [`Linear`]. This is the duration that a full traversal
71    /// would take if the cycle duration were set to 0.0 (no expanding or contracting)
72    pub fn period(mut self, duration: Duration) -> Self {
73        self.period = duration;
74        self
75    }
76
77    /// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`.
78    pub fn progress(mut self, progress: f32) -> Self {
79        self.progress = Some(progress.clamp(0.0, 1.0));
80        self
81    }
82}
83
84impl<Theme> Default for Linear<Theme>
85where
86    Theme: StyleSheet,
87{
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93#[derive(Default)]
94struct State {
95    animation: Animation,
96    progress: Progress,
97}
98
99impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
100where
101    Message: Clone,
102    Theme: StyleSheet,
103    Renderer: advanced::Renderer,
104{
105    fn tag(&self) -> tree::Tag {
106        tree::Tag::of::<State>()
107    }
108
109    fn state(&self) -> tree::State {
110        tree::State::new(State::default())
111    }
112
113    fn size(&self) -> Size<Length> {
114        Size {
115            width: self.width,
116            height: self.girth,
117        }
118    }
119
120    fn layout(
121        &mut self,
122        _tree: &mut Tree,
123        _renderer: &Renderer,
124        limits: &layout::Limits,
125    ) -> layout::Node {
126        layout::atomic(limits, self.width, self.girth)
127    }
128
129    fn update(
130        &mut self,
131        tree: &mut Tree,
132        event: &Event,
133        _layout: Layout<'_>,
134        _cursor: mouse::Cursor,
135        _renderer: &Renderer,
136        _clipboard: &mut dyn Clipboard,
137        shell: &mut Shell<'_, Message>,
138        _viewport: &Rectangle,
139    ) {
140        let state = tree.state.downcast_mut::<State>();
141        if let Event::Window(window::Event::RedrawRequested(now)) = event {
142            if let Some(target) = self.progress {
143                if state.progress.update(target, *now) {
144                    shell.request_redraw();
145                }
146            } else {
147                state.animation = state.animation.timed_transition(
148                    self.cycle_duration,
149                    self.period,
150                    WRAP_LENGTH,
151                    *now,
152                );
153                shell.request_redraw();
154            }
155        }
156    }
157
158    fn draw(
159        &self,
160        tree: &Tree,
161        renderer: &mut Renderer,
162        theme: &Theme,
163        _style: &renderer::Style,
164        layout: Layout<'_>,
165        _cursor: mouse::Cursor,
166        _viewport: &Rectangle,
167    ) {
168        let bounds = layout.bounds();
169        let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
170        let state = tree.state.downcast_ref::<State>();
171
172        renderer.fill_quad(
173            renderer::Quad {
174                bounds,
175                border: iced::Border {
176                    width: if custom_style.border_color.is_some() {
177                        1.0
178                    } else {
179                        0.0
180                    },
181                    color: custom_style.border_color.unwrap_or(custom_style.bar_color),
182                    radius: custom_style.border_radius.into(),
183                },
184                snap: true,
185                ..renderer::Quad::default()
186            },
187            Background::Color(custom_style.track_color),
188        );
189
190        let mut draw_segment = |x: f32, width: f32| {
191            if width > 0.001 {
192                renderer.fill_quad(
193                    renderer::Quad {
194                        bounds: Rectangle {
195                            x: bounds.x + x * bounds.width,
196                            y: bounds.y,
197                            width: width * bounds.width,
198                            height: bounds.height,
199                        },
200                        border: iced::Border {
201                            width: 0.0,
202                            color: iced::Color::TRANSPARENT,
203                            radius: custom_style.border_radius.into(),
204                        },
205                        snap: true,
206                        ..renderer::Quad::default()
207                    },
208                    Background::Color(custom_style.bar_color),
209                );
210            }
211        };
212
213        if self.progress.is_some() {
214            draw_segment(0.0, state.progress.current);
215        } else {
216            let (bar_start, bar_end) =
217                state
218                    .animation
219                    .bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH);
220            let length = bar_end - bar_start;
221            let start = bar_start % 1.0;
222            let right_width = (1.0 - start).min(length);
223            let left_width = length - right_width;
224
225            draw_segment(start, right_width);
226            draw_segment(0.0, left_width);
227        }
228    }
229}
230
231impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
232where
233    Message: Clone + 'a,
234    Theme: StyleSheet + 'a,
235    Renderer: iced::advanced::Renderer + 'a,
236{
237    fn from(linear: Linear<Theme>) -> Self {
238        Self::new(linear)
239    }
240}