Coverage Report

Created: 2026-07-21 00:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
super-stt-cosmic-applet/src/ui/components/working_animations/comet.rs
Line
Count
Source
1
// SPDX-License-Identifier: GPL-3.0-only
2
use std::f32::consts::PI;
3
4
use cosmic::{
5
    Renderer,
6
    iced::{
7
        Color, Point,
8
        widget::canvas::{Frame, path, stroke},
9
    },
10
};
11
12
use super::{WorkingAnimationRenderer, WorkingDrawContext, logical_span};
13
14
pub struct CometAnimation;
15
16
impl WorkingAnimationRenderer for CometAnimation {
17
    #[allow(clippy::cast_precision_loss)] // trail index is a tiny loop counter
18
    #[allow(clippy::many_single_char_names)] // math variables: w, h, k, a are standard notation
19
0
    fn draw(&self, frame: &mut Frame<Renderer>, ctx: &WorkingDrawContext) {
20
        let WorkingDrawContext {
21
0
            bounds,
22
0
            elapsed_ms,
23
0
            color_config,
24
0
            is_dark,
25
0
            cosmic_theme,
26
0
            side,
27
0
        } = *ctx;
28
0
        let (w, h) = (bounds.width, bounds.height);
29
0
        if w <= 0.0 {
30
0
            return;
31
0
        }
32
0
        let mid_y = bounds.y + h / 2.0;
33
0
        let amp = h * 0.30;
34
0
        let base = color_config.get_color_with_theme(is_dark, cosmic_theme);
35
36
        // Split by side: the comet path/head live in a logical full-width space;
37
        // this applet renders the `[x_lo, x_lo + w)` slice of it, so the head
38
        // travels continuously from the Left applet into the Right one.
39
0
        let (wlog, x_lo) = logical_span(side, w);
40
0
        let k = 2.0 * PI * 1.8 / wlog;
41
        // `lx` is a logical x-coordinate; `lx - x_lo` maps it to this canvas.
42
0
        let path_y = |lx: f32| mid_y + amp * (lx * k).sin();
43
44
        // Faint guide line over this applet's slice.
45
0
        let mut gb = path::Builder::new();
46
0
        let mut local = 0.0;
47
0
        let mut first = true;
48
0
        while local <= w {
49
0
            let p = Point::new(bounds.x + local, path_y(x_lo + local));
50
0
            if first {
51
0
                gb.move_to(p);
52
0
                first = false;
53
0
            } else {
54
0
                gb.line_to(p);
55
0
            }
56
0
            local += 3.0;
57
        }
58
0
        frame.stroke(
59
0
            &gb.build(),
60
0
            stroke::Stroke {
61
0
                style: stroke::Style::Solid(Color::from_rgba(
62
0
                    base.r,
63
0
                    base.g,
64
0
                    base.b,
65
0
                    base.a * 0.18,
66
0
                )),
67
0
                width: 1.4,
68
0
                ..Default::default()
69
0
            },
70
        );
71
72
        // Comet head + fading trail, positioned in logical space and clipped to
73
        // this applet's slice.
74
0
        let trail: i32 = 44;
75
0
        let head = (elapsed_ms * 0.11) % (wlog + 40.0) - 20.0;
76
0
        for i in (0..=trail).rev() {
77
0
            let lx = head - (i as f32) * 3.0;
78
0
            if lx < x_lo || lx > x_lo + w {
79
0
                continue;
80
0
            }
81
0
            let sx = bounds.x + (lx - x_lo);
82
0
            let yy = path_y(lx);
83
0
            let a = 1.0 - (i as f32) / (trail as f32);
84
0
            if i == 0 {
85
0
                let glow = path::Path::circle(Point::new(sx, yy), 7.0);
86
0
                frame.fill(
87
0
                    &glow,
88
0
                    Color::from_rgba(base.r, base.g, base.b, base.a * 0.3),
89
0
                );
90
0
            }
91
0
            let r = if i == 0 { 4.0 } else { 2.0 * a + 0.6 };
92
0
            let dot = path::Path::circle(Point::new(sx, yy), r);
93
0
            frame.fill(
94
0
                &dot,
95
0
                Color::from_rgba(base.r, base.g, base.b, base.a * a * a * 0.9),
96
            );
97
        }
98
0
    }
99
}