rae 0.1.11

Renderer-neutral widget, layout, sanitization, and GLSL shader primitives for Rust desktop tools.
Documentation
#![allow(dead_code)]

use std::env;

use macroquad::prelude::*;
use rae::shader;
use rae::{Palette, Rect, Rgba};

pub fn max_frames_from_env() -> Option<u64> {
    env::var("RAE_RENDER_DEMO_FRAMES")
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
}

pub fn should_stop(frames: u64, max_frames: Option<u64>) -> bool {
    is_key_pressed(KeyCode::Escape) || max_frames.is_some_and(|max_frames| frames >= max_frames)
}

pub fn to_mq(color: Rgba) -> Color {
    Color::new(color.r, color.g, color.b, color.a)
}

pub fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Color {
    to_mq(Rgba::rgba8(r, g, b, a))
}

pub fn draw_scene_backdrop(palette: &Palette, time: f32) {
    clear_background(to_mq(palette.background));
    let width = screen_width();
    let height = screen_height();
    for index in 0..28 {
        let y = index as f32 * 42.0 + (time * 7.0).sin() * 4.0;
        let alpha = if index % 3 == 0 { 0.040 } else { 0.022 };
        draw_line(
            0.0,
            y,
            width,
            y + (time * 0.8 + index as f32).sin() * 16.0,
            1.0,
            to_mq(palette.accent.with_alpha(alpha)),
        );
    }
    for index in 0..22 {
        let x = index as f32 * 76.0 + (time * 5.0).cos() * 6.0;
        draw_line(
            x,
            0.0,
            x + (time * 0.6 + index as f32).cos() * 18.0,
            height,
            1.0,
            rgba8(87, 118, 190, 11),
        );
    }

    draw_glow(
        width * (0.24 + (time * 0.07).sin() * 0.04),
        height * 0.18,
        280.0,
        Rgba::rgb8(93, 144, 255).with_alpha(0.18),
        8,
    );
    draw_glow(
        width * 0.82,
        height * (0.72 + (time * 0.09).cos() * 0.05),
        380.0,
        Rgba::rgb8(91, 255, 211).with_alpha(0.13),
        10,
    );
    draw_glow(
        width * (0.58 + (time * 0.05).cos() * 0.05),
        height * 0.42,
        260.0,
        Rgba::rgb8(190, 94, 255).with_alpha(0.10),
        7,
    );
}

pub fn load_rae_material(source: shader::ShaderSource) -> Option<Material> {
    let uniforms = source
        .uniforms
        .iter()
        .map(|uniform| {
            let kind = match uniform.kind {
                shader::ShaderUniformKind::Float1 => UniformType::Float1,
                shader::ShaderUniformKind::Float2 => UniformType::Float2,
            };
            UniformDesc::new(uniform.name, kind)
        })
        .collect();

    load_material(
        ShaderSource::Glsl {
            vertex: source.vertex,
            fragment: source.fragment,
        },
        MaterialParams {
            uniforms,
            ..Default::default()
        },
    )
    .ok()
}

pub fn draw_shader_rect(
    material: Option<&Material>,
    rect: Rect,
    time: f32,
    intensity: f32,
    resolution: Vec2,
) {
    if let Some(material) = material {
        material.set_uniform("u_time", time);
        material.set_uniform("u_resolution", resolution);
        material.set_uniform("u_intensity", intensity);
        gl_use_material(material);
        draw_rectangle(rect.x, rect.y, rect.width, rect.height, WHITE);
        gl_use_default_material();
    }
}

pub fn draw_glow(x: f32, y: f32, radius: f32, color: Rgba, layers: usize) {
    for index in (0..layers).rev() {
        let t = (index + 1) as f32 / layers.max(1) as f32;
        let alpha = color.a * (1.0 - t).powi(2) * 0.8;
        draw_circle(x, y, radius * t, to_mq(color.with_alpha(alpha)));
    }
}

pub fn draw_shadow(rect: Rect, radius: f32, strength: f32) {
    for index in 0..8 {
        let offset = 2.0 + index as f32 * 2.0;
        let spread = index as f32 * 2.2;
        let shadow = Rect::new(
            rect.x - spread,
            rect.y + offset - spread,
            rect.width + spread * 2.0,
            rect.height + spread * 2.0,
        );
        draw_rounded_rect(
            shadow,
            radius + spread,
            Color::new(
                0.0,
                0.0,
                0.0,
                strength * (0.12 - index as f32 * 0.011).max(0.0),
            ),
        );
    }
}

