opui 0.1.0

A Rust library for stylish realtime 2D motion visuals and layered hero scenes.
Documentation
use macroquad::prelude::*;

use crate::scene::FrameContext;

#[derive(Debug, Clone, Copy)]
pub struct GlowStyle {
    pub rings: usize,
    pub spread: f32,
    pub alpha: f32,
}

impl GlowStyle {
    pub fn soft() -> Self {
        Self {
            rings: 5,
            spread: 18.0,
            alpha: 0.18,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Particle {
    pub orbit_radius: f32,
    pub speed: f32,
    pub phase: f32,
    pub size: f32,
    pub height_bias: f32,
}

#[derive(Debug, Clone)]
pub struct ParticleField {
    particles: Vec<Particle>,
}

impl ParticleField {
    pub fn orbiting(count: usize, radius_start: f32, radius_end: f32) -> Self {
        let count = count.max(1);
        let mut particles = Vec::with_capacity(count);

        for index in 0..count {
            let t = index as f32 / count as f32;
            particles.push(Particle {
                orbit_radius: radius_start + (radius_end - radius_start) * t,
                speed: 0.18 + t * 0.95,
                phase: t * std::f32::consts::TAU * 4.0,
                size: 1.2 + (index % 6) as f32 * 0.45,
                height_bias: -120.0 + ((index * 53) % 240) as f32,
            });
        }

        Self { particles }
    }

    pub fn particles(&self) -> &[Particle] {
        &self.particles
    }
}

pub fn mix_color(a: Color, b: Color, t: f32) -> Color {
    let t = t.clamp(0.0, 1.0);
    Color::new(
        a.r + (b.r - a.r) * t,
        a.g + (b.g - a.g) * t,
        a.b + (b.b - a.b) * t,
        a.a + (b.a - a.a) * t,
    )
}

pub fn draw_gradient_background(ctx: &FrameContext, top: Color, bottom: Color, steps: usize) {
    let steps = steps.max(2);

    for i in 0..steps {
        let t = i as f32 / (steps - 1) as f32;
        let color = mix_color(top, bottom, t);
        draw_rectangle(
            0.0,
            ctx.height * t,
            ctx.width,
            ctx.height / steps as f32 + 1.0,
            color,
        );
    }
}

pub fn draw_glow_circle(center: Vec2, radius: f32, color: Color, style: GlowStyle) {
    for index in 0..style.rings {
        let spread = radius + index as f32 * style.spread;
        let alpha = color.a * (style.alpha - index as f32 * 0.025).max(0.02);
        draw_circle(center.x, center.y, spread, Color::new(color.r, color.g, color.b, alpha));
    }
}

pub fn draw_polyline(points: &[Vec2], thickness: f32, color: Color) {
    for pair in points.windows(2) {
        let start = pair[0];
        let end = pair[1];
        draw_line(start.x, start.y, end.x, end.y, thickness, color);
    }
}

pub fn draw_particle_field<F>(
    ctx: &FrameContext,
    origin: Vec2,
    field: &ParticleField,
    mut color_at: F,
)
where
    F: FnMut(&Particle, Vec2, f32) -> Color,
{
    for particle in field.particles() {
        let angle = ctx.time * particle.speed + particle.phase;
        let local = vec2(
            angle.cos() * particle.orbit_radius,
            particle.height_bias + angle.sin() * (36.0 + particle.orbit_radius * 0.08),
        );
        let position = origin + local;
        let shimmer = 0.2 + 0.8 * (ctx.time * 1.6 + particle.phase).sin().abs();
        let color = color_at(particle, position, shimmer);
        draw_circle(
            position.x,
            position.y,
            particle.size + shimmer * 1.6,
            color,
        );
    }
}