ui: add countdown progress bar, arrival bloom, fade transitions, and icon animations to notifications
This commit is contained in:
parent
a341565c5a
commit
0e620e2658
2 changed files with 190 additions and 69 deletions
|
|
@ -306,28 +306,64 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
|
|||
}
|
||||
window.style_context().add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_USER);
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let hbox = Box::new(Orientation::Horizontal, 10);
|
||||
hbox.style_context().add_class("toast-card");
|
||||
hbox.set_size_request(320, 56);
|
||||
hbox.set_app_paintable(true);
|
||||
|
||||
let start_time_draw = start_time;
|
||||
hbox.connect_draw(move |widget, cr| {
|
||||
let w = widget.allocated_width() as f64;
|
||||
let h = widget.allocated_height() as f64;
|
||||
let elapsed = start_time_draw.elapsed().as_secs_f64();
|
||||
let total_dur = 2.80_f64;
|
||||
|
||||
// Dynamic fade transition
|
||||
let fade_in = (elapsed / 0.22).min(1.0);
|
||||
let fade_out = ((total_dur - elapsed) / 0.32).clamp(0.0, 1.0);
|
||||
let alpha = fade_in.min(fade_out);
|
||||
|
||||
cr.set_operator(gtk::cairo::Operator::Over);
|
||||
|
||||
// Sleek translucent neutral dark glass background (matching HUD cards)
|
||||
cr.set_source_rgba(10.0 / 255.0, 10.0 / 255.0, 10.0 / 255.0, 0.65);
|
||||
// 1. Entrance accent bloom (first 0.55s)
|
||||
let bloom = if elapsed < 0.55 { (1.0 - elapsed / 0.55).powi(2) } else { 0.0 };
|
||||
if bloom > 0.01 {
|
||||
cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.35 * bloom * alpha);
|
||||
cr.set_line_width(2.0);
|
||||
cr.rectangle(1.0, 1.0, w - 2.0, h - 2.0);
|
||||
let _ = cr.stroke();
|
||||
}
|
||||
|
||||
// 2. Sleek translucent neutral dark glass background (matching HUD cards)
|
||||
cr.set_source_rgba(10.0 / 255.0, 10.0 / 255.0, 10.0 / 255.0, 0.68 * alpha);
|
||||
cr.rectangle(0.0, 0.0, w, h);
|
||||
let _ = cr.fill();
|
||||
|
||||
// Refined subtle accent stroke framing the card
|
||||
cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.55);
|
||||
// 3. Top specular frost highlight
|
||||
cr.set_source_rgba(1.0, 1.0, 1.0, 0.15 * alpha);
|
||||
cr.set_line_width(1.0);
|
||||
cr.move_to(1.0, 0.5);
|
||||
cr.line_to(w - 1.0, 0.5);
|
||||
let _ = cr.stroke();
|
||||
|
||||
// 4. Refined subtle accent stroke framing the card
|
||||
cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.55 * alpha);
|
||||
cr.set_line_width(1.0);
|
||||
cr.rectangle(0.5, 0.5, w - 1.0, h - 1.0);
|
||||
let _ = cr.stroke();
|
||||
|
||||
// 5. Animated countdown timer line on bottom edge
|
||||
let progress = 1.0 - (elapsed / total_dur).clamp(0.0, 1.0);
|
||||
cr.set_source_rgba(1.0, 1.0, 1.0, 0.08 * alpha);
|
||||
cr.rectangle(0.0, h - 2.0, w, 2.0);
|
||||
let _ = cr.fill();
|
||||
|
||||
cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.75 * alpha);
|
||||
cr.rectangle(0.0, h - 2.0, w * progress, 2.0);
|
||||
let _ = cr.fill();
|
||||
|
||||
gtk::glib::Propagation::Proceed
|
||||
});
|
||||
|
||||
|
|
@ -337,18 +373,21 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
|
|||
icon_area.set_margin_start(14);
|
||||
icon_area.set_valign(gtk::Align::Center);
|
||||
let icon_type = icon;
|
||||
let start_time_icon = start_time;
|
||||
icon_area.connect_draw(move |_, cr| {
|
||||
let cx = 16.0;
|
||||
let cy = 16.0;
|
||||
let elapsed = start_time_icon.elapsed().as_secs_f64();
|
||||
match icon_type {
|
||||
ToastIcon::Replay => {
|
||||
let scale = if elapsed < 0.35 { 0.85 + 0.15 * (elapsed / 0.35) } else { 1.0 };
|
||||
cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2);
|
||||
cr.set_line_width(1.8);
|
||||
cr.set_line_cap(gtk::cairo::LineCap::Round);
|
||||
cr.set_line_join(gtk::cairo::LineJoin::Round);
|
||||
let ch_h = 7.0;
|
||||
let ch_w = 4.4;
|
||||
let gap = 4.2;
|
||||
let ch_h = 7.0 * scale;
|
||||
let ch_w = 4.4 * scale;
|
||||
let gap = 4.2 * scale;
|
||||
let start_x = cx - (ch_w + gap) * 0.5;
|
||||
for i in 0..2 {
|
||||
let tip_x = start_x + (i as f64) * gap;
|
||||
|
|
@ -360,6 +399,7 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
|
|||
}
|
||||
}
|
||||
ToastIcon::Record => {
|
||||
let pulse = ((elapsed * 5.0).sin() * 0.5 + 0.5) * 0.35 + 0.65;
|
||||
let red = (0.937, 0.267, 0.267);
|
||||
cr.set_source_rgb(red.0, red.1, red.2);
|
||||
cr.set_line_width(1.6);
|
||||
|
|
@ -370,7 +410,7 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
|
|||
cr.move_to(cx - half + arm, cy + half); cr.line_to(cx - half, cy + half); cr.line_to(cx - half, cy + half - arm);
|
||||
cr.move_to(cx + half - arm, cy + half); cr.line_to(cx + half, cy + half); cr.line_to(cx + half, cy + half - arm);
|
||||
let _ = cr.stroke();
|
||||
cr.arc(cx, cy, 3.5, 0.0, PI * 2.0);
|
||||
cr.arc(cx, cy, 3.5 * pulse, 0.0, PI * 2.0);
|
||||
let _ = cr.fill();
|
||||
}
|
||||
ToastIcon::Save => {
|
||||
|
|
@ -451,7 +491,6 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
|
|||
let window_clone = window.clone();
|
||||
let hbox_clone = hbox.clone();
|
||||
let fixed_clone = fixed.clone();
|
||||
let start_time = Instant::now();
|
||||
|
||||
gtk::glib::timeout_add_local(Duration::from_millis(16), move || {
|
||||
let elapsed = start_time.elapsed().as_secs_f32();
|
||||
|
|
@ -460,14 +499,14 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
|
|||
return gtk::glib::ControlFlow::Break;
|
||||
}
|
||||
|
||||
let slide_x = if elapsed < 0.35 {
|
||||
let t = (elapsed / 0.35).min(1.0);
|
||||
let ease = 1.0 - (1.0 - t).powi(3);
|
||||
let slide_x = if elapsed < 0.38 {
|
||||
let t = (elapsed / 0.38).min(1.0);
|
||||
let ease = 1.0 - (1.0 - t).powi(4);
|
||||
(1.0 - ease) * 320.0
|
||||
} else if elapsed < 2.40 {
|
||||
} else if elapsed < 2.45 {
|
||||
0.0
|
||||
} else {
|
||||
let t = ((elapsed - 2.40) / 0.40).min(1.0);
|
||||
let t = ((elapsed - 2.45) / 0.35).min(1.0);
|
||||
let ease = t.powi(3);
|
||||
ease * 320.0
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2154,21 +2154,28 @@ impl ScytheOverlayApp {
|
|||
let accent = self.accent_color();
|
||||
if let Some(notif) = &self.hud_notification {
|
||||
let elapsed = notif.start_time.elapsed().as_secs_f32();
|
||||
if elapsed < notif.duration_secs {
|
||||
let total_dur = notif.duration_secs;
|
||||
if elapsed < total_dur {
|
||||
ctx.request_repaint(); // 60 FPS animation
|
||||
|
||||
let slide_x = if elapsed < 0.35 {
|
||||
let t = (elapsed / 0.35).min(1.0);
|
||||
let ease = 1.0 - (1.0 - t).powi(3);
|
||||
(1.0 - ease) * 360.0
|
||||
} else if elapsed < notif.duration_secs - 0.40 {
|
||||
// Silky smooth quintic decel entrance & cubic exit slide
|
||||
let slide_x = if elapsed < 0.38 {
|
||||
let t = (elapsed / 0.38).min(1.0);
|
||||
let ease = 1.0 - (1.0 - t).powi(4);
|
||||
(1.0 - ease) * 320.0
|
||||
} else if elapsed < total_dur - 0.35 {
|
||||
0.0
|
||||
} else {
|
||||
let t = ((elapsed - (notif.duration_secs - 0.40)) / 0.40).min(1.0);
|
||||
let t = ((elapsed - (total_dur - 0.35)) / 0.35).min(1.0);
|
||||
let ease = t.powi(3);
|
||||
ease * 360.0
|
||||
ease * 320.0
|
||||
};
|
||||
|
||||
// Dynamic fade transition
|
||||
let fade_in = (elapsed / 0.22).clamp(0.0, 1.0);
|
||||
let fade_out = ((total_dur - elapsed) / 0.32).clamp(0.0, 1.0);
|
||||
let anim_alpha = fade_in.min(fade_out);
|
||||
|
||||
let screen_w = ui.available_width();
|
||||
let card_w = 320.0;
|
||||
let card_h = 56.0;
|
||||
|
|
@ -2179,34 +2186,70 @@ impl ScytheOverlayApp {
|
|||
|
||||
let painter = ui.painter();
|
||||
|
||||
// Sleek translucent neutral dark glass toast card (matching HUD cards)
|
||||
let active_color = if notif.icon == crate::overlay::ToastIcon::Record || notif.icon == crate::overlay::ToastIcon::Error {
|
||||
Color32::from_rgb(239, 68, 68)
|
||||
} else {
|
||||
accent
|
||||
};
|
||||
|
||||
// 1. Entrance accent bloom / gleam pulse (first 0.55s)
|
||||
let bloom = if elapsed < 0.55 { (1.0 - elapsed / 0.55).powi(2) } else { 0.0 };
|
||||
if bloom > 0.01 {
|
||||
painter.rect_stroke(
|
||||
toast_rect.expand(2.0),
|
||||
CornerRadius::ZERO,
|
||||
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (bloom * 90.0 * anim_alpha) as u8)),
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Sleek translucent neutral dark glass toast card fill
|
||||
painter.rect_filled(
|
||||
toast_rect,
|
||||
CornerRadius::ZERO,
|
||||
Color32::from_rgba_unmultiplied(10, 10, 10, 165),
|
||||
Color32::from_rgba_unmultiplied(10, 10, 10, (170.0 * anim_alpha) as u8),
|
||||
);
|
||||
|
||||
// Refined subtle accent stroke framing the card
|
||||
let stroke_color = if notif.icon == crate::overlay::ToastIcon::Record || notif.icon == crate::overlay::ToastIcon::Error {
|
||||
Color32::from_rgba_unmultiplied(239, 68, 68, 140)
|
||||
} else {
|
||||
Color32::from_rgba_unmultiplied(accent.r(), accent.g(), accent.b(), 140)
|
||||
};
|
||||
// 3. Top subtle specular frost highlight
|
||||
painter.line_segment(
|
||||
[toast_rect.left_top() + Vec2::new(1.0, 0.5), toast_rect.right_top() + Vec2::new(-1.0, 0.5)],
|
||||
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, (38.0 * anim_alpha) as u8)),
|
||||
);
|
||||
|
||||
// 4. Refined accent stroke framing the card
|
||||
painter.rect_stroke(
|
||||
toast_rect,
|
||||
CornerRadius::ZERO,
|
||||
Stroke::new(1.0_f32, stroke_color),
|
||||
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (140.0 * anim_alpha) as u8)),
|
||||
egui::StrokeKind::Inside,
|
||||
);
|
||||
|
||||
// 5. Animated Countdown Timer Line (bottom edge)
|
||||
let progress = 1.0 - (elapsed / total_dur).clamp(0.0, 1.0);
|
||||
let bar_track = egui::Rect::from_min_size(egui::pos2(toast_rect.left(), toast_rect.bottom() - 2.0), egui::vec2(card_w, 2.0));
|
||||
painter.rect_filled(bar_track, CornerRadius::ZERO, Color32::from_rgba_unmultiplied(255, 255, 255, (16.0 * anim_alpha) as u8));
|
||||
let bar_rect = egui::Rect::from_min_size(egui::pos2(toast_rect.left(), toast_rect.bottom() - 2.0), egui::vec2(card_w * progress, 2.0));
|
||||
painter.rect_filled(bar_rect, CornerRadius::ZERO, Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (190.0 * anim_alpha) as u8));
|
||||
|
||||
// Left icon area (centered vertically at x = 24.0)
|
||||
let icon_center = egui::pos2(toast_rect.left() + 24.0, toast_rect.center().y);
|
||||
if bloom > 0.01 {
|
||||
painter.circle_filled(
|
||||
icon_center,
|
||||
12.0,
|
||||
Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (bloom * 40.0 * anim_alpha) as u8),
|
||||
);
|
||||
}
|
||||
|
||||
let icon_tint = Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (255.0 * anim_alpha) as u8);
|
||||
match notif.icon {
|
||||
crate::overlay::ToastIcon::Replay => {
|
||||
draw_replay_icon(painter, icon_center, 12.0, true, accent);
|
||||
let scale = if elapsed < 0.35 { 0.85 + 0.15 * (elapsed / 0.35) } else { 1.0 };
|
||||
draw_replay_icon(painter, icon_center, 12.0 * scale, true, icon_tint);
|
||||
}
|
||||
crate::overlay::ToastIcon::Record => {
|
||||
let red_bright = Color32::from_rgb(239, 68, 68);
|
||||
let pulse = ((elapsed * 5.0).sin() * 0.5 + 0.5) * 0.35 + 0.65;
|
||||
let red_bright = Color32::from_rgba_unmultiplied(239, 68, 68, (255.0 * anim_alpha) as u8);
|
||||
let half = 8.5;
|
||||
let arm = 3.2;
|
||||
let stroke = Stroke::new(1.6_f32, red_bright);
|
||||
|
|
@ -2218,7 +2261,7 @@ impl ScytheOverlayApp {
|
|||
painter.line_segment([icon_center + Vec2::new(-half, half), icon_center + Vec2::new(-half, half - arm)], stroke);
|
||||
painter.line_segment([icon_center + Vec2::new(half - arm, half), icon_center + Vec2::new(half, half)], stroke);
|
||||
painter.line_segment([icon_center + Vec2::new(half, half), icon_center + Vec2::new(half, half - arm)], stroke);
|
||||
painter.circle_filled(icon_center, 3.5, red_bright);
|
||||
painter.circle_filled(icon_center, 3.2 * pulse, red_bright);
|
||||
}
|
||||
crate::overlay::ToastIcon::Save => {
|
||||
painter.add(egui::epaint::PathShape::line(
|
||||
|
|
@ -2227,7 +2270,7 @@ impl ScytheOverlayApp {
|
|||
icon_center + Vec2::new(-2.0, 4.5),
|
||||
icon_center + Vec2::new(6.0, -4.5),
|
||||
],
|
||||
Stroke::new(2.0_f32, accent),
|
||||
Stroke::new(2.0_f32, icon_tint),
|
||||
));
|
||||
}
|
||||
crate::overlay::ToastIcon::Cursor => {
|
||||
|
|
@ -2242,35 +2285,34 @@ impl ScytheOverlayApp {
|
|||
icon_center + Vec2::new(5.0, 1.0),
|
||||
icon_center + Vec2::new(-5.0, -7.0),
|
||||
],
|
||||
Stroke::new(1.8_f32, accent),
|
||||
Stroke::new(1.8_f32, icon_tint),
|
||||
));
|
||||
}
|
||||
crate::overlay::ToastIcon::Error => {
|
||||
let red = Color32::from_rgb(239, 68, 68);
|
||||
let stroke = Stroke::new(2.0_f32, red);
|
||||
let stroke = Stroke::new(2.0_f32, icon_tint);
|
||||
painter.line_segment([icon_center + Vec2::new(-5.0, -5.0), icon_center + Vec2::new(5.0, 5.0)], stroke);
|
||||
painter.line_segment([icon_center + Vec2::new(5.0, -5.0), icon_center + Vec2::new(-5.0, 5.0)], stroke);
|
||||
}
|
||||
crate::overlay::ToastIcon::Info => {
|
||||
painter.circle_filled(icon_center, 4.5, accent);
|
||||
painter.circle_filled(icon_center, 4.5, icon_tint);
|
||||
}
|
||||
}
|
||||
|
||||
// Text stack (Title + Subtitle)
|
||||
let text_left = toast_rect.left() + 46.0;
|
||||
painter.text(
|
||||
egui::pos2(text_left, toast_rect.top() + 19.0),
|
||||
egui::pos2(text_left, toast_rect.top() + 18.0),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
¬if.title,
|
||||
FontId::proportional(12.5),
|
||||
Color32::WHITE,
|
||||
Color32::from_rgba_unmultiplied(255, 255, 255, (255.0 * anim_alpha) as u8),
|
||||
);
|
||||
painter.text(
|
||||
egui::pos2(text_left, toast_rect.top() + 37.0),
|
||||
egui::pos2(text_left, toast_rect.top() + 36.0),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
¬if.subtitle,
|
||||
FontId::proportional(11.0),
|
||||
Color32::from_rgb(175, 180, 190),
|
||||
Color32::from_rgba_unmultiplied(175, 180, 190, (230.0 * anim_alpha) as u8),
|
||||
);
|
||||
} else {
|
||||
self.hud_notification = None;
|
||||
|
|
@ -2657,18 +2699,23 @@ impl eframe::App for ShadowPlayToastApp {
|
|||
}
|
||||
ctx.request_repaint_after(Duration::from_millis(16));
|
||||
|
||||
let slide_x = if elapsed < 0.35 {
|
||||
let t = (elapsed / 0.35).min(1.0);
|
||||
let ease = 1.0 - (1.0 - t).powi(3);
|
||||
let slide_x = if elapsed < 0.38 {
|
||||
let t = (elapsed / 0.38).min(1.0);
|
||||
let ease = 1.0 - (1.0 - t).powi(4);
|
||||
(1.0 - ease) * 320.0
|
||||
} else if elapsed < total_dur - 0.40 {
|
||||
} else if elapsed < total_dur - 0.35 {
|
||||
0.0
|
||||
} else {
|
||||
let t = ((elapsed - (total_dur - 0.40)) / 0.40).min(1.0);
|
||||
let t = ((elapsed - (total_dur - 0.35)) / 0.35).min(1.0);
|
||||
let ease = t.powi(3);
|
||||
ease * 320.0
|
||||
};
|
||||
|
||||
// Dynamic fade transition
|
||||
let fade_in = (elapsed / 0.22).clamp(0.0, 1.0);
|
||||
let fade_out = ((total_dur - elapsed) / 0.32).clamp(0.0, 1.0);
|
||||
let anim_alpha = fade_in.min(fade_out);
|
||||
|
||||
let mut visuals = egui::Visuals::dark();
|
||||
visuals.panel_fill = Color32::TRANSPARENT;
|
||||
visuals.window_fill = Color32::TRANSPARENT;
|
||||
|
|
@ -2683,34 +2730,70 @@ impl eframe::App for ShadowPlayToastApp {
|
|||
);
|
||||
let painter = ui.painter();
|
||||
|
||||
// Sleek translucent neutral dark glass toast card (matching HUD cards)
|
||||
let active_color = if self.icon == crate::overlay::ToastIcon::Record || self.icon == crate::overlay::ToastIcon::Error {
|
||||
Color32::from_rgb(239, 68, 68)
|
||||
} else {
|
||||
self.accent
|
||||
};
|
||||
|
||||
// 1. Entrance accent bloom / gleam pulse (first 0.55s)
|
||||
let bloom = if elapsed < 0.55 { (1.0 - elapsed / 0.55).powi(2) } else { 0.0 };
|
||||
if bloom > 0.01 {
|
||||
painter.rect_stroke(
|
||||
rect.expand(2.0),
|
||||
CornerRadius::ZERO,
|
||||
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (bloom * 90.0 * anim_alpha) as u8)),
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Sleek translucent neutral dark glass toast card fill
|
||||
painter.rect_filled(
|
||||
rect,
|
||||
CornerRadius::ZERO,
|
||||
Color32::from_rgba_unmultiplied(10, 10, 10, 165),
|
||||
Color32::from_rgba_unmultiplied(10, 10, 10, (170.0 * anim_alpha) as u8),
|
||||
);
|
||||
|
||||
// Refined subtle accent stroke framing the card
|
||||
let stroke_color = if self.icon == crate::overlay::ToastIcon::Record || self.icon == crate::overlay::ToastIcon::Error {
|
||||
Color32::from_rgba_unmultiplied(239, 68, 68, 140)
|
||||
} else {
|
||||
Color32::from_rgba_unmultiplied(self.accent.r(), self.accent.g(), self.accent.b(), 140)
|
||||
};
|
||||
// 3. Top subtle specular frost highlight
|
||||
painter.line_segment(
|
||||
[rect.left_top() + Vec2::new(1.0, 0.5), rect.right_top() + Vec2::new(-1.0, 0.5)],
|
||||
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, (38.0 * anim_alpha) as u8)),
|
||||
);
|
||||
|
||||
// 4. Refined accent stroke framing the card
|
||||
painter.rect_stroke(
|
||||
rect,
|
||||
CornerRadius::ZERO,
|
||||
Stroke::new(1.0_f32, stroke_color),
|
||||
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (140.0 * anim_alpha) as u8)),
|
||||
egui::StrokeKind::Inside,
|
||||
);
|
||||
|
||||
// 5. Animated Countdown Timer Line (bottom edge)
|
||||
let progress = 1.0 - (elapsed / total_dur).clamp(0.0, 1.0);
|
||||
let bar_track = egui::Rect::from_min_size(egui::pos2(rect.left(), rect.bottom() - 2.0), egui::vec2(320.0, 2.0));
|
||||
painter.rect_filled(bar_track, CornerRadius::ZERO, Color32::from_rgba_unmultiplied(255, 255, 255, (16.0 * anim_alpha) as u8));
|
||||
let bar_rect = egui::Rect::from_min_size(egui::pos2(rect.left(), rect.bottom() - 2.0), egui::vec2(320.0 * progress, 2.0));
|
||||
painter.rect_filled(bar_rect, CornerRadius::ZERO, Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (190.0 * anim_alpha) as u8));
|
||||
|
||||
// Left icon area (centered vertically at x = 24.0)
|
||||
let icon_center = egui::pos2(rect.left() + 24.0, rect.center().y);
|
||||
if bloom > 0.01 {
|
||||
painter.circle_filled(
|
||||
icon_center,
|
||||
12.0,
|
||||
Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (bloom * 40.0 * anim_alpha) as u8),
|
||||
);
|
||||
}
|
||||
|
||||
let icon_tint = Color32::from_rgba_unmultiplied(active_color.r(), active_color.g(), active_color.b(), (255.0 * anim_alpha) as u8);
|
||||
match self.icon {
|
||||
crate::overlay::ToastIcon::Replay => {
|
||||
draw_replay_icon(painter, icon_center, 12.0, true, self.accent);
|
||||
let scale = if elapsed < 0.35 { 0.85 + 0.15 * (elapsed / 0.35) } else { 1.0 };
|
||||
draw_replay_icon(painter, icon_center, 12.0 * scale, true, icon_tint);
|
||||
}
|
||||
crate::overlay::ToastIcon::Record => {
|
||||
let red_bright = Color32::from_rgb(239, 68, 68);
|
||||
let pulse = ((elapsed * 5.0).sin() * 0.5 + 0.5) * 0.35 + 0.65;
|
||||
let red_bright = Color32::from_rgba_unmultiplied(239, 68, 68, (255.0 * anim_alpha) as u8);
|
||||
let half = 8.5;
|
||||
let arm = 3.2;
|
||||
let stroke = Stroke::new(1.6_f32, red_bright);
|
||||
|
|
@ -2722,7 +2805,7 @@ impl eframe::App for ShadowPlayToastApp {
|
|||
painter.line_segment([icon_center + Vec2::new(-half, half), icon_center + Vec2::new(-half, half - arm)], stroke);
|
||||
painter.line_segment([icon_center + Vec2::new(half - arm, half), icon_center + Vec2::new(half, half)], stroke);
|
||||
painter.line_segment([icon_center + Vec2::new(half, half), icon_center + Vec2::new(half, half - arm)], stroke);
|
||||
painter.circle_filled(icon_center, 3.5, red_bright);
|
||||
painter.circle_filled(icon_center, 3.2 * pulse, red_bright);
|
||||
}
|
||||
crate::overlay::ToastIcon::Save => {
|
||||
painter.add(egui::epaint::PathShape::line(
|
||||
|
|
@ -2731,7 +2814,7 @@ impl eframe::App for ShadowPlayToastApp {
|
|||
icon_center + Vec2::new(-2.0, 4.5),
|
||||
icon_center + Vec2::new(6.0, -4.5),
|
||||
],
|
||||
Stroke::new(2.0_f32, self.accent),
|
||||
Stroke::new(2.0_f32, icon_tint),
|
||||
));
|
||||
}
|
||||
crate::overlay::ToastIcon::Cursor => {
|
||||
|
|
@ -2746,35 +2829,34 @@ impl eframe::App for ShadowPlayToastApp {
|
|||
icon_center + Vec2::new(5.0, 1.0),
|
||||
icon_center + Vec2::new(-5.0, -7.0),
|
||||
],
|
||||
Stroke::new(1.8_f32, self.accent),
|
||||
Stroke::new(1.8_f32, icon_tint),
|
||||
));
|
||||
}
|
||||
crate::overlay::ToastIcon::Error => {
|
||||
let red = Color32::from_rgb(239, 68, 68);
|
||||
let stroke = Stroke::new(2.0_f32, red);
|
||||
let stroke = Stroke::new(2.0_f32, icon_tint);
|
||||
painter.line_segment([icon_center + Vec2::new(-5.0, -5.0), icon_center + Vec2::new(5.0, 5.0)], stroke);
|
||||
painter.line_segment([icon_center + Vec2::new(5.0, -5.0), icon_center + Vec2::new(-5.0, 5.0)], stroke);
|
||||
}
|
||||
crate::overlay::ToastIcon::Info => {
|
||||
painter.circle_filled(icon_center, 4.5, self.accent);
|
||||
painter.circle_filled(icon_center, 4.5, icon_tint);
|
||||
}
|
||||
}
|
||||
|
||||
// Text stack (Title + Subtitle)
|
||||
let text_left = rect.left() + 46.0;
|
||||
painter.text(
|
||||
egui::pos2(text_left, rect.top() + 19.0),
|
||||
egui::pos2(text_left, rect.top() + 18.0),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
&self.title,
|
||||
FontId::proportional(12.5),
|
||||
Color32::WHITE,
|
||||
Color32::from_rgba_unmultiplied(255, 255, 255, (255.0 * anim_alpha) as u8),
|
||||
);
|
||||
painter.text(
|
||||
egui::pos2(text_left, rect.top() + 37.0),
|
||||
egui::pos2(text_left, rect.top() + 36.0),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
&self.subtitle,
|
||||
FontId::proportional(11.0),
|
||||
Color32::from_rgb(175, 180, 190),
|
||||
Color32::from_rgba_unmultiplied(175, 180, 190, (230.0 * anim_alpha) as u8),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue