cosmic/widget/progress_bar/
linear.rs1use 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; #[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 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 pub fn width(mut self, width: impl Into<Length>) -> Self {
48 self.width = width.into();
49 self
50 }
51
52 pub fn girth(mut self, girth: impl Into<Length>) -> Self {
54 self.girth = girth.into();
55 self
56 }
57
58 pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
60 self.style = style.into();
61 self
62 }
63
64 pub fn cycle_duration(mut self, duration: Duration) -> Self {
66 self.cycle_duration = duration / 2;
67 self
68 }
69
70 pub fn period(mut self, duration: Duration) -> Self {
73 self.period = duration;
74 self
75 }
76
77 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}