rae 0.1.11

Renderer-neutral widget, layout, sanitization, and GLSL shader primitives for Rust desktop tools.
Documentation
mod support;

use macroquad::prelude::*;
use rae::{MorphCacheKey, MorphProfile, MorphShape, Palette, Rect, Rgba, Vec2};
use support::*;

fn window_conf() -> Conf {
    Conf {
        window_title: "rae morph surfaces".to_string(),
        window_width: 1440,
        window_height: 900,
        high_dpi: true,
        sample_count: 4,
        ..Default::default()
    }
}

#[macroquad::main(window_conf)]
async fn main() {
    let palette = Palette::midnight();
    let transitions = transitions();
    let max_frames = max_frames_from_env();
    let mut frames = 0u64;

    loop {
        frames = frames.saturating_add(1);
        if should_stop(frames, max_frames) {
            break;
        }

        let time = get_time() as f32;
        let viewport = Rect::new(0.0, 0.0, screen_width(), screen_height());
        draw_scene_backdrop(&palette, time);
        draw_header(viewport, &palette);
        draw_morph_grid(viewport, &palette, &transitions, time);

        next_frame().await;
    }
}

fn transitions() -> [(MorphShape, MorphShape, Rgba); 6] {
    [
        (
            MorphShape::RoundedCard,
            MorphShape::Blob,
            Rgba::rgb8(122, 185, 255),
        ),
        (
            MorphShape::Capsule,
            MorphShape::Ticket,
            Rgba::rgb8(83, 225, 147),
        ),
        (
            MorphShape::DockIsland,
            MorphShape::CommandLens,
            Rgba::rgb8(255, 190, 94),
        ),
        (
            MorphShape::NotchedPanel,
            MorphShape::RoundedCard,
            Rgba::rgb8(205, 130, 255),
        ),
        (
            MorphShape::Ticket,
            MorphShape::DockIsland,
            Rgba::rgb8(255, 126, 171),
        ),
        (
            MorphShape::Blob,
            MorphShape::Capsule,
            Rgba::rgb8(91, 255, 211),
        ),
    ]
}

fn draw_header(viewport: Rect, palette: &Palette) {
    let header = Rect::new(28.0, 24.0, viewport.width - 56.0, 74.0);
    draw_glass_panel(header, 26.0, palette, palette.accent, true);
    draw_text_line(
        "Morph Surfaces",
        header.x + 26.0,
        header.y + 42.0,
        31.0,
        to_mq(palette.text),
    );
    draw_text_line(
        "topology descriptors animated into card-like surface approximations",
        header.x + 294.0,
        header.y + 40.0,
        17.0,
        to_mq(palette.muted),
    );
    draw_pill(
        Rect::new(header.right() - 236.0, header.y + 20.0, 214.0, 32.0),
        "SDF data model",
        palette.accent,
        palette.text,
    );
}

fn draw_morph_grid(
    viewport: Rect,
    palette: &Palette,
    transitions: &[(MorphShape, MorphShape, Rgba)],
    time: f32,
) {
    let grid = Rect::new(28.0, 126.0, viewport.width - 56.0, viewport.height - 154.0);
    let columns = if viewport.width < 900.0 { 2 } else { 3 };
    let rows = transitions.len().div_ceil(columns);
    let gap = 20.0;
    let card_w = (grid.width - gap * (columns - 1) as f32) / columns as f32;
    let card_h = (grid.height - gap * (rows - 1) as f32) / rows as f32;

    for (index, (from, to, accent)) in transitions.iter().enumerate() {
        let rect = Rect::new(
            grid.x + (index % columns) as f32 * (card_w + gap),
            grid.y + (index / columns) as f32 * (card_h + gap),
            card_w,
            card_h,
        );
        draw_morph_card(
            rect,
            palette,
            *from,
            *to,
            *accent,
            time + index as f32 * 0.22,
        );
    }
}

