ui: align notifications flush to screen edge with translucent cards and modern icons

This commit is contained in:
katR 2026-09-12 10:31:33 +05:30
parent f0117451e7
commit a341565c5a
3 changed files with 192 additions and 166 deletions

View file

@ -158,6 +158,7 @@ pub fn register_hyprland_binds(config: &ScytheConfig) {
"float, class:^(scythe-toast)$",
"pin, class:^(scythe-toast)$",
"noborder, class:^(scythe-toast)$",
"move 100%-w 16, class:^(scythe-toast)$",
"float, class:^(vrec-overlay)$",
"move 0 0, class:^(vrec-overlay)$",
"size 100% 100%, class:^(vrec-overlay)$",

View file

@ -199,7 +199,7 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
_ => ("#38bdf8", (0.220, 0.741, 0.973)),
};
let (active_accent_hex, active_accent) = if icon == ToastIcon::Record {
let (_active_accent_hex, active_accent) = if icon == ToastIcon::Record {
("#ef4444", (0.937, 0.267, 0.267))
} else {
(accent_hex, accent_rgb)
@ -229,8 +229,8 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
app.connect_activate(move |app| {
let window = ApplicationWindow::builder()
.application(app)
.default_width(360)
.default_height(64)
.default_width(320)
.default_height(56)
.build();
#[cfg(target_os = "linux")]
@ -250,8 +250,8 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
}
window.set_anchor(gtk_layer_shell::Edge::Top, true);
window.set_anchor(gtk_layer_shell::Edge::Right, true);
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 28);
window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, 28);
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 16);
window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, 0); // flush on screen right edge
window.set_keyboard_interactivity(false);
} else {
window.set_decorated(false);
@ -260,8 +260,8 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
window.set_accept_focus(false);
if let Some(ref mon) = active_monitor {
let geom = mon.geometry();
let x = geom.x() + geom.width() - 360 - 28;
let y = geom.y() + 28;
let x = geom.x() + geom.width() - 320;
let y = geom.y() + 16;
window.move_(x, y);
}
}
@ -289,14 +289,12 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
box-shadow: none;
}}
.toast-card {{
background-color: rgba(14, 16, 21, 0.96);
border: 1px solid rgba(255, 255, 255, 0.14);
border-left: 4px solid {accent};
background-color: rgba(10, 10, 10, 0.65);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0px;
box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.75);
box-shadow: none;
}}
"#,
accent = active_accent_hex
);
let _ = css_provider.load_from_data(css.as_bytes());
if let Some(screen) = gdk::Screen::default() {
@ -316,31 +314,20 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
hbox.connect_draw(move |widget, cr| {
let w = widget.allocated_width() as f64;
let h = widget.allocated_height() as f64;
let card_h = (h - 4.0).max(10.0);
cr.set_operator(gtk::cairo::Operator::Over);
// 1. Drop shadow (soft dark shadow offset down by 4px)
cr.set_source_rgba(0.0, 0.0, 0.0, 0.60);
cr.rectangle(0.0, 4.0, w, card_h);
// 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);
cr.rectangle(0.0, 0.0, w, h);
let _ = cr.fill();
// 2. Solid obsidian dark slate background (matching egui rgba(14, 16, 21, 0.96))
cr.set_source_rgba(14.0 / 255.0, 16.0 / 255.0, 21.0 / 255.0, 0.96);
cr.rectangle(0.0, 0.0, w, card_h);
let _ = cr.fill();
// 3. Subtle white border (1px inside)
cr.set_source_rgba(1.0, 1.0, 1.0, 0.14);
// Refined subtle accent stroke framing the card
cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.55);
cr.set_line_width(1.0);
cr.rectangle(0.5, 0.5, w - 1.0, card_h - 1.0);
cr.rectangle(0.5, 0.5, w - 1.0, h - 1.0);
let _ = cr.stroke();
// 4. Left accent bar (4.0px width)
cr.set_source_rgb(active_accent.0, active_accent.1, active_accent.2);
cr.rectangle(0.0, 0.0, 4.0, card_h);
let _ = cr.fill();
gtk::glib::Propagation::Proceed
});
@ -355,75 +342,75 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
let cy = 16.0;
match icon_type {
ToastIcon::Replay => {
let r = 10.0;
cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2);
cr.set_line_width(2.2);
cr.arc(cx, cy, r, 0.25 * PI, 1.80 * PI);
let _ = cr.stroke();
let a_x = cx + r * (0.25 * PI).cos();
let a_y = cy + r * (0.25 * PI).sin();
cr.move_to(a_x, a_y);
cr.line_to(a_x - 4.5, a_y + 0.5);
cr.line_to(a_x - 0.5, a_y - 4.5);
cr.close_path();
let _ = cr.fill();
let tri_r = 4.0;
cr.move_to(cx + tri_r + 0.5, cy);
cr.line_to(cx - tri_r * 0.6 + 0.5, cy - tri_r * 0.86);
cr.line_to(cx - tri_r * 0.6 + 0.5, cy + tri_r * 0.86);
cr.close_path();
let _ = cr.fill();
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 start_x = cx - (ch_w + gap) * 0.5;
for i in 0..2 {
let tip_x = start_x + (i as f64) * gap;
let base_x = tip_x + ch_w;
cr.move_to(base_x, cy - ch_h);
cr.line_to(tip_x, cy);
cr.line_to(base_x, cy + ch_h);
let _ = cr.stroke();
}
}
ToastIcon::Record => {
cr.set_source_rgba(0.937, 0.267, 0.267, 0.25);
cr.arc(cx, cy, 13.0, 0.0, PI * 2.0);
let _ = cr.fill();
cr.set_source_rgb(0.937, 0.267, 0.267);
cr.set_line_width(1.8);
cr.arc(cx, cy, 10.0, 0.0, PI * 2.0);
let red = (0.937, 0.267, 0.267);
cr.set_source_rgb(red.0, red.1, red.2);
cr.set_line_width(1.6);
let half = 8.5;
let arm = 3.2;
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);
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, 5.0, 0.0, PI * 2.0);
cr.arc(cx, cy, 3.5, 0.0, PI * 2.0);
let _ = cr.fill();
}
ToastIcon::Save => {
cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2);
cr.set_line_width(2.5);
cr.set_line_width(2.0);
cr.set_line_cap(gtk::cairo::LineCap::Round);
cr.set_line_join(gtk::cairo::LineJoin::Round);
cr.move_to(cx - 7.0, cy);
cr.line_to(cx - 2.0, cy + 5.0);
cr.line_to(cx + 7.0, cy - 5.0);
cr.move_to(cx - 6.0, cy + 0.5);
cr.line_to(cx - 2.0, cy + 4.5);
cr.line_to(cx + 6.0, cy - 4.5);
let _ = cr.stroke();
}
ToastIcon::Cursor => {
cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2);
cr.set_line_width(1.8);
cr.move_to(cx - 6.0, cy - 8.0);
cr.line_to(cx + 6.0, cy - 1.0);
cr.line_to(cx, cy + 1.0);
cr.line_to(cx + 2.0, cy + 7.0);
cr.line_to(cx - 1.0, cy + 8.0);
cr.line_to(cx - 3.0, cy + 2.0);
cr.line_to(cx - 6.0, cy + 4.0);
cr.set_line_cap(gtk::cairo::LineCap::Round);
cr.set_line_join(gtk::cairo::LineJoin::Round);
cr.move_to(cx - 5.0, cy - 7.0);
cr.line_to(cx - 5.0, cy + 5.0);
cr.line_to(cx - 1.5, cy + 2.0);
cr.line_to(cx + 1.5, cy + 7.0);
cr.line_to(cx + 3.5, cy + 6.0);
cr.line_to(cx + 0.5, cy + 1.0);
cr.line_to(cx + 5.0, cy + 1.0);
cr.close_path();
let _ = cr.fill();
let _ = cr.stroke();
}
ToastIcon::Error => {
cr.set_source_rgb(0.937, 0.267, 0.267);
cr.set_line_width(2.2);
cr.move_to(cx - 6.0, cy - 6.0);
cr.line_to(cx + 6.0, cy + 6.0);
cr.move_to(cx + 6.0, cy - 6.0);
cr.line_to(cx - 6.0, cy + 6.0);
cr.set_line_width(2.0);
cr.set_line_cap(gtk::cairo::LineCap::Round);
cr.move_to(cx - 5.0, cy - 5.0);
cr.line_to(cx + 5.0, cy + 5.0);
cr.move_to(cx + 5.0, cy - 5.0);
cr.line_to(cx - 5.0, cy + 5.0);
let _ = cr.stroke();
}
ToastIcon::Info => {
cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2);
cr.arc(cx, cy, 6.0, 0.0, PI * 2.0);
cr.arc(cx, cy, 4.5, 0.0, PI * 2.0);
let _ = cr.fill();
}
}
@ -437,14 +424,14 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
let title_lbl = Label::new(None);
title_lbl.set_markup(&format!(
"<span font_desc='monospace bold 10.5' color='#ffffff'>{}</span>",
"<span font_desc='sans bold 10.5' color='#ffffff'>{}</span>",
gtk::glib::markup_escape_text(&title_text)
));
title_lbl.set_halign(gtk::Align::Start);
let sub_lbl = Label::new(None);
sub_lbl.set_markup(&format!(
"<span font_desc='sans 9' color='#a1a1aa'>{}</span>",
"<span font_desc='sans 9.5' color='#b4b9c4'>{}</span>",
gtk::glib::markup_escape_text(&sub_text)
));
sub_lbl.set_halign(gtk::Align::Start);
@ -456,8 +443,8 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
hbox.pack_start(&vbox, true, true, 0);
let fixed = Fixed::new();
fixed.set_size_request(360, 64);
fixed.put(&hbox, 360, 4);
fixed.set_size_request(320, 56);
fixed.put(&hbox, 320, 0);
window.add(&fixed);
window.show_all();
@ -476,17 +463,17 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) {
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
(1.0 - ease) * 320.0
} else if elapsed < 2.40 {
0.0
} else {
let t = ((elapsed - 2.40) / 0.40).min(1.0);
let ease = t.powi(3);
ease * 360.0
ease * 320.0
};
let target_x = (slide_x + 14.0).round() as i32;
fixed_clone.move_(&hbox_clone, target_x, 4);
let target_x = slide_x.round() as i32;
fixed_clone.move_(&hbox_clone, target_x, 0);
window_clone.queue_draw();
gtk::glib::ControlFlow::Continue
});

