Skip to main content

opui/
primitives.rs

1use macroquad::prelude::*;
2
3use crate::scene::FrameContext;
4
5#[derive(Debug, Clone, Copy)]
6pub struct GlowStyle {
7    pub rings: usize,
8    pub spread: f32,
9    pub alpha: f32,
10}
11
12impl GlowStyle {
13    pub fn soft() -> Self {
14        Self {
15            rings: 5,
16            spread: 18.0,
17            alpha: 0.18,
18        }
19    }
20}
21
22#[derive(Debug, Clone, Copy)]
23pub struct Particle {
24    pub orbit_radius: f32,
25    pub speed: f32,
26    pub phase: f32,
27    pub size: f32,
28    pub height_bias: f32,
29}
30
31#[derive(Debug, Clone)]
32pub struct ParticleField {
33    particles: Vec<Particle>,
34}
35
36impl ParticleField {
37    pub fn orbiting(count: usize, radius_start: f32, radius_end: f32) -> Self {
38        let count = count.max(1);
39        let mut particles = Vec::with_capacity(count);
40
41        for index in 0..count {
42            let t = index as f32 / count as f32;
43            particles.push(Particle {
44                orbit_radius: radius_start + (radius_end - radius_start) * t,
45                speed: 0.18 + t * 0.95,
46                phase: t * std::f32::consts::TAU * 4.0,
47                size: 1.2 + (index % 6) as f32 * 0.45,
48                height_bias: -120.0 + ((index * 53) % 240) as f32,
49            });
50        }
51
52        Self { particles }
53    }
54
55    pub fn particles(&self) -> &[Particle] {
56        &self.particles
57    }
58}
59
60pub fn mix_color(a: Color, b: Color, t: f32) -> Color {
61    let t = t.clamp(0.0, 1.0);
62    Color::new(
63        a.r + (b.r - a.r) * t,
64        a.g + (b.g - a.g) * t,
65        a.b + (b.b - a.b) * t,
66        a.a + (b.a - a.a) * t,
67    )
68}
69
70pub fn draw_gradient_background(ctx: &FrameContext, top: Color, bottom: Color, steps: usize) {
71    let steps = steps.max(2);
72
73    for i in 0..steps {
74        let t = i as f32 / (steps - 1) as f32;
75        let color = mix_color(top, bottom, t);
76        draw_rectangle(
77            0.0,
78            ctx.height * t,
79            ctx.width,
80            ctx.height / steps as f32 + 1.0,
81            color,
82        );
83    }
84}
85
86pub fn draw_glow_circle(center: Vec2, radius: f32, color: Color, style: GlowStyle) {
87    for index in 0..style.rings {
88        let spread = radius + index as f32 * style.spread;
89        let alpha = color.a * (style.alpha - index as f32 * 0.025).max(0.02);
90        draw_circle(center.x, center.y, spread, Color::new(color.r, color.g, color.b, alpha));
91    }
92}
93
94pub fn draw_polyline(points: &[Vec2], thickness: f32, color: Color) {
95    for pair in points.windows(2) {
96        let start = pair[0];
97        let end = pair[1];
98        draw_line(start.x, start.y, end.x, end.y, thickness, color);
99    }
100}
101
102pub fn draw_particle_field<F>(
103    ctx: &FrameContext,
104    origin: Vec2,
105    field: &ParticleField,
106    mut color_at: F,
107)
108where
109    F: FnMut(&Particle, Vec2, f32) -> Color,
110{
111    for particle in field.particles() {
112        let angle = ctx.time * particle.speed + particle.phase;
113        let local = vec2(
114            angle.cos() * particle.orbit_radius,
115            particle.height_bias + angle.sin() * (36.0 + particle.orbit_radius * 0.08),
116        );
117        let position = origin + local;
118        let shimmer = 0.2 + 0.8 * (ctx.time * 1.6 + particle.phase).sin().abs();
119        let color = color_at(particle, position, shimmer);
120        draw_circle(
121            position.x,
122            position.y,
123            particle.size + shimmer * 1.6,
124            color,
125        );
126    }
127}