Skip to main content

flashkraft_gui/components/
animated_progress.rs

1//! Animated Progress Bar with Canvas Effects
2//!
3//! This module provides a custom animated progress bar using Iced's canvas
4//! for smooth, animated gradient and shimmer effects during flash operations.
5
6use iced::widget::canvas::{self, Cache, Frame, Geometry, Path, Stroke};
7use iced::{Color, Element, Length, Point, Rectangle, Renderer, Size, Theme};
8
9/// Animated progress bar with shimmer effect
10#[derive(Debug)]
11pub struct AnimatedProgress {
12    /// Progress value (0.0 to 1.0)
13    progress: f32,
14    /// Animation time for shimmer effect
15    animation_time: f32,
16    /// Cache for the progress bar rendering
17    cache: Cache,
18    /// Theme for color palette
19    theme: Theme,
20}
21
22impl AnimatedProgress {
23    /// Create a new animated progress bar
24    pub fn new() -> Self {
25        Self {
26            progress: 0.0,
27            animation_time: 0.0,
28            cache: Cache::new(),
29            theme: Theme::Dark,
30        }
31    }
32
33    /// Update the progress value
34    pub fn set_progress(&mut self, progress: f32) {
35        let new_progress = progress.clamp(0.0, 1.0);
36        if (self.progress - new_progress).abs() > 0.001 {
37            self.progress = new_progress;
38            self.cache.clear();
39        }
40    }
41
42    /// Update animation time based on transfer speed
43    ///
44    /// # Arguments
45    /// * `speed_mb_s` - Current transfer speed in MB/s to scale animation speed
46    pub fn tick(&mut self, speed_mb_s: f32) {
47        // Scale animation speed based on transfer rate
48        // - At 1 MB/s: 0.3x speed (slow)
49        // - At 20 MB/s: 1.0x speed (baseline)
50        // - At 100+ MB/s: 3.0x speed (fast, capped)
51        let speed_multiplier = (speed_mb_s / 20.0).clamp(0.3, 3.0);
52        self.animation_time += 0.05 * speed_multiplier;
53        if self.animation_time > 1000.0 {
54            self.animation_time = 0.0;
55        }
56        self.cache.clear();
57    }
58
59    /// Set the theme for color rendering
60    pub fn set_theme(&mut self, theme: Theme) {
61        self.theme = theme;
62        self.cache.clear();
63    }
64
65    /// Create the widget view
66    pub fn view<'a, Message: 'a>(&'a self) -> Element<'a, Message, Theme, Renderer> {
67        iced::widget::canvas(self)
68            .width(Length::Fill)
69            .height(Length::Fixed(20.0))
70            .into()
71    }
72}
73
74impl Default for AnimatedProgress {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl<Message> canvas::Program<Message, Theme, Renderer> for AnimatedProgress {
81    type State = ();
82
83    fn draw(
84        &self,
85        _state: &Self::State,
86        renderer: &Renderer,
87        _theme: &Theme,
88        bounds: Rectangle,
89        _cursor: iced::mouse::Cursor,
90    ) -> Vec<Geometry<Renderer>> {
91        let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
92            draw_animated_progress(
93                frame,
94                bounds.size(),
95                self.progress,
96                self.animation_time,
97                &self.theme,
98            );
99        });
100
101        vec![geometry]
102    }
103}
104
105/// Draw the animated progress bar with effects
106fn draw_animated_progress(frame: &mut Frame, size: Size, progress: f32, time: f32, theme: &Theme) {
107    let width = size.width;
108    let height = size.height;
109
110    // Background (dark gray)
111    let background = Path::rectangle(Point::ORIGIN, size);
112    frame.fill(&background, Color::from_rgb(0.15, 0.15, 0.15));
113
114    if progress > 0.0 {
115        let progress_width = width * progress;
116
117        // Main progress bar with gradient effect
118        draw_gradient_progress(frame, progress_width, height, time, theme);
119
120        // Add shimmer/shine effect
121        draw_shimmer_effect(frame, progress_width, height, time);
122
123        // Add subtle pulse effect at the edge
124        if progress < 1.0 {
125            draw_edge_pulse(frame, progress_width, height, time);
126        }
127    }
128
129    // Border
130    let border = Path::rectangle(Point::ORIGIN, size);
131    frame.stroke(
132        &border,
133        Stroke::default()
134            .with_color(Color::from_rgb(0.3, 0.3, 0.3))
135            .with_width(1.0),
136    );
137}
138
139/// Draw gradient progress fill
140fn draw_gradient_progress(frame: &mut Frame, width: f32, height: f32, time: f32, theme: &Theme) {
141    // Get theme colors
142    let palette = theme.palette();
143    let primary = palette.primary;
144
145    // Create color variants from theme
146    let color_start = Color::from_rgb(primary.r, primary.g, primary.b);
147    let color_end = Color::from_rgb(
148        (primary.r * 0.7 + 0.3).min(1.0),
149        (primary.g * 0.9 + 0.1).min(1.0),
150        (primary.b * 0.95 + 0.05).min(1.0),
151    );
152    // Create a multi-segment gradient effect
153    let segments = 20;
154    let segment_width = width / segments as f32;
155
156    for i in 0..segments {
157        let x = i as f32 * segment_width;
158        let progress_ratio = i as f32 / segments as f32;
159
160        // Animated color shifting (slower)
161        let hue_shift = (time * 0.2 + progress_ratio * 2.0).sin() * 0.08;
162
163        // Theme-based gradient with animation
164        let base_color = interpolate_color(color_start, color_end, progress_ratio);
165
166        let animated_color = Color {
167            r: (base_color.r + hue_shift).clamp(0.0, 1.0),
168            g: (base_color.g + hue_shift * 0.5).clamp(0.0, 1.0),
169            b: (base_color.b - hue_shift * 0.3).clamp(0.0, 1.0),
170            a: 1.0,
171        };
172
173        let segment = Path::rectangle(Point::new(x, 0.0), Size::new(segment_width + 1.0, height));
174        frame.fill(&segment, animated_color);
175    }
176}
177
178/// Draw shimmer/shine effect
179fn draw_shimmer_effect(frame: &mut Frame, width: f32, height: f32, time: f32) {
180    // Moving shine effect (slower sweep)
181    let shine_position = (time * 0.3).fract();
182    let shine_x = width * shine_position;
183    let shine_width = width * 0.15;
184
185    // Create shimmer with gradient
186    let shimmer_segments = 10;
187    for i in 0..shimmer_segments {
188        let offset = i as f32 / shimmer_segments as f32;
189        let x = shine_x + (offset - 0.5) * shine_width;
190
191        if x >= 0.0 && x <= width {
192            // Gaussian-like falloff
193            let distance_from_center = ((offset - 0.5) * 2.0).abs();
194            let alpha = (1.0 - distance_from_center.powf(2.0)) * 0.3;
195
196            let shimmer = Path::rectangle(
197                Point::new(x, 0.0),
198                Size::new(shine_width / shimmer_segments as f32, height),
199            );
200
201            frame.fill(
202                &shimmer,
203                Color {
204                    r: 1.0,
205                    g: 1.0,
206                    b: 1.0,
207                    a: alpha,
208                },
209            );
210        }
211    }
212}
213
214/// Draw pulsing effect at the leading edge
215fn draw_edge_pulse(frame: &mut Frame, width: f32, height: f32, time: f32) {
216    let pulse = (time * 3.0).sin() * 0.5 + 0.5;
217    let pulse_width = 4.0 + pulse * 2.0;
218
219    // Glowing edge
220    let edge = Path::rectangle(
221        Point::new(width - pulse_width / 2.0, 0.0),
222        Size::new(pulse_width, height),
223    );
224
225    frame.fill(
226        &edge,
227        Color {
228            r: 1.0,
229            g: 1.0,
230            b: 1.0,
231            a: 0.3 + pulse * 0.3,
232        },
233    );
234}
235
236/// Interpolate between two colors
237fn interpolate_color(start: Color, end: Color, t: f32) -> Color {
238    Color {
239        r: start.r + (end.r - start.r) * t,
240        g: start.g + (end.g - start.g) * t,
241        b: start.b + (end.b - start.b) * t,
242        a: start.a + (end.a - start.a) * t,
243    }
244}