use iced::widget::canvas::{self, Cache, Frame, Geometry, Path, Stroke};
use iced::{Color, Element, Length, Point, Rectangle, Renderer, Size, Theme};
#[derive(Debug)]
pub struct AnimatedProgress {
progress: f32,
animation_time: f32,
cache: Cache,
theme: Theme,
color_override: Option<Color>,
}
impl AnimatedProgress {
pub fn new() -> Self {
Self {
progress: 0.0,
animation_time: 0.0,
cache: Cache::new(),
theme: Theme::Dark,
color_override: None,
}
}
pub fn new_with_color(color: Color) -> Self {
Self {
progress: 0.0,
animation_time: 0.0,
cache: Cache::new(),
theme: Theme::Dark,
color_override: Some(color),
}
}
pub fn set_color_override(&mut self, color: Option<Color>) {
self.color_override = color;
self.cache.clear();
}
pub fn set_progress(&mut self, progress: f32) {
let new_progress = progress.clamp(0.0, 1.0);
if (self.progress - new_progress).abs() > 0.001 {
self.progress = new_progress;
self.cache.clear();
}
}
pub fn tick(&mut self, speed_mb_s: f32) {
let speed_multiplier = (speed_mb_s / 20.0).clamp(0.15, 1.2);
self.animation_time += 0.016 * speed_multiplier;
if self.animation_time > 1000.0 {
self.animation_time = 0.0;
}
self.cache.clear();
}
pub fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
self.cache.clear();
}
pub fn color_override(&self) -> Option<Color> {
self.color_override
}
pub fn view<'a, Message: 'a>(&'a self) -> Element<'a, Message, Theme, Renderer> {
iced::widget::canvas(self)
.width(Length::Fill)
.height(Length::Fixed(20.0))
.into()
}
}
impl Default for AnimatedProgress {
fn default() -> Self {
Self::new()
}
}
impl<Message> canvas::Program<Message, Theme, Renderer> for AnimatedProgress {
type State = ();
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: iced::mouse::Cursor,
) -> Vec<Geometry<Renderer>> {
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
draw_animated_progress(
frame,
bounds.size(),
self.progress,
self.animation_time,
&self.theme,
self.color_override,
);
});
vec![geometry]
}
}
fn draw_animated_progress(
frame: &mut Frame,
size: Size,
progress: f32,
time: f32,
theme: &Theme,
color_override: Option<Color>,
) {
let width = size.width;
let height = size.height;
let background = Path::rectangle(Point::ORIGIN, size);
frame.fill(&background, Color::from_rgb(0.15, 0.15, 0.15));
if progress > 0.0 {
let progress_width = width * progress;
draw_gradient_progress(frame, progress_width, height, time, theme, color_override);
draw_shimmer_effect(frame, progress_width, height, time);
if progress < 1.0 {
draw_edge_pulse(frame, progress_width, height, time);
}
}
let border = Path::rectangle(Point::ORIGIN, size);
frame.stroke(
&border,
Stroke::default()
.with_color(Color::from_rgb(0.3, 0.3, 0.3))
.with_width(1.0),
);
}
fn draw_gradient_progress(
frame: &mut Frame,
width: f32,
height: f32,
time: f32,
theme: &Theme,
color_override: Option<Color>,
) {
let primary = color_override.unwrap_or_else(|| theme.palette().primary);
let color_start = Color::from_rgb(primary.r, primary.g, primary.b);
let color_end = Color::from_rgb(
(primary.r * 0.7 + 0.3).min(1.0),
(primary.g * 0.9 + 0.1).min(1.0),
(primary.b * 0.95 + 0.05).min(1.0),
);
let segments = 20;
let segment_width = width / segments as f32;
for i in 0..segments {
let x = i as f32 * segment_width;
let progress_ratio = i as f32 / segments as f32;
let hue_shift = (time * 0.12 + progress_ratio * 2.0).sin() * 0.06;
let base_color = interpolate_color(color_start, color_end, progress_ratio);
let animated_color = Color {
r: (base_color.r + hue_shift).clamp(0.0, 1.0),
g: (base_color.g + hue_shift * 0.5).clamp(0.0, 1.0),
b: (base_color.b - hue_shift * 0.3).clamp(0.0, 1.0),
a: 1.0,
};
let segment = Path::rectangle(Point::new(x, 0.0), Size::new(segment_width + 1.0, height));
frame.fill(&segment, animated_color);
}
}
fn draw_shimmer_effect(frame: &mut Frame, width: f32, height: f32, time: f32) {
let shine_position = (time * 0.18).fract();
let shine_x = width * shine_position;
let shine_width = width * 0.15;
let shimmer_segments = 10;
for i in 0..shimmer_segments {
let offset = i as f32 / shimmer_segments as f32;
let x = shine_x + (offset - 0.5) * shine_width;
if x >= 0.0 && x <= width {
let distance_from_center = ((offset - 0.5) * 2.0).abs();
let alpha = (1.0 - distance_from_center.powf(2.0)) * 0.3;
let shimmer = Path::rectangle(
Point::new(x, 0.0),
Size::new(shine_width / shimmer_segments as f32, height),
);
frame.fill(
&shimmer,
Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: alpha,
},
);
}
}
}
fn draw_edge_pulse(frame: &mut Frame, width: f32, height: f32, time: f32) {
let pulse = (time * 1.8).sin() * 0.5 + 0.5;
let pulse_width = 4.0 + pulse * 2.0;
let edge = Path::rectangle(
Point::new(width - pulse_width / 2.0, 0.0),
Size::new(pulse_width, height),
);
frame.fill(
&edge,
Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 0.3 + pulse * 0.3,
},
);
}
fn interpolate_color(start: Color, end: Color, t: f32) -> Color {
Color {
r: start.r + (end.r - start.r) * t,
g: start.g + (end.g - start.g) * t,
b: start.b + (end.b - start.b) * t,
a: start.a + (end.a - start.a) * t,
}
}