cosmic/widget/progress_bar/
linear.rs1use super::animation::{Animation, Progress};
3use super::style::{self, Catalog};
4use iced::advanced::widget::tree::{self, Tree};
5use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
6use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
7
8use std::time::Duration;
9
10const MIN_LENGTH: f32 = 0.15;
11const WRAP_LENGTH: f32 = 0.618; #[must_use]
14pub struct Linear<Theme>
15where
16 Theme: Catalog,
17{
18 width: Length,
19 girth: Length,
20 class: Theme::Class,
21 cycle_duration: Duration,
22 period: Duration,
23 progress: Option<f32>,
24 markers: Vec<f32>,
25 segment_spacing: f32,
26}
27
28impl<Theme> Linear<Theme>
29where
30 Theme: Catalog<Class = style::Class>,
31{
32 pub fn new() -> Self {
34 Linear {
35 width: Length::Fixed(100.0),
36 girth: Length::Fixed(4.0),
37 class: Theme::Class::default(),
38 cycle_duration: Duration::from_millis(1500),
39 period: Duration::from_secs(2),
40 progress: None,
41 markers: Vec::new(),
42 segment_spacing: 1.0,
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 class(mut self, class: Theme::Class) -> Self {
60 self.class = class;
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 pub fn markers(mut self, markers: impl Into<Vec<f32>>) -> Self {
86 let mut markers = markers.into();
87 for marker in &mut markers {
88 *marker = marker.clamp(0.0, 1.0);
89 }
90 markers.sort_by(f32::total_cmp);
91 markers.dedup();
92
93 self.markers = markers;
94 self
95 }
96
97 pub fn segment_spacing(mut self, spacing: impl Into<Pixels>) -> Self {
99 self.segment_spacing = spacing.into().0.max(1.0);
100 self
101 }
102
103 pub fn track_color(mut self, color: impl Into<iced::Color>) -> Self {
105 self.class = self.class.track_color(color);
106 self
107 }
108
109 pub fn bar_color(mut self, color: impl Into<iced::Color>) -> Self {
111 self.class = self.class.bar_color(color);
112 self
113 }
114
115 pub fn border_color(mut self, color: impl Into<iced::Color>) -> Self {
117 self.class = self.class.border_color(color);
118 self
119 }
120
121 pub fn border_radius(mut self, radius: f32) -> Self {
123 self.class = self.class.border_radius(radius);
124 self
125 }
126}
127
128impl<Theme> Default for Linear<Theme>
129where
130 Theme: Catalog<Class = style::Class>,
131{
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137#[derive(Default)]
138struct State {
139 animation: Animation,
140 progress: Progress,
141}
142
143impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
144where
145 Message: Clone,
146 Theme: Catalog,
147 Renderer: advanced::Renderer,
148{
149 fn tag(&self) -> tree::Tag {
150 tree::Tag::of::<State>()
151 }
152
153 fn state(&self) -> tree::State {
154 tree::State::new(State::default())
155 }
156
157 fn size(&self) -> Size<Length> {
158 Size {
159 width: self.width,
160 height: self.girth,
161 }
162 }
163
164 fn layout(
165 &mut self,
166 _tree: &mut Tree,
167 _renderer: &Renderer,
168 limits: &layout::Limits,
169 ) -> layout::Node {
170 layout::atomic(limits, self.width, self.girth)
171 }
172
173 fn update(
174 &mut self,
175 tree: &mut Tree,
176 event: &Event,
177 _layout: Layout<'_>,
178 _cursor: mouse::Cursor,
179 _renderer: &Renderer,
180 _clipboard: &mut dyn Clipboard,
181 shell: &mut Shell<'_, Message>,
182 _viewport: &Rectangle,
183 ) {
184 let state = tree.state.downcast_mut::<State>();
185 if let Event::Window(window::Event::RedrawRequested(now)) = event {
186 if let Some(target) = self.progress {
187 if state.progress.update(target, *now) {
188 shell.request_redraw();
189 }
190 } else {
191 state.animation = state.animation.timed_transition(
192 self.cycle_duration,
193 self.period,
194 WRAP_LENGTH,
195 *now,
196 );
197 shell.request_redraw();
198 }
199 }
200 }
201
202 fn draw(
203 &self,
204 tree: &Tree,
205 renderer: &mut Renderer,
206 theme: &Theme,
207 _style: &renderer::Style,
208 layout: Layout<'_>,
209 _cursor: mouse::Cursor,
210 _viewport: &Rectangle,
211 ) {
212 let bounds = layout.bounds();
213 let custom_style = theme.style(&self.class, self.progress.is_some(), false);
214 let state = tree.state.downcast_ref::<State>();
215
216 let border_width = if custom_style.border_color.is_some() {
217 1.0
218 } else {
219 0.0
220 };
221 let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color);
222 let radius = custom_style.border_radius;
223 let track_color = custom_style.track_color;
224 let bar_color = custom_style.bar_color;
225
226 let draw_quad = |renderer: &mut Renderer, rect: Rectangle, border: Border, color: Color| {
227 renderer.fill_quad(
228 renderer::Quad {
229 bounds: rect,
230 border,
231 snap: true,
232 ..renderer::Quad::default()
233 },
234 color,
235 );
236 };
237 let to_rect = |x: f32, width: f32| Rectangle {
238 x: bounds.x + x * bounds.width,
239 y: bounds.y,
240 width: width * bounds.width,
241 height: bounds.height,
242 };
243
244 if self.progress.is_some() {
246 let current_p = state.progress.current;
247 let len = self.markers.len();
248 let spacing = self.segment_spacing;
249 let gap = spacing / bounds.width;
250 let drawable = 1.0 - gap * len as f32;
251 let radius_inner = radius.min(spacing);
252
253 for i in 0..=len {
254 let (seg_lo, r_left) = if i == 0 {
255 (0.0, radius)
256 } else {
257 (self.markers[i - 1], radius_inner)
258 };
259 let (seg_hi, r_right) = if i == len {
260 (1.0, radius)
261 } else {
262 (self.markers[i], radius_inner)
263 };
264 let x_start = seg_lo * drawable + i as f32 * gap;
265 let x_width = (seg_hi - seg_lo) * drawable;
266 let rect = to_rect(x_start, x_width);
267 let segment_radius = [r_left, r_right, r_right, r_left].into();
268
269 let border = Border {
270 width: border_width,
271 color: border_color,
272 radius: segment_radius,
273 };
274
275 if current_p < seg_lo {
277 draw_quad(renderer, rect, border, track_color);
278 }
279 else if current_p > seg_hi {
281 draw_quad(renderer, rect, border, bar_color);
282 }
283 else {
285 let fill = (current_p - seg_lo) / (seg_hi - seg_lo);
286 draw_quad(renderer, rect, border, track_color);
287 renderer.with_layer(to_rect(x_start, x_width * fill), |renderer| {
288 draw_quad(renderer, rect, border, bar_color);
289 });
290 }
291 }
292 }
293 else {
295 draw_quad(
297 renderer,
298 bounds,
299 Border {
300 width: border_width,
301 color: border_color,
302 radius: radius.into(),
303 },
304 track_color,
305 );
306
307 let (bar_start, bar_end) =
309 state
310 .animation
311 .bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH);
312 let length = bar_end - bar_start;
313 let start = bar_start % 1.0;
314 let right_width = (1.0 - start).min(length);
315 let left_width = length - right_width;
316 let border = Border {
317 radius: radius.into(),
318 ..Border::default()
319 };
320
321 renderer.with_layer(to_rect(start, right_width), |renderer| {
322 draw_quad(renderer, bounds, border, bar_color);
323 });
324 renderer.with_layer(to_rect(0.0, left_width), |renderer| {
325 draw_quad(renderer, bounds, border, bar_color);
326 });
327 }
328 }
329}
330
331impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
332where
333 Message: Clone + 'a,
334 Theme: Catalog + 'a,
335 Renderer: iced::advanced::Renderer + 'a,
336{
337 fn from(linear: Linear<Theme>) -> Self {
338 Self::new(linear)
339 }
340}