fn draw_morph_card(
    rect: Rect,
    palette: &Palette,
    from: MorphShape,
    to: MorphShape,
    accent: Rgba,
    time: f32,
) {
    draw_glass_panel(rect, 28.0, palette, accent, true);
    let profile = MorphProfile::material_default(from, to);
    let elapsed = ((time * 420.0).sin() * 0.5 + 0.5) * 680.0;
    let state = profile.sample(elapsed.round() as u32);
    let key = MorphCacheKey::new(from, to, Vec2::new(rect.width, rect.height), state.progress);

    draw_text_line(
        &format!("{from:?} -> {to:?}"),
        rect.x + 20.0,
        rect.y + 30.0,
        17.0,
        to_mq(palette.text),
    );
    draw_text_line(
        &format!(
            "bucket {}  anchor {:?}",
            key.frame_bucket, state.topology.anchor
        ),
        rect.x + 20.0,
        rect.y + 53.0,
        13.0,
        to_mq(palette.muted),
    );

    let stage = Rect::new(
        rect.x + 28.0,
        rect.y + 82.0,
        rect.width - 56.0,
        rect.height * 0.42,
    );
    draw_rounded_rect(stage, 24.0, rgba8(1, 5, 16, 150));
    draw_topology_shape(stage, palette, accent, state.topology, state.progress);

    let metric_y = stage.bottom() + 22.0;
    draw_bar(
        rect.x + 24.0,
        metric_y,
        rect.width - 48.0,
        "progress",
        state.progress,
        accent,
        palette,
    );
    draw_bar(
        rect.x + 24.0,
        metric_y + 34.0,
        rect.width - 48.0,
        "corner",
        (state.topology.corner_radius / 80.0).min(1.0),
        palette.accent,
        palette,
    );
    draw_bar(
        rect.x + 24.0,
        metric_y + 68.0,
        rect.width - 48.0,
        "blob",
        state.topology.blob_strength,
        palette.success,
        palette,
    );
}

fn draw_topology_shape(
    stage: Rect,
    palette: &Palette,
    accent: Rgba,
    topology: rae::CardTopology,
    progress: f32,
) {
    let pulse = 0.94 + progress * 0.06;
    let w = stage.width * (0.66 + topology.aspect_pull * 0.18) * pulse;
    let h = stage.height * (0.48 + topology.blob_strength * 0.10);
    let cx = stage.x + stage.width * 0.5;
    let cy = stage.y + stage.height * 0.5;
    let shape = Rect::new(cx - w * 0.5, cy - h * 0.5, w, h);
    let radius = topology.corner_radius.min(h * 0.5).max(8.0);

    draw_glow(
        cx,
        cy,
        120.0 + topology.edge_wave * 3.0,
        accent.with_alpha(0.15 + topology.blob_strength * 0.08),
        7,
    );
    draw_rounded_rect(
        shape,
        radius,
        to_mq(accent.with_alpha(0.22 + progress * 0.16)),
    );
    draw_rectangle_lines(
        shape.x,
        shape.y,
        shape.width,
        shape.height,
        1.4,
        to_mq(accent.with_alpha(0.72)),
    );

    if topology.notch_depth > 1.0 {
        let notch_r = topology.notch_depth.min(22.0);
        draw_circle(
            shape.x,
            shape.y + shape.height * 0.5,
            notch_r,
            to_mq(palette.background.with_alpha(0.92)),
        );
        draw_circle(
            shape.right(),
            shape.y + shape.height * 0.5,
            notch_r,
            to_mq(palette.background.with_alpha(0.92)),
        );
    }
    if topology.edge_wave > 1.0 || topology.blob_strength > 0.1 {
        for index in 0..9 {
            let t = index as f32 / 8.0;
            let x = shape.x + shape.width * t;
            let offset = (t * std::f32::consts::TAU * 2.0 + progress * 4.0).sin()
                * topology.edge_wave
                * 0.32;
            draw_circle(
                x,
                shape.y + offset,
                3.0 + topology.blob_strength * 4.0,
                to_mq(accent.with_alpha(0.42)),
            );
            draw_circle(
                x,
                shape.bottom() - offset,
                3.0 + topology.blob_strength * 4.0,
                to_mq(accent.with_alpha(0.32)),
            );
        }
    }
}

fn draw_bar(x: f32, y: f32, width: f32, label: &str, value: f32, color: Rgba, palette: &Palette) {
    draw_text_line(label, x, y + 12.0, 12.0, to_mq(palette.muted));
    let track = Rect::new(x + 72.0, y, width - 72.0, 16.0);
    draw_rounded_rect(track, 8.0, rgba8(1, 5, 16, 150));
    draw_rounded_rect(
        Rect::new(
            track.x,
            track.y,
            track.width * value.clamp(0.0, 1.0),
            track.height,
        ),
        8.0,
        to_mq(color.with_alpha(0.58)),
    );
}