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::{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
200 let mut draw_quad = |x: f32, width: f32, color: iced::Color, border: iced::Border| {
201 if width * bounds.width > 0.1 {
203 renderer.fill_quad(
204 renderer::Quad {
205 bounds: Rectangle {
206 x: bounds.x + x * bounds.width,
207 y: bounds.y,
208 width: width * bounds.width,
209 height: bounds.height,
210 },
211 border,
212 snap: true,
213 ..renderer::Quad::default()
214 },
215 color,
216 );
217 }
218 };
219
220 if self.progress.is_some() {
221 let current_p = state.progress.current;
222 let len = self.markers.len();
223 let spacing = self.segment_spacing;
224 let radius_inner = radius.min(spacing);
225
226 let gap = if len != 0 {
227 spacing / bounds.width
228 } else {
229 0.0
230 };
231 let drawable = 1.0 - gap * len as f32;
232
233 for i in 0..=len {
234 let (seg_lo, r_left) = if i == 0 {
235 (0.0, radius)
236 } else {
237 (self.markers[i - 1], radius_inner)
238 };
239 let (seg_hi, r_right) = if i == len {
240 (1.0, radius)
241 } else {
242 (self.markers[i], radius_inner)
243 };
244 let x_start = seg_lo * drawable + i as f32 * gap;
245 let x_width = (seg_hi - seg_lo) * drawable;
246 let segment_radius = [r_left, r_right, r_right, r_left].into();
247
248 draw_quad(
250 x_start,
251 x_width,
252 custom_style.track_color,
253 iced::Border {
254 width: border_width,
255 color: border_color,
256 radius: segment_radius,
257 },
258 );
259
260 if current_p > seg_lo {
262 let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).min(1.0);
263 draw_quad(
264 x_start,
265 x_width * fill,
266 custom_style.bar_color,
267 iced::Border {
268 radius: segment_radius,
269 ..iced::Border::default()
270 },
271 );
272 }
273 }
274 } else {
275 draw_quad(
277 0.0,
278 1.0,
279 custom_style.track_color,
280 iced::Border {
281 width: border_width,
282 color: border_color,
283 radius: radius.into(),
284 },
285 );
286
287 let (bar_start, bar_end) =
289 state
290 .animation
291 .bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH);
292 let length = bar_end - bar_start;
293 let start = bar_start % 1.0;
294 let right_width = (1.0 - start).min(length);
295 let left_width = length - right_width;
296 let border = iced::Border {
297 radius: radius.into(),
298 ..iced::Border::default()
299 };
300
301 draw_quad(start, right_width, custom_style.bar_color, border);
302 draw_quad(0.0, left_width, custom_style.bar_color, border);
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}