cosmic/widget/progress_bar/
linear.rs1use super::animation::{Animation, Progress};
3use super::style::StyleSheet;
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: StyleSheet,
17{
18 width: Length,
19 girth: Length,
20 style: Theme::Style,
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: StyleSheet,
31{
32 pub fn new() -> Self {
34 Linear {
35 width: Length::Fixed(100.0),
36 girth: Length::Fixed(4.0),
37 style: Theme::Style::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 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 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
104impl<Theme> Default for Linear<Theme>
105where
106 Theme: StyleSheet,
107{
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113#[derive(Default)]
114struct State {
115 animation: Animation,
116 progress: Progress,
117}
118
119impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
120where
121 Message: Clone,
122 Theme: StyleSheet,
123 Renderer: advanced::Renderer,
124{
125 fn tag(&self) -> tree::Tag {
126 tree::Tag::of::<State>()
127 }
128
129 fn state(&self) -> tree::State {
130 tree::State::new(State::default())
131 }
132
133 fn size(&self) -> Size<Length> {
134 Size {
135 width: self.width,
136 height: self.girth,
137 }
138 }
139
140 fn layout(
141 &mut self,
142 _tree: &mut Tree,
143 _renderer: &Renderer,
144 limits: &layout::Limits,
145 ) -> layout::Node {
146 layout::atomic(limits, self.width, self.girth)
147 }
148
149 fn update(
150 &mut self,
151 tree: &mut Tree,
152 event: &Event,
153 _layout: Layout<'_>,
154 _cursor: mouse::Cursor,
155 _renderer: &Renderer,
156 _clipboard: &mut dyn Clipboard,
157 shell: &mut Shell<'_, Message>,
158 _viewport: &Rectangle,
159 ) {
160 let state = tree.state.downcast_mut::<State>();
161 if let Event::Window(window::Event::RedrawRequested(now)) = event {
162 if let Some(target) = self.progress {
163 if state.progress.update(target, *now) {
164 shell.request_redraw();
165 }
166 } else {
167 state.animation = state.animation.timed_transition(
168 self.cycle_duration,
169 self.period,
170 WRAP_LENGTH,
171 *now,
172 );
173 shell.request_redraw();
174 }
175 }
176 }
177
178 fn draw(
179 &self,
180 tree: &Tree,
181 renderer: &mut Renderer,
182 theme: &Theme,
183 _style: &renderer::Style,
184 layout: Layout<'_>,
185 _cursor: mouse::Cursor,
186 _viewport: &Rectangle,
187 ) {
188 let bounds = layout.bounds();
189 let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
190 let state = tree.state.downcast_ref::<State>();
191
192 let border_width = if custom_style.border_color.is_some() {
193 1.0
194 } else {
195 0.0
196 };
197 let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color);
198 let radius = custom_style.border_radius;
199 let track_color = custom_style.track_color;
200 let bar_color = custom_style.bar_color;
201
202 let draw_quad = |renderer: &mut Renderer, rect: Rectangle, border: Border, color: Color| {
203 renderer.fill_quad(
204 renderer::Quad {
205 bounds: rect,
206 border,
207 snap: true,
208 ..renderer::Quad::default()
209 },
210 color,
211 );
212 };
213 let to_rect = |x: f32, width: f32| Rectangle {
214 x: bounds.x + x * bounds.width,
215 y: bounds.y,
216 width: width * bounds.width,
217 height: bounds.height,
218 };
219
220 if self.progress.is_some() {
222 let current_p = state.progress.current;
223 let len = self.markers.len();
224 let spacing = self.segment_spacing;
225 let gap = spacing / bounds.width;
226 let drawable = 1.0 - gap * len as f32;
227 let radius_inner = radius.min(spacing);
228
229 for i in 0..=len {
230 let (seg_lo, r_left) = if i == 0 {
231 (0.0, radius)
232 } else {
233 (self.markers[i - 1], radius_inner)
234 };
235 let (seg_hi, r_right) = if i == len {
236 (1.0, radius)
237 } else {
238 (self.markers[i], radius_inner)
239 };
240 let x_start = seg_lo * drawable + i as f32 * gap;
241 let x_width = (seg_hi - seg_lo) * drawable;
242 let rect = to_rect(x_start, x_width);
243 let segment_radius = [r_left, r_right, r_right, r_left].into();
244
245 let border = Border {
246 width: border_width,
247 color: border_color,
248 radius: segment_radius,
249 };
250
251 if current_p < seg_lo {
253 draw_quad(renderer, rect, border, track_color);
254 }
255 else if current_p > seg_hi {
257 draw_quad(renderer, rect, border, bar_color);
258 }
259 else {
261 let fill = (current_p - seg_lo) / (seg_hi - seg_lo);
262 draw_quad(renderer, rect, border, track_color);
263 renderer.with_layer(to_rect(x_start, x_width * fill), |renderer| {
264 draw_quad(renderer, rect, border, bar_color);
265 });
266 }
267 }
268 }
269 else {
271 draw_quad(
273 renderer,
274 bounds,
275 Border {
276 width: border_width,
277 color: border_color,
278 radius: radius.into(),
279 },
280 track_color,
281 );
282
283 let (bar_start, bar_end) =
285 state
286 .animation
287 .bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH);
288 let length = bar_end - bar_start;
289 let start = bar_start % 1.0;
290 let right_width = (1.0 - start).min(length);
291 let left_width = length - right_width;
292 let border = Border {
293 radius: radius.into(),
294 ..Border::default()
295 };
296
297 renderer.with_layer(to_rect(start, right_width), |renderer| {
298 draw_quad(renderer, bounds, border, bar_color);
299 });
300 renderer.with_layer(to_rect(0.0, left_width), |renderer| {
301 draw_quad(renderer, bounds, border, bar_color);
302 });
303 }
304 }
305}
306
307impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
308where
309 Message: Clone + 'a,
310 Theme: StyleSheet + 'a,
311 Renderer: iced::advanced::Renderer + 'a,
312{
313 fn from(linear: Linear<Theme>) -> Self {
314 Self::new(linear)
315 }
316}