flashkraft_gui/components/
animated_progress.rs1use iced::widget::canvas::{self, Cache, Frame, Geometry, Path, Stroke};
7use iced::{Color, Element, Length, Point, Rectangle, Renderer, Size, Theme};
8
9#[derive(Debug)]
11pub struct AnimatedProgress {
12 progress: f32,
14 animation_time: f32,
16 cache: Cache,
18 theme: Theme,
20 color_override: Option<Color>,
23}
24
25impl AnimatedProgress {
26 pub fn new() -> Self {
28 Self {
29 progress: 0.0,
30 animation_time: 0.0,
31 cache: Cache::new(),
32 theme: Theme::Dark,
33 color_override: None,
34 }
35 }
36
37 pub fn new_with_color(color: Color) -> Self {
39 Self {
40 progress: 0.0,
41 animation_time: 0.0,
42 cache: Cache::new(),
43 theme: Theme::Dark,
44 color_override: Some(color),
45 }
46 }
47
48 pub fn set_color_override(&mut self, color: Option<Color>) {
50 self.color_override = color;
51 self.cache.clear();
52 }
53
54 pub fn set_progress(&mut self, progress: f32) {
56 let new_progress = progress.clamp(0.0, 1.0);
57 if (self.progress - new_progress).abs() > 0.001 {
58 self.progress = new_progress;
59 self.cache.clear();
60 }
61 }
62
63 pub fn tick(&mut self, speed_mb_s: f32) {
68 let speed_multiplier = (speed_mb_s / 20.0).clamp(0.15, 1.2);
73 self.animation_time += 0.016 * speed_multiplier;
74 if self.animation_time > 1000.0 {
75 self.animation_time = 0.0;
76 }
77 self.cache.clear();
78 }
79
80 pub fn set_theme(&mut self, theme: Theme) {
82 self.theme = theme;
83 self.cache.clear();
84 }
85
86 pub fn color_override(&self) -> Option<Color> {
88 self.color_override
89 }
90
91 pub fn view<'a, Message: 'a>(&'a self) -> Element<'a, Message, Theme, Renderer> {
93 iced::widget::canvas(self)
94 .width(Length::Fill)
95 .height(Length::Fixed(20.0))
96 .into()
97 }
98}
99
100impl Default for AnimatedProgress {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106impl<Message> canvas::Program<Message, Theme, Renderer> for AnimatedProgress {
107 type State = ();
108
109 fn draw(
110 &self,
111 _state: &Self::State,
112 renderer: &Renderer,
113 _theme: &Theme,
114 bounds: Rectangle,
115 _cursor: iced::mouse::Cursor,
116 ) -> Vec<Geometry<Renderer>> {
117 let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
118 draw_animated_progress(
119 frame,
120 bounds.size(),
121 self.progress,
122 self.animation_time,
123 &self.theme,
124 self.color_override,
125 );
126 });
127
128 vec![geometry]
129 }
130}
131
132fn draw_animated_progress(
134 frame: &mut Frame,
135 size: Size,
136 progress: f32,
137 time: f32,
138 theme: &Theme,
139 color_override: Option<Color>,
140) {
141 let width = size.width;
142 let height = size.height;
143
144 let background = Path::rectangle(Point::ORIGIN, size);
146 frame.fill(&background, Color::from_rgb(0.15, 0.15, 0.15));
147
148 if progress > 0.0 {
149 let progress_width = width * progress;
150
151 draw_gradient_progress(frame, progress_width, height, time, theme, color_override);
153
154 draw_shimmer_effect(frame, progress_width, height, time);
156
157 if progress < 1.0 {
159 draw_edge_pulse(frame, progress_width, height, time);
160 }
161 }
162
163 let border = Path::rectangle(Point::ORIGIN, size);
165 frame.stroke(
166 &border,
167 Stroke::default()
168 .with_color(Color::from_rgb(0.3, 0.3, 0.3))
169 .with_width(1.0),
170 );
171}
172
173fn draw_gradient_progress(
175 frame: &mut Frame,
176 width: f32,
177 height: f32,
178 time: f32,
179 theme: &Theme,
180 color_override: Option<Color>,
181) {
182 let primary = color_override.unwrap_or_else(|| theme.palette().primary);
185
186 let color_start = Color::from_rgb(primary.r, primary.g, primary.b);
188 let color_end = Color::from_rgb(
189 (primary.r * 0.7 + 0.3).min(1.0),
190 (primary.g * 0.9 + 0.1).min(1.0),
191 (primary.b * 0.95 + 0.05).min(1.0),
192 );
193 let segments = 20;
195 let segment_width = width / segments as f32;
196
197 for i in 0..segments {
198 let x = i as f32 * segment_width;
199 let progress_ratio = i as f32 / segments as f32;
200
201 let hue_shift = (time * 0.12 + progress_ratio * 2.0).sin() * 0.06;
203
204 let base_color = interpolate_color(color_start, color_end, progress_ratio);
206
207 let animated_color = Color {
208 r: (base_color.r + hue_shift).clamp(0.0, 1.0),
209 g: (base_color.g + hue_shift * 0.5).clamp(0.0, 1.0),
210 b: (base_color.b - hue_shift * 0.3).clamp(0.0, 1.0),
211 a: 1.0,
212 };
213
214 let segment = Path::rectangle(Point::new(x, 0.0), Size::new(segment_width + 1.0, height));
215 frame.fill(&segment, animated_color);
216 }
217}
218
219fn draw_shimmer_effect(frame: &mut Frame, width: f32, height: f32, time: f32) {
221 let shine_position = (time * 0.18).fract();
223 let shine_x = width * shine_position;
224 let shine_width = width * 0.15;
225
226 let shimmer_segments = 10;
228 for i in 0..shimmer_segments {
229 let offset = i as f32 / shimmer_segments as f32;
230 let x = shine_x + (offset - 0.5) * shine_width;
231
232 if x >= 0.0 && x <= width {
233 let distance_from_center = ((offset - 0.5) * 2.0).abs();
235 let alpha = (1.0 - distance_from_center.powf(2.0)) * 0.3;
236
237 let shimmer = Path::rectangle(
238 Point::new(x, 0.0),
239 Size::new(shine_width / shimmer_segments as f32, height),
240 );
241
242 frame.fill(
243 &shimmer,
244 Color {
245 r: 1.0,
246 g: 1.0,
247 b: 1.0,
248 a: alpha,
249 },
250 );
251 }
252 }
253}
254
255fn draw_edge_pulse(frame: &mut Frame, width: f32, height: f32, time: f32) {
257 let pulse = (time * 1.8).sin() * 0.5 + 0.5;
258 let pulse_width = 4.0 + pulse * 2.0;
259
260 let edge = Path::rectangle(
262 Point::new(width - pulse_width / 2.0, 0.0),
263 Size::new(pulse_width, height),
264 );
265
266 frame.fill(
267 &edge,
268 Color {
269 r: 1.0,
270 g: 1.0,
271 b: 1.0,
272 a: 0.3 + pulse * 0.3,
273 },
274 );
275}
276
277fn interpolate_color(start: Color, end: Color, t: f32) -> Color {
279 Color {
280 r: start.r + (end.r - start.r) * t,
281 g: start.g + (end.g - start.g) * t,
282 b: start.b + (end.b - start.b) * t,
283 a: start.a + (end.a - start.a) * t,
284 }
285}