pub fn draw_glass_panel(rect: Rect, radius: f32, palette: &Palette, accent: Rgba, focused: bool) {
    draw_shadow(rect, radius, if focused { 1.2 } else { 0.82 });
    draw_rounded_rect(
        rect,
        radius,
        to_mq(palette.panel.with_alpha(if focused { 0.74 } else { 0.64 })),
    );
    let inner = rect.inset(rae::Insets::all(1.0));
    draw_rounded_rect(
        inner,
        (radius - 1.0).max(0.0),
        to_mq(
            palette
                .panel_hot
                .with_alpha(if focused { 0.22 } else { 0.13 }),
        ),
    );
    draw_rectangle(
        rect.x + radius,
        rect.y + 1.0,
        (rect.width - radius * 2.0).max(0.0),
        1.0,
        to_mq(Rgba::WHITE.with_alpha(0.16)),
    );
    draw_rectangle_lines(
        rect.x,
        rect.y,
        rect.width,
        rect.height,
        if focused { 1.7 } else { 1.0 },
        to_mq(accent.with_alpha(if focused { 0.70 } else { 0.34 })),
    );
}

pub fn draw_section_header(label: &str, rect: Rect, accent: Rgba, muted: Rgba) {
    draw_text_line(label, rect.x, rect.y, 12.0, to_mq(accent.with_alpha(0.90)));
    draw_line(
        rect.x + 92.0,
        rect.y - 4.0,
        rect.right(),
        rect.y - 4.0,
        1.0,
        to_mq(muted.with_alpha(0.23)),
    );
}

pub fn draw_pill(rect: Rect, label: &str, color: Rgba, text: Rgba) {
    draw_rounded_rect(rect, rect.height * 0.5, to_mq(color.with_alpha(0.17)));
    draw_rectangle_lines(
        rect.x,
        rect.y,
        rect.width,
        rect.height,
        1.0,
        to_mq(color.with_alpha(0.42)),
    );
    draw_text_line(
        label,
        rect.x + 12.0,
        rect.y + rect.height * 0.64,
        13.0,
        to_mq(text),
    );
}

pub fn draw_metric(rect: Rect, label: &str, value: &str, color: Rgba, palette: &Palette) {
    draw_rounded_rect(rect, 16.0, to_mq(palette.background.with_alpha(0.42)));
    draw_rectangle_lines(
        rect.x,
        rect.y,
        rect.width,
        rect.height,
        1.0,
        to_mq(color.with_alpha(0.32)),
    );
    draw_text_line(
        label,
        rect.x + 14.0,
        rect.y + 22.0,
        12.0,
        to_mq(palette.muted),
    );
    draw_text_line(value, rect.x + 14.0, rect.y + 51.0, 24.0, to_mq(color));
}

pub fn draw_text_line(text: &str, x: f32, y: f32, size: f32, color: Color) {
    draw_text_ex(
        text,
        x,
        y,
        TextParams {
            font_size: size as u16,
            color,
            ..Default::default()
        },
    );
}

pub fn draw_wrapped_text(
    text: &str,
    x: f32,
    mut y: f32,
    width: f32,
    size: f32,
    line_height: f32,
    color: Color,
) -> f32 {
    for source_line in text.lines() {
        let mut line = String::new();
        for word in source_line.split_whitespace() {
            let candidate = if line.is_empty() {
                word.to_string()
            } else {
                format!("{line} {word}")
            };
            if measure_text(&candidate, None, size as u16, 1.0).width > width && !line.is_empty() {
                draw_text_line(&line, x, y, size, color);
                y += line_height;
                line.clear();
                line.push_str(word);
            } else {
                line = candidate;
            }
        }
        if !line.is_empty() {
            draw_text_line(&line, x, y, size, color);
            y += line_height;
        }
    }
    y
}

pub fn draw_rounded_rect(rect: Rect, radius: f32, color: Color) {
    if rect.width <= 0.0 || rect.height <= 0.0 {
        return;
    }
    let radius = radius.min(rect.width * 0.5).min(rect.height * 0.5).max(0.0);
    draw_rectangle(
        rect.x + radius,
        rect.y,
        (rect.width - radius * 2.0).max(0.0),
        rect.height,
        color,
    );
    draw_rectangle(
        rect.x,
        rect.y + radius,
        rect.width,
        (rect.height - radius * 2.0).max(0.0),
        color,
    );
    draw_circle(rect.x + radius, rect.y + radius, radius, color);
    draw_circle(rect.right() - radius, rect.y + radius, radius, color);
    draw_circle(rect.x + radius, rect.bottom() - radius, radius, color);
    draw_circle(rect.right() - radius, rect.bottom() - radius, radius, color);
}