use macroquad::prelude::*;
use crate::color::Palette;
use crate::layers::{
GlassWaveConfig, GlassWaveLayer, LayerScene, LiquidOrbConfig, LiquidOrbLayer, OrbSpec,
ParticleMistLayer,
};
use crate::primitives::{
GlowStyle, ParticleField, draw_glow_circle, draw_gradient_background, draw_particle_field,
draw_polyline,
};
use crate::scene::{FrameContext, Scene};
#[derive(Debug, Clone)]
struct Orb {
radius: f32,
distance: f32,
speed: f32,
phase: f32,
size: f32,
color: Color,
}
pub struct OrbField {
palette: Palette,
orbs: Vec<Orb>,
particles: ParticleField,
pulse: f32,
drift: f32,
focus: Vec2,
trail_strength: f32,
}
impl OrbField {
pub fn new() -> Self {
let palette = Palette::aurora();
let orbs = vec![
Orb {
radius: 80.0,
distance: 90.0,
speed: 0.9,
phase: 0.0,
size: 16.0,
color: palette.accent_a,
},
Orb {
radius: 140.0,
distance: 150.0,
speed: -0.65,
phase: 1.2,
size: 22.0,
color: palette.accent_b,
},
Orb {
radius: 220.0,
distance: 210.0,
speed: 0.35,
phase: 2.7,
size: 12.0,
color: palette.glow,
},
];
Self {
palette,
orbs,
particles: ParticleField::orbiting(140, 90.0, 370.0),
pulse: 0.0,
drift: 0.0,
focus: Vec2::ZERO,
trail_strength: 0.35,
}
}
pub fn with_palette(mut self, palette: Palette) -> Self {
self.palette = palette;
self.orbs[0].color = palette.accent_a;
self.orbs[1].color = palette.accent_b;
self.orbs[2].color = palette.glow;
self
}
pub fn with_particle_density(mut self, count: usize) -> Self {
self.particles = ParticleField::orbiting(count.max(24), 90.0, 410.0);
self
}
pub fn with_trail_strength(mut self, strength: f32) -> Self {
self.trail_strength = strength.clamp(0.05, 1.2);
self
}
fn draw_particles(&self, ctx: &FrameContext, base: Vec2) {
draw_particle_field(ctx, base, &self.particles, |_, _, shimmer| {
Color::new(
self.palette.accent_a.r,
self.palette.accent_a.g,
self.palette.accent_a.b,
0.08 + shimmer * 0.18,
)
});
}
fn draw_trails(&self, ctx: &FrameContext, base: Vec2) {
for orb in &self.orbs {
let mut points = Vec::with_capacity(22);
for segment in 0..22 {
let t = segment as f32 / 21.0;
let angle = (ctx.time - t * self.trail_strength) * orb.speed + orb.phase;
let wobble = vec2(
angle.cos() * orb.distance,
angle.sin() * orb.radius + (ctx.time * 1.3 + orb.phase - t).sin() * 16.0,
);
points.push(base + wobble);
}
for (index, pair) in points.windows(2).enumerate() {
let t = index as f32 / (points.len().saturating_sub(2).max(1)) as f32;
draw_polyline(
pair,
2.0 - t * 1.4,
Color::new(orb.color.r, orb.color.g, orb.color.b, (1.0 - t) * 0.22),
);
}
}
}
}
impl Default for OrbField {
fn default() -> Self {
Self::new()
}
}
impl Scene for OrbField {
fn update(&mut self, ctx: &FrameContext) {
self.pulse += ctx.delta * 1.8;
self.drift += ctx.delta * 0.35;
let target_focus = ctx.mouse_offset * 0.12;
self.focus = self.focus.lerp(target_focus, 1.0 - (-ctx.delta * 3.2).exp());
}
fn draw(&self, ctx: &FrameContext) {
draw_gradient_background(ctx, self.palette.background_top, self.palette.background_bottom, 32);
let base = vec2(
ctx.center.x + (ctx.time * 0.35).sin() * 24.0 + self.focus.x,
ctx.center.y + (ctx.time * 0.2).cos() * 20.0 + self.focus.y,
);
self.draw_particles(ctx, base);
self.draw_trails(ctx, base);
let core_radius = 70.0 + self.pulse.sin() * 8.0;
draw_glow_circle(base, core_radius, self.palette.glow, GlowStyle::soft());
draw_circle(
base.x,
base.y,
core_radius,
Color::new(
self.palette.accent_a.r,
self.palette.accent_a.g,
self.palette.accent_a.b,
0.16,
),
);
for orb in &self.orbs {
let angle = ctx.time * orb.speed + orb.phase;
let wobble = vec2(
angle.cos() * orb.distance,
angle.sin() * orb.radius
+ (ctx.time * 1.3 + orb.phase).sin() * 16.0
+ self.focus.y * 0.08,
);
let pos = base + wobble;
draw_glow_circle(pos, orb.size * 0.9, orb.color, GlowStyle::soft());
draw_circle(pos.x, pos.y, orb.size, orb.color);
}
for line_idx in 0..18 {
let t = line_idx as f32 / 17.0;
let y = ctx.height * t;
let alpha = 0.03 + 0.02 * ((ctx.time * 0.4 + t * 9.0).sin() * 0.5 + 0.5);
draw_line(
0.0,
y,
ctx.width,
y + (ctx.time * 0.8 + t * 5.0 + self.drift).sin() * 6.0 + self.focus.y * 0.04,
1.5,
Color::new(1.0, 1.0, 1.0, alpha),
);
}
let vignette = Color::new(
self.palette.background_bottom.r,
self.palette.background_bottom.g,
self.palette.background_bottom.b,
0.18,
);
draw_rectangle(0.0, 0.0, ctx.width, 36.0, vignette);
draw_rectangle(0.0, ctx.height - 36.0, ctx.width, 36.0, vignette);
}
}
pub struct WaveGrid {
palette: Palette,
columns: usize,
rows: usize,
spacing: f32,
amplitude: f32,
speed: f32,
focus: Vec2,
}
impl WaveGrid {
pub fn new() -> Self {
Self {
palette: Palette::neon_night(),
columns: 26,
rows: 15,
spacing: 42.0,
amplitude: 28.0,
speed: 1.0,
focus: Vec2::ZERO,
}
}
pub fn with_palette(mut self, palette: Palette) -> Self {
self.palette = palette;
self
}
pub fn with_density(mut self, columns: usize, rows: usize) -> Self {
self.columns = columns.max(6);
self.rows = rows.max(4);
self
}
pub fn with_motion(mut self, amplitude: f32, speed: f32) -> Self {
self.amplitude = amplitude.max(1.0);
self.speed = speed.max(0.05);
self
}
}
impl Default for WaveGrid {
fn default() -> Self {
Self::new()
}
}
impl Scene for WaveGrid {
fn update(&mut self, ctx: &FrameContext) {
let target_focus = ctx.mouse_offset * 0.08;
self.focus = self.focus.lerp(target_focus, 1.0 - (-ctx.delta * 4.0).exp());
}
fn draw(&self, ctx: &FrameContext) {
draw_gradient_background(ctx, self.palette.background_top, self.palette.background_bottom, 40);
let grid_width = (self.columns.saturating_sub(1)) as f32 * self.spacing;
let grid_height = (self.rows.saturating_sub(1)) as f32 * self.spacing;
let start = vec2(
ctx.center.x - grid_width * 0.5 + self.focus.x,
ctx.center.y - grid_height * 0.5 + self.focus.y * 0.35,
);
for row in 0..self.rows {
let mut line_points = Vec::with_capacity(self.columns);
let row_t = row as f32 / self.rows.max(1) as f32;
for col in 0..self.columns {
let col_t = col as f32 / self.columns.max(1) as f32;
let x = start.x + col as f32 * self.spacing;
let base_y = start.y + row as f32 * self.spacing;
let wave = (ctx.time * self.speed + col_t * 5.2 + row_t * 3.4).sin();
let ripple = (ctx.time * self.speed * 0.7 + row_t * 7.0 - col_t * 3.0).cos();
let mouse_pull = self.focus.length() * 0.12 * (1.0 - (col_t - 0.5).abs());
let y = base_y + wave * self.amplitude + ripple * self.amplitude * 0.35 + mouse_pull;
line_points.push(vec2(x, y));
}
let tint = Color::new(
self.palette.accent_a.r,
self.palette.accent_a.g,
self.palette.accent_a.b,
0.09 + row_t * 0.08,
);
draw_polyline(&line_points, 2.0, tint);
for point in &line_points {
draw_glow_circle(
*point,
2.8 + row_t * 2.0,
Color::new(
self.palette.accent_b.r,
self.palette.accent_b.g,
self.palette.accent_b.b,
0.16,
),
GlowStyle {
rings: 3,
spread: 10.0,
alpha: 0.12,
},
);
draw_circle(point.x, point.y, 1.8 + row_t * 1.2, self.palette.accent_a);
}
}
let horizon = ctx.center.y + self.focus.y * 0.2;
draw_line(
0.0,
horizon,
ctx.width,
horizon,
3.0,
Color::new(
self.palette.glow.r,
self.palette.glow.g,
self.palette.glow.b,
0.22,
),
);
}
}
pub fn liquid_glass() -> LayerScene {
LayerScene::new()
.gradient(color_u8!(7, 14, 29, 255), color_u8!(9, 28, 54, 255))
.mist(color_u8!(118, 238, 255, 255), 220)
.layer(GlassWaveLayer::new(
color_u8!(108, 238, 255, 255),
color_u8!(255, 127, 209, 255),
))
.layer(LiquidOrbLayer::new(Palette::neon_night()))
.scanlines(WHITE)
.vignette(color_u8!(8, 18, 38, 255))
}
pub fn aurora_bloom() -> LayerScene {
let palette = Palette::aurora();
LayerScene::new()
.gradient(color_u8!(6, 10, 22, 255), color_u8!(20, 10, 44, 255))
.halo(palette.glow, 260.0)
.layer(
ParticleMistLayer::new(color_u8!(124, 240, 255, 255))
.with_density(260)
.with_anchor(vec2(0.0, -20.0)),
)
.glass_waves(
color_u8!(92, 184, 255, 255),
color_u8!(162, 134, 255, 255),
GlassWaveConfig::new(28, 10)
.spacing(46.0)
.amplitude(11.0)
.speed(0.55),
)
.liquid_orbs(
LiquidOrbConfig::new(palette)
.orbs(vec![
OrbSpec::new(58.0, 62.0, 0.42, 0.2, 44.0, color_u8!(120, 244, 255, 255)),
OrbSpec::new(102.0, 112.0, -0.24, 2.0, 20.0, color_u8!(255, 136, 194, 255)),
])
.glow_style(GlowStyle {
rings: 6,
spread: 22.0,
alpha: 0.16,
}),
)
.scanlines(color_u8!(210, 230, 255, 255))
.vignette(color_u8!(18, 10, 38, 255))
}
pub fn neon_flow() -> LayerScene {
LayerScene::new()
.gradient(color_u8!(4, 7, 20, 255), color_u8!(4, 22, 42, 255))
.halo(color_u8!(255, 82, 210, 255), 190.0)
.layer(
ParticleMistLayer::new(color_u8!(255, 112, 228, 255))
.with_density(180)
.with_anchor(vec2(10.0, 40.0)),
)
.glass_waves(
color_u8!(0, 255, 214, 255),
color_u8!(255, 78, 185, 255),
GlassWaveConfig::new(26, 14)
.spacing(38.0)
.amplitude(22.0)
.speed(1.15),
)
.liquid_orbs(
LiquidOrbConfig::new(Palette::neon_night())
.orbs(vec![
OrbSpec::new(120.0, 122.0, 0.88, 0.1, 16.0, color_u8!(0, 255, 214, 255)),
OrbSpec::new(160.0, 176.0, -0.62, 1.8, 24.0, color_u8!(255, 78, 185, 255)),
OrbSpec::new(216.0, 228.0, 0.35, 3.1, 14.0, color_u8!(147, 112, 255, 255)),
])
.glow_style(GlowStyle {
rings: 5,
spread: 16.0,
alpha: 0.14,
}),
)
.scanlines(color_u8!(255, 255, 255, 255))
.vignette(color_u8!(5, 11, 29, 255))
}