View file

@ -525,7 +525,7 @@ fn draw_replay_icon(painter: &egui::Painter, center: egui::Pos2, radius: f32, is
};
// Minimalist Line Chevrons (≪) - Crisp, high-tech stroked rewind glyph
let stroke_w = 2.4_f32;
let stroke_w = (radius * 0.10).clamp(1.7, 2.4);
let stroke = Stroke::new(stroke_w, color);
let chevron_h = radius * 0.62;
let chevron_w = radius * 0.38;
@ -2173,72 +2173,91 @@ impl ScytheOverlayApp {
let card_w = 320.0;
let card_h = 56.0;
let toast_rect = egui::Rect::from_min_size(
egui::pos2(screen_w - card_w - 28.0 + slide_x, 28.0),
egui::pos2(screen_w - card_w + slide_x, 16.0),
egui::vec2(card_w, card_h),
);
let painter = ui.painter();
// Drop shadow
// Sleek translucent neutral dark glass toast card (matching HUD cards)
painter.rect_filled(
toast_rect.translate(Vec2::new(0.0, 4.0)),
CornerRadius::ZERO,
Color32::from_rgba_unmultiplied(0, 0, 0, 140),
);
// Modern sleek dark glass toast card
painter.rect(
toast_rect,
CornerRadius::ZERO,
Color32::from_rgba_unmultiplied(12, 13, 16, 252),
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, 30)),
Color32::from_rgba_unmultiplied(10, 10, 10, 165),
);
// 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)
};
painter.rect_stroke(
toast_rect,
CornerRadius::ZERO,
Stroke::new(1.0_f32, stroke_color),
egui::StrokeKind::Inside,
);
// Left vertical accent bar
let line_color = if notif.icon == crate::overlay::ToastIcon::Record {
Color32::from_rgb(239, 68, 68)
} else {
accent
};
let accent_line = egui::Rect::from_min_size(toast_rect.min + Vec2::new(1.0, 1.0), Vec2::new(3.5, toast_rect.height() - 2.0));
painter.rect_filled(accent_line, CornerRadius::ZERO, line_color);
// Left icon area (32x32 at center-left)
// Left icon area (centered vertically at x = 24.0)
let icon_center = egui::pos2(toast_rect.left() + 24.0, toast_rect.center().y);
match notif.icon {
crate::overlay::ToastIcon::Replay => {
draw_replay_icon(painter, icon_center, 9.5, true, accent);
draw_replay_icon(painter, icon_center, 12.0, true, accent);
}
crate::overlay::ToastIcon::Record => {
painter.circle_filled(icon_center, 5.5, Color32::from_rgb(239, 68, 68));
painter.circle_stroke(icon_center, 9.5, Stroke::new(1.2_f32, Color32::from_rgba_unmultiplied(239, 68, 68, 120)));
let red_bright = Color32::from_rgb(239, 68, 68);
let half = 8.5;
let arm = 3.2;
let stroke = Stroke::new(1.6_f32, red_bright);
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.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.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.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);
}
crate::overlay::ToastIcon::Save => {
let c = icon_center;
let p1 = c + Vec2::new(-5.5, 0.0);
let p2 = c + Vec2::new(-1.5, 4.0);
let p3 = c + Vec2::new(5.5, -4.0);
painter.line_segment([p1, p2], Stroke::new(2.2_f32, accent));
painter.line_segment([p2, p3], Stroke::new(2.2_f32, accent));
painter.add(egui::epaint::PathShape::line(
vec![
icon_center + Vec2::new(-6.0, 0.5),
icon_center + Vec2::new(-2.0, 4.5),
icon_center + Vec2::new(6.0, -4.5),
],
Stroke::new(2.0_f32, accent),
));
}
crate::overlay::ToastIcon::Cursor => {
let c = icon_center;
painter.text(c, egui::Align2::CENTER_CENTER, "⯈", FontId::proportional(14.0), accent);
painter.add(egui::epaint::PathShape::line(
vec![
icon_center + Vec2::new(-5.0, -7.0),
icon_center + Vec2::new(-5.0, 5.0),
icon_center + Vec2::new(-1.5, 2.0),
icon_center + Vec2::new(1.5, 7.0),
icon_center + Vec2::new(3.5, 6.0),
icon_center + Vec2::new(0.5, 1.0),
icon_center + Vec2::new(5.0, 1.0),
icon_center + Vec2::new(-5.0, -7.0),
],
Stroke::new(1.8_f32, accent),
));
}
crate::overlay::ToastIcon::Error => {
let c = icon_center;
let red = Color32::from_rgb(239, 68, 68);
painter.line_segment([c + Vec2::new(-4.0, -4.0), c + Vec2::new(4.0, 4.0)], Stroke::new(2.0_f32, red));
painter.line_segment([c + Vec2::new(4.0, -4.0), c + Vec2::new(-4.0, 4.0)], Stroke::new(2.0_f32, red));
let stroke = Stroke::new(2.0_f32, red);
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, 5.0, accent);
painter.circle_filled(icon_center, 4.5, accent);
}
}
// Text stack (Title + Subtitle)
let text_left = toast_rect.left() + 48.0;
let text_left = toast_rect.left() + 46.0;
painter.text(
egui::pos2(text_left, toast_rect.top() + 19.0),
egui::Align2::LEFT_CENTER,
@ -2251,7 +2270,7 @@ impl ScytheOverlayApp {
egui::Align2::LEFT_CENTER,
&notif.subtitle,
FontId::proportional(11.0),
Color32::from_rgb(160, 160, 168),
Color32::from_rgb(175, 180, 190),
);
} else {
self.hud_notification = None;
@ -2641,13 +2660,13 @@ impl eframe::App for ShadowPlayToastApp {
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) * 340.0
(1.0 - ease) * 320.0
} else if elapsed < total_dur - 0.40 {
0.0
} else {
let t = ((elapsed - (total_dur - 0.40)) / 0.40).min(1.0);
let ease = t.powi(3);
ease * 340.0
ease * 320.0
};
let mut visuals = egui::Visuals::dark();
@ -2659,71 +2678,90 @@ impl eframe::App for ShadowPlayToastApp {
.frame(egui::Frame::NONE.fill(Color32::TRANSPARENT))
.show(ctx, |ui| {
let rect = egui::Rect::from_min_size(
egui::pos2(slide_x + 10.0, 4.0),
egui::pos2(slide_x, 0.0),
Vec2::new(320.0, 56.0),
);
let painter = ui.painter();
// Drop shadow
// Sleek translucent neutral dark glass toast card (matching HUD cards)
painter.rect_filled(
rect.translate(Vec2::new(0.0, 4.0)),
CornerRadius::ZERO,
Color32::from_rgba_unmultiplied(0, 0, 0, 140),
);
// Modern sleek dark glass toast card
painter.rect(
rect,
CornerRadius::ZERO,
Color32::from_rgba_unmultiplied(12, 13, 16, 252),
Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, 30)),
Color32::from_rgba_unmultiplied(10, 10, 10, 165),
);
// 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)
};
painter.rect_stroke(
rect,
CornerRadius::ZERO,
Stroke::new(1.0_f32, stroke_color),
egui::StrokeKind::Inside,
);
// Left accent line
let line_color = if self.icon == crate::overlay::ToastIcon::Record {
Color32::from_rgb(239, 68, 68)
} else {
self.accent
};
let accent_line = egui::Rect::from_min_size(rect.min + Vec2::new(1.0, 1.0), Vec2::new(3.5, rect.height() - 2.0));
painter.rect_filled(accent_line, CornerRadius::ZERO, line_color);
// Left icon area (32x32 at center-left)
// Left icon area (centered vertically at x = 24.0)
let icon_center = egui::pos2(rect.left() + 24.0, rect.center().y);
match self.icon {
crate::overlay::ToastIcon::Replay => {
draw_replay_icon(painter, icon_center, 9.5, true, self.accent);
draw_replay_icon(painter, icon_center, 12.0, true, self.accent);
}
crate::overlay::ToastIcon::Record => {
painter.circle_filled(icon_center, 5.5, Color32::from_rgb(239, 68, 68));
painter.circle_stroke(icon_center, 9.5, Stroke::new(1.2_f32, Color32::from_rgba_unmultiplied(239, 68, 68, 120)));
let red_bright = Color32::from_rgb(239, 68, 68);
let half = 8.5;
let arm = 3.2;
let stroke = Stroke::new(1.6_f32, red_bright);
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.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.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.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);
}
crate::overlay::ToastIcon::Save => {
let c = icon_center;
let p1 = c + Vec2::new(-5.5, 0.0);
let p2 = c + Vec2::new(-1.5, 4.0);
let p3 = c + Vec2::new(5.5, -4.0);
painter.line_segment([p1, p2], Stroke::new(2.2_f32, self.accent));
painter.line_segment([p2, p3], Stroke::new(2.2_f32, self.accent));
painter.add(egui::epaint::PathShape::line(
vec![
icon_center + Vec2::new(-6.0, 0.5),
icon_center + Vec2::new(-2.0, 4.5),
icon_center + Vec2::new(6.0, -4.5),
],
Stroke::new(2.0_f32, self.accent),
));
}
crate::overlay::ToastIcon::Cursor => {
let c = icon_center;
painter.text(c, egui::Align2::CENTER_CENTER, "⯈", FontId::proportional(14.0), self.accent);
painter.add(egui::epaint::PathShape::line(
vec![
icon_center + Vec2::new(-5.0, -7.0),
icon_center + Vec2::new(-5.0, 5.0),
icon_center + Vec2::new(-1.5, 2.0),
icon_center + Vec2::new(1.5, 7.0),
icon_center + Vec2::new(3.5, 6.0),
icon_center + Vec2::new(0.5, 1.0),
icon_center + Vec2::new(5.0, 1.0),
icon_center + Vec2::new(-5.0, -7.0),
],
Stroke::new(1.8_f32, self.accent),
));
}
crate::overlay::ToastIcon::Error => {
let c = icon_center;
let red = Color32::from_rgb(239, 68, 68);
painter.line_segment([c + Vec2::new(-4.0, -4.0), c + Vec2::new(4.0, 4.0)], Stroke::new(2.0_f32, red));
painter.line_segment([c + Vec2::new(4.0, -4.0), c + Vec2::new(-4.0, 4.0)], Stroke::new(2.0_f32, red));
let stroke = Stroke::new(2.0_f32, red);
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, 5.0, self.accent);
painter.circle_filled(icon_center, 4.5, self.accent);
}
}
// Text stack (Title + Subtitle)
let text_left = rect.left() + 48.0;
let text_left = rect.left() + 46.0;
painter.text(
egui::pos2(text_left, rect.top() + 19.0),
egui::Align2::LEFT_CENTER,
@ -2736,7 +2774,7 @@ impl eframe::App for ShadowPlayToastApp {
egui::Align2::LEFT_CENTER,
&self.subtitle,
FontId::proportional(11.0),
Color32::from_rgb(160, 160, 168),
Color32::from_rgb(175, 180, 190),
);
});
}
@ -2746,8 +2784,8 @@ pub fn run_egui_toast(title: &str, subtitle: &str, icon: crate::overlay::ToastIc
let cfg = ScytheConfig::load();
let accent = resolve_accent_color(&cfg.accent_color);
let toast_w: f32 = 340.0;
let toast_h: f32 = 64.0;
let toast_w: f32 = 320.0;
let toast_h: f32 = 56.0;
let toast_pid_path = crate::ipc::get_toast_pid_path();
if let Ok(prev_pid_str) = std::fs::read_to_string(&toast_pid_path)
@ -2778,8 +2816,8 @@ pub fn run_egui_toast(title: &str, subtitle: &str, icon: crate::overlay::ToastIc
#[cfg(not(target_os = "windows"))]
let (mon_x, _mon_y, screen_w, _screen_h) = query_focused_monitor_rect();
let pos_x = (mon_x + screen_w - toast_w - 28.0).max(10.0);
let pos_y = 28.0;
let pos_x = (mon_x + screen_w - toast_w).max(0.0);
let pos_y = 16.0;
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()