use macroquad::prelude::*;
use crate::color::Palette;
use crate::primitives::{
GlowStyle, ParticleField, draw_glow_circle, draw_gradient_background, draw_particle_field,
draw_polyline,
};
use crate::scene::{FrameContext, Scene};
pub trait Layer {
fn update(&mut self, ctx: &FrameContext);
fn draw(&self, ctx: &FrameContext);
}
#[derive(Debug, Clone, Copy)]
pub struct OrbSpec {
pub radius: f32,
pub distance: f32,
pub speed: f32,
pub phase: f32,
pub size: f32,
pub color: Color,
}
impl OrbSpec {
pub fn new(radius: f32, distance: f32, speed: f32, phase: f32, size: f32, color: Color) -> Self {
Self {
radius,
distance,
speed,
phase,
size,
color,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct GlassWaveConfig {
pub cols: usize,
pub rows: usize,
pub spacing: f32,
pub amplitude: f32,
pub speed: f32,
}
impl GlassWaveConfig {
pub fn new(cols: usize, rows: usize) -> Self {
Self {
cols,
rows,
spacing: 40.0,
amplitude: 18.0,
speed: 0.9,
}
}
pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing.max(10.0);
self
}
pub fn amplitude(mut self, amplitude: f32) -> Self {
self.amplitude = amplitude.max(1.0);
self
}
pub fn speed(mut self, speed: f32) -> Self {
self.speed = speed.max(0.05);
self
}
}
#[derive(Debug, Clone)]
pub struct LiquidOrbConfig {
pub palette: Palette,
pub orbs: Vec<OrbSpec>,
pub glow_style: GlowStyle,
}
impl LiquidOrbConfig {
pub fn new(palette: Palette) -> Self {
Self {
orbs: vec![
OrbSpec::new(84.0, 90.0, 0.95, 0.0, 18.0, palette.accent_a),
OrbSpec::new(140.0, 145.0, -0.72, 1.4, 24.0, palette.accent_b),
OrbSpec::new(210.0, 205.0, 0.38, 2.3, 13.0, palette.glow),
],
palette,
glow_style: GlowStyle::soft(),
}
}
pub fn orbs(mut self, orbs: Vec<OrbSpec>) -> Self {
self.orbs = orbs;
self
}
pub fn glow_style(mut self, glow_style: GlowStyle) -> Self {
self.glow_style = glow_style;
self
}
}
pub struct LayerScene {
layers: Vec<Box<dyn Layer>>,
}
impl LayerScene {
pub fn new() -> Self {
Self { layers: Vec::new() }
}
pub fn push<L>(mut self, layer: L) -> Self
where
L: Layer + 'static,
{
self.layers.push(Box::new(layer));
self
}
pub fn layer<L>(self, layer: L) -> Self
where
L: Layer + 'static,
{
self.push(layer)
}
pub fn gradient(self, top: Color, bottom: Color) -> Self {
self.push(GradientLayer::new(top, bottom))
}
pub fn mist(self, color: Color, density: usize) -> Self {
self.push(ParticleMistLayer::new(color).with_density(density))
}
pub fn halo(self, color: Color, radius: f32) -> Self {
self.push(HaloLayer::new(color).with_radius(radius))
}
pub fn glass_waves(
self,
tint: Color,
highlight: Color,
config: GlassWaveConfig,
) -> Self {
self.push(
GlassWaveLayer::new(tint, highlight)
.with_grid(config.cols, config.rows, config.spacing)
.with_motion(config.amplitude, config.speed),
)
}
pub fn liquid_orbs(self, config: LiquidOrbConfig) -> Self {
self.push(LiquidOrbLayer::new(config.palette).with_config(config))
}
pub fn scanlines(self, color: Color) -> Self {
self.push(ScanlineLayer::new(color))
}
pub fn vignette(self, color: Color) -> Self {
self.push(VignetteLayer::new(color))
}
}
impl Default for LayerScene {
fn default() -> Self {
Self::new()
}
}
impl Scene for LayerScene {
fn update(&mut self, ctx: &FrameContext) {
for layer in &mut self.layers {
layer.update(ctx);
}
}
fn draw(&self, ctx: &FrameContext) {
for layer in &self.layers {
layer.draw(ctx);
}
}
}
pub struct GradientLayer {
top: Color,
bottom: Color,
steps: usize,
}
impl GradientLayer {
pub fn new(top: Color, bottom: Color) -> Self {
Self {
top,
bottom,
steps: 36,
}
}
pub fn with_steps(mut self, steps: usize) -> Self {
self.steps = steps.max(2);
self
}
}
impl Layer for GradientLayer {
fn update(&mut self, _ctx: &FrameContext) {}
fn draw(&self, ctx: &FrameContext) {
draw_gradient_background(ctx, self.top, self.bottom, self.steps);
}
}
pub struct ParticleMistLayer {
particles: ParticleField,
color: Color,
anchor: Vec2,
parallax: f32,
}
impl ParticleMistLayer {
pub fn new(color: Color) -> Self {
Self {
particles: ParticleField::orbiting(180, 70.0, 420.0),
color,
anchor: vec2(0.0, 24.0),
parallax: 0.14,
}
}
pub fn with_density(mut self, count: usize) -> Self {
self.particles = ParticleField::orbiting(count.max(24), 70.0, 460.0);
self
}
pub fn with_anchor(mut self, anchor: Vec2) -> Self {
self.anchor = anchor;
self
}
}
impl Layer for ParticleMistLayer {
fn update(&mut self, _ctx: &FrameContext) {}
fn draw(&self, ctx: &FrameContext) {
let origin = ctx.center + self.anchor + ctx.mouse_offset * self.parallax;
draw_particle_field(ctx, origin, &self.particles, |_, _, shimmer| {
Color::new(self.color.r, self.color.g, self.color.b, 0.05 + shimmer * 0.16)
});
}
}
pub struct LiquidOrbLayer {
palette: Palette,
orbs: Vec<OrbSpec>,
pulse: f32,
focus: Vec2,
glow_style: GlowStyle,
}
impl LiquidOrbLayer {
pub fn new(palette: Palette) -> Self {
let config = LiquidOrbConfig::new(palette);
Self::from_config(config)
}
pub fn from_config(config: LiquidOrbConfig) -> Self {
Self {
orbs: config.orbs,
palette: config.palette,
pulse: 0.0,
focus: Vec2::ZERO,
glow_style: config.glow_style,
}
}
pub fn with_config(mut self, config: LiquidOrbConfig) -> Self {
self.orbs = config.orbs;
self.glow_style = config.glow_style;
self.palette = config.palette;
self
}
}
impl Layer for LiquidOrbLayer {
fn update(&mut self, ctx: &FrameContext) {
self.pulse += ctx.delta * 1.7;
let target = ctx.mouse_offset * 0.12;
self.focus = self.focus.lerp(target, 1.0 - (-ctx.delta * 3.5).exp());
}
fn draw(&self, ctx: &FrameContext) {
let base = vec2(
ctx.center.x + (ctx.time * 0.34).sin() * 26.0 + self.focus.x,
ctx.center.y + (ctx.time * 0.22).cos() * 18.0 + self.focus.y,
);
let core_radius = 84.0 + self.pulse.sin() * 9.0;
draw_glow_circle(base, core_radius, self.palette.glow, self.glow_style);
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.14,
),
);
for orb in &self.orbs {
let mut trail = Vec::with_capacity(22);
for segment in 0..22 {
let t = segment as f32 / 21.0;
let angle = (ctx.time - t * 0.58) * orb.speed + orb.phase;
trail.push(
base + vec2(
angle.cos() * orb.distance,
angle.sin() * orb.radius + (ctx.time * 1.2 + orb.phase - t).sin() * 18.0,
),
);
}
for (index, pair) in trail.windows(2).enumerate() {
let t = index as f32 / 20.0;
draw_polyline(
pair,
2.4 - t * 1.6,
Color::new(orb.color.r, orb.color.g, orb.color.b, (1.0 - t) * 0.2),
);
}
let angle = ctx.time * orb.speed + orb.phase;
let pos = base
+ vec2(
angle.cos() * orb.distance,
angle.sin() * orb.radius + (ctx.time * 1.2 + orb.phase).sin() * 18.0,
);
draw_glow_circle(pos, orb.size, orb.color, self.glow_style);
draw_circle(pos.x, pos.y, orb.size, orb.color);
}
}
}
pub struct GlassWaveLayer {
tint: Color,
highlight: Color,
rows: usize,
cols: usize,
spacing: f32,
amplitude: f32,
speed: f32,
focus: Vec2,
}
impl GlassWaveLayer {
pub fn new(tint: Color, highlight: Color) -> Self {
Self {
tint,
highlight,
rows: 12,
cols: 24,
spacing: 40.0,
amplitude: 18.0,
speed: 0.9,
focus: Vec2::ZERO,
}
}
pub fn with_grid(mut self, cols: usize, rows: usize, spacing: f32) -> Self {
self.cols = cols.max(4);
self.rows = rows.max(3);
self.spacing = spacing.max(10.0);
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 Layer for GlassWaveLayer {
fn update(&mut self, ctx: &FrameContext) {
let target = ctx.mouse_offset * 0.08;
self.focus = self.focus.lerp(target, 1.0 - (-ctx.delta * 4.0).exp());
}
fn draw(&self, ctx: &FrameContext) {
let width = (self.cols.saturating_sub(1)) as f32 * self.spacing;
let height = (self.rows.saturating_sub(1)) as f32 * self.spacing;
let start = vec2(
ctx.center.x - width * 0.5 + self.focus.x,
ctx.center.y - height * 0.1 + self.focus.y * 0.3,
);
for row in 0..self.rows {
let row_t = row as f32 / self.rows.max(1) as f32;
let mut points = Vec::with_capacity(self.cols);
for col in 0..self.cols {
let col_t = col as f32 / self.cols.max(1) as f32;
let x = start.x + col as f32 * self.spacing;
let y = start.y
+ row as f32 * self.spacing
+ (ctx.time * self.speed + col_t * 5.4 + row_t * 3.0).sin() * self.amplitude
+ (ctx.time * self.speed * 0.65 + row_t * 7.0 - col_t * 2.8).cos()
* self.amplitude
* 0.45;
points.push(vec2(x, y));
}
draw_polyline(
&points,
1.9,
Color::new(self.tint.r, self.tint.g, self.tint.b, 0.06 + row_t * 0.07),
);
for point in &points {
draw_glow_circle(
*point,
2.6 + row_t * 1.4,
Color::new(
self.highlight.r,
self.highlight.g,
self.highlight.b,
0.12,
),
GlowStyle {
rings: 3,
spread: 9.0,
alpha: 0.11,
},
);
}
}
}
}
pub struct VignetteLayer {
color: Color,
height: f32,
alpha: f32,
}
impl VignetteLayer {
pub fn new(color: Color) -> Self {
Self {
color,
height: 42.0,
alpha: 0.18,
}
}
}
impl Layer for VignetteLayer {
fn update(&mut self, _ctx: &FrameContext) {}
fn draw(&self, ctx: &FrameContext) {
let band = Color::new(self.color.r, self.color.g, self.color.b, self.alpha);
draw_rectangle(0.0, 0.0, ctx.width, self.height, band);
draw_rectangle(0.0, ctx.height - self.height, ctx.width, self.height, band);
}
}
pub struct HaloLayer {
color: Color,
radius: f32,
drift: f32,
parallax: f32,
}
impl HaloLayer {
pub fn new(color: Color) -> Self {
Self {
color,
radius: 220.0,
drift: 0.0,
parallax: 0.1,
}
}
pub fn with_radius(mut self, radius: f32) -> Self {
self.radius = radius.max(10.0);
self
}
}
impl Layer for HaloLayer {
fn update(&mut self, ctx: &FrameContext) {
self.drift += ctx.delta * 0.25;
}
fn draw(&self, ctx: &FrameContext) {
let center = vec2(
ctx.center.x + (ctx.time * 0.2 + self.drift).sin() * 90.0 + ctx.mouse_offset.x * self.parallax,
ctx.center.y - ctx.height * 0.18 + (ctx.time * 0.14).cos() * 24.0,
);
draw_glow_circle(
center,
self.radius,
Color::new(self.color.r, self.color.g, self.color.b, 0.14),
GlowStyle {
rings: 6,
spread: 24.0,
alpha: 0.12,
},
);
}
}
pub struct ScanlineLayer {
color: Color,
lines: usize,
drift: f32,
}
impl ScanlineLayer {
pub fn new(color: Color) -> Self {
Self {
color,
lines: 18,
drift: 0.0,
}
}
}
impl Layer for ScanlineLayer {
fn update(&mut self, ctx: &FrameContext) {
self.drift += ctx.delta * 0.35;
}
fn draw(&self, ctx: &FrameContext) {
for line_idx in 0..self.lines {
let t = line_idx as f32 / self.lines.saturating_sub(1).max(1) as f32;
let y = ctx.height * t;
let alpha = 0.018 + 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,
1.4,
Color::new(self.color.r, self.color.g, self.color.b, alpha),
);
}
}
}