#![allow(dead_code)]
use std::{cell::OnceCell, env, fs, path::Path};
use macroquad::prelude::*;
use rae::shader;
use rae::{lerp, Palette, Rect, Rgba};
thread_local! {
static DEMO_FONT: OnceCell<Option<&'static Font>> = const { OnceCell::new() };
static DEMO_MONO_FONT: OnceCell<Option<&'static Font>> = const { OnceCell::new() };
}
pub fn max_frames_from_env() -> Option<u64> {
parse_positive_u64_env("RAE_RENDER_DEMO_FRAMES")
}
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 maybe_save_screenshot(example_name: &str, frames: u64) {
let Ok(dir) = env::var("RAE_RENDER_SCREENSHOT_DIR") else {
return;
};
let target_frame = parse_positive_u64_env("RAE_RENDER_SCREENSHOT_FRAME").unwrap_or(1);
if frames != target_frame {
return;
}
if let Err(error) = fs::create_dir_all(&dir) {
eprintln!("failed to create screenshot output directory {dir}: {error}");
return;
}
let path = Path::new(&dir).join(format!("{example_name}.png"));
let path = path.to_string_lossy().to_string();
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
get_screen_data().export_png(&path);
}))
.is_err()
{
eprintln!("failed to export screenshot to {path}");
}
}
fn load_demo_font() -> Option<&'static Font> {
let candidates = [
"/usr/share/fonts/liberation/LiberationSans-Regular.ttf",
"/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Regular.otf",
"/usr/share/fonts/liberation/LiberationMono-Regular.ttf",
];
for path in candidates {
if let Ok(bytes) = fs::read(path) {
if let Ok(font) = load_ttf_font_from_bytes(&bytes) {
return Some(Box::leak(Box::new(font)));
}
}
}
None
}
fn load_demo_mono_font() -> Option<&'static Font> {
let candidates = [
"/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Regular.otf",
"/usr/share/fonts/liberation/LiberationMono-Regular.ttf",
"/usr/share/fonts/liberation/LiberationSans-Regular.ttf",
];
for path in candidates {
if let Ok(bytes) = fs::read(path) {
if let Ok(font) = load_ttf_font_from_bytes(&bytes) {
return Some(Box::leak(Box::new(font)));
}
}
}
None
}
fn with_demo_font<T>(render: impl FnOnce(Option<&Font>) -> T) -> T {
DEMO_FONT.with(|font| {
let font = font.get_or_init(load_demo_font).as_ref().map(|font| *font);
render(font)
})
}
fn with_demo_mono_font<T>(render: impl FnOnce(Option<&Font>) -> T) -> T {
DEMO_MONO_FONT.with(|font| {
let font = font
.get_or_init(load_demo_mono_font)
.as_ref()
.map(|font| *font);
render(font)
})
}
pub fn to_mq(color: Rgba) -> Color {
Color::new(
clamp_channel(color.r),
clamp_channel(color.g),
clamp_channel(color.b),
clamp_channel(color.a),
)
}
fn clamp_channel(value: f32) -> f32 {
if value.is_finite() {
value.clamp(0.0, 1.0)
} else {
0.0
}
}
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> {
if !live_shaders_enabled() {
return None;
}
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();
match load_material(
ShaderSource::Glsl {
vertex: source.vertex,
fragment: source.fragment,
},
MaterialParams {
uniforms,
..Default::default()
},
) {
Ok(material) => Some(material),
Err(error) => {
eprintln!(
"failed to load live shader material {}: {error}",
source.name
);
None
}
}
}
fn parse_positive_u64_env(name: &str) -> Option<u64> {
let value = env::var(name).ok()?;
match value.parse::<u64>() {
Ok(parsed) if parsed > 0 => Some(parsed),
_ => {
eprintln!("invalid {name}={value:?}; using 1");
Some(1)
}
}
}
pub fn draw_shader_rect(
material: Option<&Material>,
rect: Rect,
time: f32,
intensity: f32,
resolution: Vec2,
) {
draw_shader_rect_variant(
material,
rect,
time,
intensity,
resolution,
Rgba::rgb8(74, 204, 255),
ShaderPreviewKind::Nebula,
);
}
pub fn draw_shader_rect_with_accent(
material: Option<&Material>,
rect: Rect,
time: f32,
intensity: f32,
resolution: Vec2,
accent: Rgba,
) {
draw_shader_rect_variant(
material,
rect,
time,
intensity,
resolution,
accent,
ShaderPreviewKind::Nebula,
);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ShaderPreviewKind {
Nebula,
Lattice,
Beam,
Ripple,
Code,
Flow,
Crystal,
Aurora,
Knot,
Bloom,
Lens,
Graph,
Reaction,
Mesh,
CardMorph,
Metaball,
Aura,
GridPulse,
Spotlight,
Glitch,
Waves,
Chrome,
Caustic,
Vortex,
Storm,
Fireworks,
}
pub fn draw_shader_rect_variant(
material: Option<&Material>,
rect: Rect,
time: f32,
intensity: f32,
resolution: Vec2,
accent: Rgba,
kind: ShaderPreviewKind,
) {
draw_shader_base(rect, intensity, accent);
match kind {
ShaderPreviewKind::Nebula => draw_preview_nebula(rect, time, intensity, accent),
ShaderPreviewKind::Lattice => draw_preview_lattice(rect, time, accent),
ShaderPreviewKind::Beam => draw_preview_beams(rect, time, accent),
ShaderPreviewKind::Ripple => draw_preview_ripples(rect, time, accent),
ShaderPreviewKind::Code => draw_preview_code(rect, time, accent),
ShaderPreviewKind::Flow => draw_preview_flow(rect, time, accent),
ShaderPreviewKind::Crystal => draw_preview_crystal(rect, time, accent),
ShaderPreviewKind::Aurora => draw_preview_aurora(rect, time, accent),
ShaderPreviewKind::Knot => draw_preview_knot(rect, time, accent),
ShaderPreviewKind::Bloom => draw_preview_bloom(rect, time, accent),
ShaderPreviewKind::Lens => draw_preview_lens(rect, time, accent),
ShaderPreviewKind::Graph => draw_preview_graph(rect, time, accent),
ShaderPreviewKind::Reaction => draw_preview_reaction(rect, time, accent),
ShaderPreviewKind::Mesh => draw_preview_mesh(rect, time, accent),
ShaderPreviewKind::CardMorph => draw_preview_card_morph(rect, time, accent),
ShaderPreviewKind::Metaball => draw_preview_metaball(rect, time, accent),
ShaderPreviewKind::Aura => draw_preview_aura(rect, time, accent),
ShaderPreviewKind::GridPulse => draw_preview_grid_pulse(rect, time, accent),
ShaderPreviewKind::Spotlight => draw_preview_spotlight(rect, time, accent),
ShaderPreviewKind::Glitch => draw_preview_glitch(rect, time, accent),
ShaderPreviewKind::Waves => draw_preview_waves(rect, time, accent),
ShaderPreviewKind::Chrome => draw_preview_chrome(rect, time, accent),
ShaderPreviewKind::Caustic => draw_preview_caustic(rect, time, accent),
ShaderPreviewKind::Vortex => draw_preview_vortex(rect, time, accent),
ShaderPreviewKind::Storm => draw_preview_storm(rect, time, accent),
ShaderPreviewKind::Fireworks => draw_preview_fireworks(rect, time, accent),
}
if live_shaders_enabled() {
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();
draw_rectangle(
rect.x,
rect.y,
rect.width,
rect.height,
rgba8(2, 6, 18, 146),
);
}
}
draw_shader_scanlines(rect, time, accent);
}
pub fn live_shaders_enabled() -> bool {
env::var("RAE_RENDER_LIVE_SHADERS")
.ok()
.is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "YES"))
}
fn draw_shader_base(rect: Rect, intensity: f32, accent: Rgba) {
draw_rectangle(
rect.x,
rect.y,
rect.width,
rect.height,
rgba8(2, 5, 16, 255),
);
draw_rectangle(
rect.x,
rect.y,
rect.width,
rect.height,
to_mq(accent.with_alpha(0.030 + intensity.clamp(0.0, 1.0) * 0.020)),
);
}
fn draw_preview_nebula(rect: Rect, time: f32, intensity: f32, accent: Rgba) {
let primary = accent.with_alpha(0.18 + intensity.clamp(0.0, 1.0) * 0.12);
let violet = accent.mix(Rgba::rgb8(190, 82, 255), 0.66).with_alpha(0.14);
let amber = Rgba::rgb8(255, 185, 96).with_alpha(0.10);
draw_glow(
rect.x + rect.width * (0.28 + (time * 0.17).sin() * 0.05),
rect.y + rect.height * 0.42,
rect.width * 0.36,
primary,
5,
);
draw_glow(
rect.x + rect.width * 0.76,
rect.y + rect.height * (0.58 + (time * 0.11).cos() * 0.06),
rect.width * 0.30,
violet,
5,
);
draw_glow(
rect.x + rect.width * 0.52,
rect.y + rect.height * 0.28,
rect.width * 0.18,
amber,
4,
);
for index in 0..10 {
let t = index as f32 / 9.0;
let y = rect.y + rect.height * (0.14 + t * 0.72);
let wave = (time * 0.9 + t * 7.0).sin() * rect.height * 0.06;
draw_line(
rect.x + rect.width * 0.08,
y + wave,
rect.right() - rect.width * 0.08,
y - wave * 0.45,
1.0 + t * 0.8,
to_mq(primary.with_alpha(0.05 + t * 0.035)),
);
}
}
fn draw_preview_lattice(rect: Rect, time: f32, accent: Rgba) {
let cell = (rect.height / 5.2).max(18.0);
let wobble = (time * 0.7).sin() * 2.0;
let mut y = rect.y + cell * 0.75;
let mut row = 0usize;
while y < rect.bottom() - cell * 0.35 {
let offset = if row % 2 == 0 { 0.0 } else { cell * 0.5 };
let mut x = rect.x + cell * 0.55 + offset;
while x < rect.right() - cell * 0.35 {
let pulse = ((time * 1.3 + x * 0.015 + y * 0.021).sin() * 0.5 + 0.5) * 0.18;
draw_poly_lines(
x,
y + wobble,
6,
cell * 0.38,
0.0,
1.0,
to_mq(accent.with_alpha(0.11 + pulse)),
);
x += cell;
}
y += cell * 0.78;
row += 1;
}
draw_glow(
rect.x + rect.width * 0.46,
rect.y + rect.height * 0.48,
rect.width * 0.28,
accent.with_alpha(0.12),
5,
);
}
fn draw_preview_beams(rect: Rect, time: f32, accent: Rgba) {
for index in 0..7 {
let t = index as f32 / 6.0;
let y = rect.y + rect.height * (0.20 + t * 0.58);
let phase = time * (1.1 + t * 0.4) + t * 8.0;
let width = rect.width * (0.40 + phase.sin().abs() * 0.32);
let x = rect.x + rect.width * (0.10 + (phase * 0.41).cos().abs() * 0.18);
draw_rectangle(
x,
y - 2.0,
width,
4.0,
to_mq(accent.with_alpha(0.09 + t * 0.035)),
);
draw_line(
x,
y,
(x + width).min(rect.right() - 10.0),
y + phase.sin() * 8.0,
1.4,
to_mq(Rgba::WHITE.with_alpha(0.07)),
);
}
let caret_x = rect.x + rect.width * (0.16 + (time * 0.37).sin() * 0.10 + 0.34);
draw_rectangle(
caret_x,
rect.y + rect.height * 0.18,
2.0,
rect.height * 0.64,
to_mq(accent.with_alpha(0.42)),
);
}
fn draw_preview_ripples(rect: Rect, time: f32, accent: Rgba) {
let origins = [
vec2(rect.x + rect.width * 0.28, rect.y + rect.height * 0.45),
vec2(rect.x + rect.width * 0.68, rect.y + rect.height * 0.58),
];
for (origin_index, origin) in origins.iter().enumerate() {
for ring in 0..5 {
let progress = (time * 0.28 + ring as f32 * 0.18 + origin_index as f32 * 0.32).fract();
let radius = rect.width * (0.05 + progress * 0.34);
let alpha = (1.0 - progress) * 0.20;
draw_circle_lines(
origin.x,
origin.y,
radius,
1.4,
to_mq(accent.with_alpha(alpha)),
);
}
draw_circle(origin.x, origin.y, 6.0, to_mq(accent.with_alpha(0.40)));
}
}
fn draw_preview_code(rect: Rect, time: f32, accent: Rgba) {
let columns = 14;
for col in 0..columns {
let x = rect.x + rect.width * (0.08 + col as f32 / columns as f32 * 0.84);
let speed = 0.35 + col as f32 * 0.017;
for row in 0..8 {
let y = rect.y
+ ((row as f32 * 18.0 + time * 42.0 * speed + col as f32 * 7.0) % rect.height);
let hot = ((time + col as f32 * 0.31 + row as f32 * 0.21).sin() * 0.5 + 0.5) * 0.22;
draw_rectangle(
x,
y,
2.0 + (col % 3) as f32,
8.0,
to_mq(accent.with_alpha(0.09 + hot)),
);
}
}
draw_rectangle(
rect.x + rect.width * 0.08,
rect.y + rect.height * 0.62,
rect.width * 0.74,
1.0,
to_mq(Rgba::WHITE.with_alpha(0.14)),
);
}
fn draw_preview_flow(rect: Rect, time: f32, accent: Rgba) {
let nodes = [
vec2(rect.x + rect.width * 0.14, rect.y + rect.height * 0.68),
vec2(rect.x + rect.width * 0.34, rect.y + rect.height * 0.32),
vec2(rect.x + rect.width * 0.56, rect.y + rect.height * 0.54),
vec2(rect.x + rect.width * 0.82, rect.y + rect.height * 0.24),
];
for pair in nodes.windows(2) {
draw_line(
pair[0].x,
pair[0].y,
pair[1].x,
pair[1].y,
2.0,
to_mq(accent.with_alpha(0.24)),
);
}
for (index, node) in nodes.iter().enumerate() {
let pulse = (time * 1.6 + index as f32).sin() * 0.5 + 0.5;
draw_glow(
node.x,
node.y,
28.0 + pulse * 18.0,
accent.with_alpha(0.13),
4,
);
draw_circle(
node.x,
node.y,
5.0 + pulse * 3.0,
to_mq(accent.with_alpha(0.48)),
);
}
}
fn draw_preview_aurora(rect: Rect, time: f32, accent: Rgba) {
let green = accent.mix(Rgba::rgb8(84, 255, 174), 0.52);
let violet = accent.mix(Rgba::rgb8(218, 92, 255), 0.46);
for band in 0..5 {
let t = band as f32 / 4.0;
let color = if band % 2 == 0 { green } else { violet };
let mut previous: Option<Vec2> = None;
for step in 0..28 {
let u = step as f32 / 27.0;
let wave = (u * 9.0 + time * (0.34 + t * 0.16) + t * 4.2).sin();
let fold = (u * 21.0 - time * 0.23 + t * 3.0).cos() * 0.45;
let x = rect.x + rect.width * (0.06 + u * 0.88);
let y = rect.y + rect.height * (0.20 + t * 0.12) + (wave + fold) * rect.height * 0.055;
let depth = rect.height * (0.22 + t * 0.10 + wave.abs() * 0.10);
draw_line(
x,
y,
x + wave * 5.0,
(y + depth).min(rect.bottom() - 8.0),
1.0,
to_mq(color.with_alpha(0.045 + t * 0.020)),
);
if let Some(prev) = previous {
draw_line(prev.x, prev.y, x, y, 1.6, to_mq(color.with_alpha(0.14)));
}
previous = Some(vec2(x, y));
}
}
for index in 0..24 {
let t = index as f32;
let x = rect.x + rect.width * (0.08 + ((t * 17.23).sin() * 0.5 + 0.5) * 0.84);
let y = rect.y + rect.height * (0.10 + ((t * 9.71).cos() * 0.5 + 0.5) * 0.28);
let alpha = 0.10 + ((time * 1.4 + t).sin() * 0.5 + 0.5) * 0.20;
draw_circle(
x,
y,
1.0 + (index % 3) as f32 * 0.35,
to_mq(Rgba::WHITE.with_alpha(alpha)),
);
}
}
fn draw_preview_knot(rect: Rect, time: f32, accent: Rgba) {
let center = vec2(rect.x + rect.width * 0.50, rect.y + rect.height * 0.52);
draw_glow(
center.x,
center.y,
rect.height * 0.44,
accent.with_alpha(0.12),
6,
);
for strand in 0..3 {
let phase = strand as f32 / 3.0 * std::f32::consts::TAU;
let mut previous: Option<Vec2> = None;
for step in 0..116 {
let u = step as f32 / 115.0;
let a = u * std::f32::consts::TAU * 2.0 + phase + time * 0.22;
let breathe = 1.0 + (a * 3.0 + time).sin() * 0.12;
let x = center.x
+ a.cos() * rect.width * 0.23 * breathe
+ (a * 2.0).cos() * rect.width * 0.055;
let y = center.y
+ (a * 1.5).sin() * rect.height * 0.23
+ (a * 3.0).sin() * rect.height * 0.040;
if let Some(prev) = previous {
let depth = (a.sin() * 0.5 + 0.5).clamp(0.0, 1.0);
let color = accent.mix(Rgba::rgb8(255, 190, 94), depth * 0.26);
draw_line(
prev.x,
prev.y,
x,
y,
1.4 + depth * 1.1,
to_mq(color.with_alpha(0.18 + depth * 0.18)),
);
}
previous = Some(vec2(x, y));
}
}
draw_circle(center.x, center.y, 5.0, to_mq(accent.with_alpha(0.62)));
}
fn draw_preview_bloom(rect: Rect, time: f32, accent: Rgba) {
let blooms = [
(0.26, 0.58, 0.18, 0.0),
(0.44, 0.42, 0.14, 1.2),
(0.64, 0.55, 0.20, 2.4),
(0.76, 0.34, 0.11, 3.8),
];
for (x, y, radius, phase) in blooms {
let px = rect.x + rect.width * (x + (time * 0.20 + phase).sin() * 0.025);
let py = rect.y + rect.height * (y + (time * 0.25 + phase).cos() * 0.035);
let color = accent.mix(Rgba::rgb8(255, 175, 230), phase * 0.05);
draw_glow(px, py, rect.width * radius, color.with_alpha(0.18), 8);
draw_circle(
px,
py,
rect.width * radius * 0.24,
to_mq(color.with_alpha(0.22)),
);
draw_circle_lines(
px,
py,
rect.width * radius * 0.52,
1.0,
to_mq(color.with_alpha(0.20)),
);
}
draw_line(
rect.x + rect.width * 0.12,
rect.bottom() - rect.height * 0.22,
rect.right() - rect.width * 0.12,
rect.bottom() - rect.height * 0.22,
1.3,
to_mq(Rgba::WHITE.with_alpha(0.11)),
);
}
fn draw_preview_lens(rect: Rect, time: f32, accent: Rgba) {
for row in 0..8 {
let y = rect.y + rect.height * (0.18 + row as f32 * 0.078);
let width = rect.width * (0.42 + ((row * 19) % 7) as f32 * 0.045);
draw_rectangle(
rect.x + rect.width * 0.10,
y,
width,
3.0,
to_mq(accent.with_alpha(0.08 + row as f32 * 0.010)),
);
}
let center = vec2(
rect.x + rect.width * (0.54 + (time * 0.25).sin() * 0.04),
rect.y + rect.height * 0.50,
);
let radius = rect.height * 0.24;
draw_glow(center.x, center.y, radius * 1.8, accent.with_alpha(0.10), 5);
draw_circle(center.x, center.y, radius, to_mq(accent.with_alpha(0.045)));
draw_circle_lines(
center.x,
center.y,
radius,
1.7,
to_mq(accent.with_alpha(0.42)),
);
draw_line(
center.x + radius * 0.55,
center.y + radius * 0.55,
center.x + radius * 1.18,
center.y + radius * 1.10,
2.4,
to_mq(accent.with_alpha(0.32)),
);
let scan_x = center.x - radius + (time * 0.45).fract() * radius * 2.0;
draw_rectangle(
scan_x,
center.y - radius * 0.82,
2.0,
radius * 1.64,
to_mq(Rgba::WHITE.with_alpha(0.18)),
);
}
fn draw_preview_graph(rect: Rect, time: f32, accent: Rgba) {
let points = [
vec2(0.12, 0.66),
vec2(0.24, 0.36),
vec2(0.34, 0.58),
vec2(0.48, 0.30),
vec2(0.58, 0.62),
vec2(0.72, 0.42),
vec2(0.84, 0.24),
];
let edges = [
(0, 1),
(0, 2),
(1, 3),
(2, 3),
(2, 4),
(3, 5),
(4, 5),
(5, 6),
];
for (from, to) in edges {
let a = vec2(
rect.x + rect.width * points[from].x,
rect.y + rect.height * points[from].y,
);
let b = vec2(
rect.x + rect.width * points[to].x,
rect.y + rect.height * points[to].y,
);
let spark = ((time * 1.2 + from as f32 * 0.7).sin() * 0.5 + 0.5) * 0.18;
draw_line(
a.x,
a.y,
b.x,
b.y,
1.0,
to_mq(accent.with_alpha(0.14 + spark)),
);
let p = (time * 0.24 + from as f32 * 0.11).fract();
draw_circle(
lerp(a.x, b.x, p),
lerp(a.y, b.y, p),
2.0,
to_mq(Rgba::WHITE.with_alpha(0.20 + spark)),
);
}
for (index, point) in points.iter().enumerate() {
let x = rect.x + rect.width * point.x;
let y = rect.y + rect.height * point.y;
let pulse = (time * 1.7 + index as f32).sin() * 0.5 + 0.5;
draw_glow(x, y, 18.0 + pulse * 12.0, accent.with_alpha(0.08), 4);
draw_circle(
x,
y,
4.0 + pulse * 2.0,
to_mq(accent.with_alpha(0.38 + pulse * 0.18)),
);
}
}
fn draw_preview_reaction(rect: Rect, time: f32, accent: Rgba) {
let cell = (rect.width / 12.0).max(18.0);
let mut y = rect.y + cell * 0.55;
let mut row = 0usize;
while y < rect.bottom() - cell * 0.35 {
let mut x = rect.x + cell * 0.48 + (row % 2) as f32 * cell * 0.28;
while x < rect.right() - cell * 0.35 {
let phase = time * 0.65 + x * 0.021 - y * 0.017;
let radius = cell * (0.18 + phase.sin().abs() * 0.28);
let halo = cell * (0.34 + phase.cos().abs() * 0.28);
draw_circle_lines(x, y, halo, 1.0, to_mq(accent.with_alpha(0.06)));
draw_circle(
x,
y,
radius,
to_mq(accent.with_alpha(0.09 + phase.sin().abs() * 0.08)),
);
x += cell * 0.86;
}
y += cell * 0.72;
row += 1;
}
draw_glow(
rect.x + rect.width * 0.62,
rect.y + rect.height * 0.42,
rect.width * 0.26,
accent.mix(Rgba::rgb8(255, 111, 185), 0.42).with_alpha(0.12),
5,
);
}
fn draw_preview_mesh(rect: Rect, time: f32, accent: Rgba) {
let cols = 5;
let rows = 4;
let mut nodes = Vec::new();
for row in 0..rows {
for col in 0..cols {
let u = col as f32 / (cols - 1) as f32;
let v = row as f32 / (rows - 1) as f32;
let wobble = time * 0.72 + u * 5.3 + v * 4.1;
nodes.push(vec2(
rect.x + rect.width * (0.12 + u * 0.76) + wobble.sin() * 7.0,
rect.y + rect.height * (0.18 + v * 0.64) + wobble.cos() * 5.0,
));
}
}
for row in 0..rows {
for col in 0..cols {
let index = row * cols + col;
let node = nodes[index];
if col + 1 < cols {
let next = nodes[index + 1];
draw_line(
node.x,
node.y,
next.x,
next.y,
1.0,
to_mq(accent.with_alpha(0.18)),
);
}
if row + 1 < rows {
let next = nodes[index + cols];
draw_line(
node.x,
node.y,
next.x,
next.y,
1.0,
to_mq(accent.with_alpha(0.12)),
);
}
if row + 1 < rows && col + 1 < cols && (row + col) % 2 == 0 {
let next = nodes[index + cols + 1];
draw_line(
node.x,
node.y,
next.x,
next.y,
1.0,
to_mq(accent.with_alpha(0.10)),
);
}
}
}
for (index, node) in nodes.iter().enumerate() {
let hot = ((time * 1.4 + index as f32 * 0.67).sin() * 0.5 + 0.5) * 0.30;
draw_circle(
node.x,
node.y,
3.0 + hot * 4.0,
to_mq(accent.with_alpha(0.30 + hot)),
);
}
}
fn draw_preview_card_morph(rect: Rect, time: f32, accent: Rgba) {
let pulse = time.sin() * 0.5 + 0.5;
let card = Rect::new(
rect.x + rect.width * (0.20 - pulse * 0.03),
rect.y + rect.height * (0.20 + pulse * 0.03),
rect.width * (0.48 + pulse * 0.14),
rect.height * 0.44,
);
let ghost = Rect::new(
rect.x + rect.width * 0.34,
rect.y + rect.height * 0.36,
rect.width * 0.45,
rect.height * 0.38,
);
draw_rounded_rect(ghost, 28.0, to_mq(accent.with_alpha(0.07)));
draw_rounded_rect_lines(ghost, 28.0, 1.0, to_mq(accent.with_alpha(0.20)));
draw_rounded_rect(card, 12.0 + pulse * 24.0, to_mq(accent.with_alpha(0.14)));
draw_rounded_rect_lines(
card,
12.0 + pulse * 24.0,
1.4,
to_mq(accent.with_alpha(0.46)),
);
for index in 0..5 {
let t = index as f32 / 4.0;
let x = lerp(card.x + 18.0, card.right() - 24.0, t);
let y = card.y + card.height * (0.30 + (time + t * 3.0).sin() * 0.10);
draw_circle(
x,
y,
3.5,
to_mq(accent.mix(Rgba::WHITE, t * 0.22).with_alpha(0.42)),
);
}
draw_line(
card.right() - 2.0,
card.bottom() - 8.0,
ghost.x + 10.0,
ghost.y + 12.0,
1.2,
to_mq(Rgba::WHITE.with_alpha(0.14)),
);
}
fn draw_preview_metaball(rect: Rect, time: f32, accent: Rgba) {
let balls = [
(0.30, 0.52, 0.18, 0.0),
(0.52, 0.40, 0.15, 1.8),
(0.70, 0.58, 0.20, 3.4),
(0.46, 0.68, 0.12, 5.0),
];
for (x, y, radius, phase) in balls {
let px = rect.x + rect.width * (x + (time * 0.42 + phase).sin() * 0.035);
let py = rect.y + rect.height * (y + (time * 0.36 + phase).cos() * 0.050);
draw_glow(
px,
py,
rect.width * radius * 1.4,
accent.with_alpha(0.10),
5,
);
draw_circle(px, py, rect.width * radius, to_mq(accent.with_alpha(0.16)));
draw_circle_lines(
px,
py,
rect.width * radius * 0.86,
1.0,
to_mq(Rgba::WHITE.with_alpha(0.12)),
);
}
draw_rounded_rect(
Rect::new(
rect.x + rect.width * 0.18,
rect.y + rect.height * 0.27,
rect.width * 0.64,
rect.height * 0.50,
),
46.0,
to_mq(accent.with_alpha(0.045)),
);
}
fn draw_preview_aura(rect: Rect, time: f32, accent: Rgba) {
let center = vec2(
rect.x + rect.width * (0.52 + (time * 0.24).sin() * 0.04),
rect.y + rect.height * 0.50,
);
for ring in 0..7 {
let t = ring as f32 / 6.0;
let radius = rect.height * (0.18 + t * 0.54 + (time * 0.30 + t).sin() * 0.018);
let color = accent.mix(Rgba::rgb8(255, 226, 128), t * 0.32);
draw_circle_lines(
center.x,
center.y,
radius,
1.2 + (1.0 - t) * 1.3,
to_mq(color.with_alpha((1.0 - t) * 0.22 + 0.035)),
);
}
draw_glow(
center.x,
center.y,
rect.width * 0.34,
accent.with_alpha(0.18),
7,
);
draw_circle(center.x, center.y, 8.0, to_mq(Rgba::WHITE.with_alpha(0.24)));
}
fn draw_preview_grid_pulse(rect: Rect, time: f32, accent: Rgba) {
let cols = 9;
let rows = 6;
for col in 0..=cols {
let u = col as f32 / cols as f32;
let x = rect.x + rect.width * u;
draw_line(
x,
rect.y + rect.height * 0.10,
x + (time * 0.8 + u * 3.0).sin() * 10.0,
rect.bottom() - rect.height * 0.10,
1.0,
to_mq(accent.with_alpha(0.08 + u * 0.04)),
);
}
for row in 0..=rows {
let v = row as f32 / rows as f32;
let y = rect.y + rect.height * (0.12 + v * 0.76);
draw_line(
rect.x + rect.width * 0.08,
y,
rect.right() - rect.width * 0.08,
y + (time * 0.7 + v * 5.0).cos() * 4.0,
1.0,
to_mq(accent.with_alpha(0.09)),
);
}
let sweep = (time * 0.32).fract();
let sweep_x = rect.x + rect.width * (0.10 + sweep * 0.80);
draw_rectangle(
sweep_x - rect.width * 0.06,
rect.y + rect.height * 0.12,
rect.width * 0.12,
rect.height * 0.76,
to_mq(accent.with_alpha(0.10)),
);
draw_line(
sweep_x,
rect.y + rect.height * 0.10,
sweep_x,
rect.bottom() - rect.height * 0.10,
2.0,
to_mq(accent.with_alpha(0.42)),
);
}
fn draw_preview_spotlight(rect: Rect, time: f32, accent: Rgba) {
let floor_y = rect.bottom() - rect.height * 0.18;
for index in 0..4 {
let t = index as f32 / 3.0;
let top = vec2(
rect.x + rect.width * (0.18 + t * 0.64),
rect.y + rect.height * 0.12,
);
let aim = rect.x + rect.width * (0.26 + ((time * 0.45 + t * 2.8).sin() * 0.5 + 0.5) * 0.48);
let color = accent.mix(Rgba::WHITE, 0.12 + t * 0.10);
draw_triangle(
top,
vec2(aim - rect.width * 0.13, floor_y),
vec2(aim + rect.width * 0.13, floor_y),
to_mq(color.with_alpha(0.055 + t * 0.012)),
);
draw_circle(top.x, top.y, 4.0, to_mq(color.with_alpha(0.48)));
}
draw_line(
rect.x + rect.width * 0.12,
floor_y,
rect.right() - rect.width * 0.12,
floor_y,
1.4,
to_mq(accent.with_alpha(0.26)),
);
draw_glow(
rect.x + rect.width * 0.54,
floor_y,
rect.width * 0.28,
accent.with_alpha(0.09),
4,
);
}
fn draw_preview_glitch(rect: Rect, time: f32, accent: Rgba) {
for band in 0..12 {
let t = band as f32 / 11.0;
let y = rect.y + rect.height * (0.10 + t * 0.80);
let shift = (time * 7.0 + band as f32 * 1.7).sin() * rect.width * 0.035;
let h = 3.0 + (band % 3) as f32 * 2.0;
let alpha = 0.08 + ((time * 4.0 + band as f32).sin() * 0.5 + 0.5) * 0.13;
draw_rectangle(
rect.x + rect.width * 0.08 + shift,
y,
rect.width * (0.40 + (band % 5) as f32 * 0.08),
h,
to_mq(accent.with_alpha(alpha)),
);
if band % 4 == 0 {
draw_rectangle(
rect.x + rect.width * 0.16 - shift * 0.7,
y + h + 2.0,
rect.width * 0.34,
1.4,
to_mq(Rgba::rgb8(255, 78, 135).with_alpha(alpha * 0.9)),
);
}
}
draw_text_line(
"VHS",
rect.x + rect.width * 0.36,
rect.y + rect.height * 0.56,
28.0,
to_mq(accent.with_alpha(0.28)),
);
}
fn draw_preview_waves(rect: Rect, time: f32, accent: Rgba) {
for line in 0..8 {
let t = line as f32 / 7.0;
let base = rect.y + rect.height * (0.18 + t * 0.64);
let mut previous: Option<Vec2> = None;
for step in 0..36 {
let u = step as f32 / 35.0;
let x = rect.x + rect.width * (0.06 + u * 0.88);
let wave = (u * 16.0 + time * (0.75 + t * 0.34) + t * 5.0).sin();
let y = base + wave * rect.height * (0.026 + t * 0.012);
if let Some(prev) = previous {
draw_line(
prev.x,
prev.y,
x,
y,
1.1,
to_mq(accent.with_alpha(0.10 + t * 0.05)),
);
}
previous = Some(vec2(x, y));
}
}
draw_glow(
rect.x + rect.width * 0.32,
rect.y + rect.height * 0.58,
rect.width * 0.22,
accent.with_alpha(0.10),
4,
);
}
fn draw_preview_chrome(rect: Rect, time: f32, accent: Rgba) {
let center = vec2(rect.x + rect.width * 0.50, rect.y + rect.height * 0.52);
let radius = rect.height * 0.30;
draw_glow(
center.x,
center.y,
rect.width * 0.34,
accent.with_alpha(0.13),
6,
);
for band in 0..9 {
let t = band as f32 / 8.0;
let y = center.y - radius + t * radius * 2.0;
let half = (1.0 - (t * 2.0 - 1.0).abs()).sqrt() * radius * 1.65;
let shift = (time * 0.8 + t * 5.0).sin() * 8.0;
let color = accent.mix(Rgba::WHITE, if band % 2 == 0 { 0.44 } else { 0.14 });
draw_line(
center.x - half + shift,
y,
center.x + half + shift * 0.4,
y + (time + t).cos() * 2.4,
3.0,
to_mq(color.with_alpha(0.10 + t * 0.035)),
);
}
draw_circle_lines(
center.x,
center.y,
radius * 1.26,
1.2,
to_mq(Rgba::WHITE.with_alpha(0.18)),
);
draw_circle_lines(
center.x,
center.y,
radius * 0.58,
1.0,
to_mq(accent.with_alpha(0.30)),
);
}
fn draw_preview_caustic(rect: Rect, time: f32, accent: Rgba) {
for strand in 0..13 {
let t = strand as f32 / 12.0;
let mut previous: Option<Vec2> = None;
for step in 0..30 {
let u = step as f32 / 29.0;
let x = rect.x + rect.width * (0.06 + u * 0.88);
let wave_a = (u * 9.0 + time * 0.65 + t * 5.2).sin();
let wave_b = (u * 15.0 - time * 0.43 + t * 3.3).cos();
let y = rect.y + rect.height * (0.16 + t * 0.66) + (wave_a + wave_b) * 3.2;
if let Some(prev) = previous {
draw_line(
prev.x,
prev.y,
x,
y,
1.0,
to_mq(accent.mix(Rgba::WHITE, 0.24).with_alpha(0.10 + t * 0.035)),
);
}
previous = Some(vec2(x, y));
}
}
draw_glow(
rect.x + rect.width * 0.58,
rect.y + rect.height * 0.46,
rect.width * 0.32,
accent.mix(Rgba::rgb8(142, 255, 220), 0.34).with_alpha(0.12),
5,
);
}
fn draw_preview_crystal(rect: Rect, time: f32, accent: Rgba) {
let left = rect.x + rect.width * 0.08;
let right = rect.right() - rect.width * 0.08;
let top = rect.y + rect.height * 0.20;
let bottom = rect.bottom() - rect.height * 0.18;
let mid = rect.y + rect.height * (0.48 + (time * 0.35).sin() * 0.04);
for index in 0..7 {
let t0 = index as f32 / 7.0;
let t1 = (index + 1) as f32 / 7.0;
let x0 = lerp(left, right, t0);
let x1 = lerp(left, right, t1);
let tint = accent.mix(Rgba::WHITE, if index % 2 == 0 { 0.18 } else { 0.04 });
draw_triangle(
vec2(x0, top + (index % 2) as f32 * 8.0),
vec2(x1, top + ((index + 1) % 2) as f32 * 8.0),
vec2((x0 + x1) * 0.5, mid),
to_mq(tint.with_alpha(0.10 + index as f32 * 0.012)),
);
draw_triangle(
vec2(x0, bottom - (index % 2) as f32 * 8.0),
vec2(x1, bottom - ((index + 1) % 2) as f32 * 8.0),
vec2((x0 + x1) * 0.5, mid),
to_mq(accent.with_alpha(0.08 + index as f32 * 0.010)),
);
}
}
fn draw_preview_vortex(rect: Rect, time: f32, accent: Rgba) {
let center = vec2(rect.x + rect.width * 0.50, rect.y + rect.height * 0.52);
draw_glow(
center.x,
center.y,
rect.height * 0.58,
accent.with_alpha(0.12),
6,
);
for ring in 0..11 {
let t = ring as f32 / 10.0;
let radius = rect.height * (0.07 + t * 0.62);
let squeeze = 0.34 + t * 0.26;
let alpha = (1.0 - t) * 0.32 + 0.025;
for segment in 0..34 {
let a0 = segment as f32 / 34.0 * std::f32::consts::TAU + time * 0.42 + t * 2.4;
let a1 = a0 + 0.12;
let p0 = vec2(
center.x + a0.cos() * radius,
center.y + a0.sin() * radius * squeeze,
);
let p1 = vec2(
center.x + a1.cos() * radius,
center.y + a1.sin() * radius * squeeze,
);
draw_line(
p0.x,
p0.y,
p1.x,
p1.y,
1.0 + (1.0 - t) * 0.7,
to_mq(accent.with_alpha(alpha)),
);
}
}
draw_circle(center.x, center.y, 16.0, to_mq(accent.with_alpha(0.22)));
draw_circle(center.x, center.y, 6.0, to_mq(accent.with_alpha(0.62)));
}
fn draw_preview_storm(rect: Rect, time: f32, accent: Rgba) {
for index in 0..5 {
let x = rect.x + rect.width * (0.18 + index as f32 * 0.16);
let top = rect.y + rect.height * (0.18 + (time + index as f32).sin() * 0.05);
let p1 = vec2(x, top);
let p2 = vec2(x - 10.0, top + rect.height * 0.22);
let p3 = vec2(x + 14.0, top + rect.height * 0.42);
let p4 = vec2(x + 2.0, top + rect.height * 0.66);
let alpha = 0.18 + ((time * 3.0 + index as f32).sin() * 0.5 + 0.5) * 0.22;
draw_line(p1.x, p1.y, p2.x, p2.y, 1.6, to_mq(accent.with_alpha(alpha)));
draw_line(p2.x, p2.y, p3.x, p3.y, 1.6, to_mq(accent.with_alpha(alpha)));
draw_line(p3.x, p3.y, p4.x, p4.y, 1.6, to_mq(accent.with_alpha(alpha)));
}
draw_glow(
rect.x + rect.width * 0.54,
rect.y + rect.height * 0.42,
rect.width * 0.28,
accent.with_alpha(0.14),
5,
);
}
fn draw_preview_fireworks(rect: Rect, time: f32, accent: Rgba) {
let centers = [
vec2(rect.x + rect.width * 0.30, rect.y + rect.height * 0.42),
vec2(rect.x + rect.width * 0.68, rect.y + rect.height * 0.56),
vec2(rect.x + rect.width * 0.50, rect.y + rect.height * 0.32),
];
for (center_index, center) in centers.iter().enumerate() {
let phase = (time * 0.35 + center_index as f32 * 0.42).fract();
let burst = accent.mix(
Rgba::rgb8(255, 225, 150),
if center_index % 2 == 0 { 0.28 } else { 0.08 },
);
draw_glow(
center.x,
center.y,
rect.height * (0.16 + phase * 0.28),
burst.with_alpha((1.0 - phase) * 0.12),
4,
);
for ray in 0..22 {
let angle = ray as f32 / 22.0 * std::f32::consts::TAU + center_index as f32 * 0.17;
let radius = rect.height * (0.12 + phase * 0.44);
let start = radius * 0.45;
let alpha = (1.0 - phase) * 0.38 + 0.04;
draw_line(
center.x + angle.cos() * start,
center.y + angle.sin() * start,
center.x + angle.cos() * radius,
center.y + angle.sin() * radius,
1.1,
to_mq(burst.with_alpha(alpha)),
);
if ray % 4 == 0 {
draw_circle(
center.x + angle.cos() * radius,
center.y + angle.sin() * radius,
1.8,
to_mq(burst.with_alpha(alpha * 0.9)),
);
}
}
draw_circle(center.x, center.y, 3.0, to_mq(burst.with_alpha(0.58)));
}
}
fn draw_shader_scanlines(rect: Rect, time: f32, accent: Rgba) {
let mut y = rect.y + 4.0 + (time * 18.0).fract() * 3.0;
while y < rect.bottom() - 2.0 {
draw_line(
rect.x + 6.0,
y,
rect.right() - 6.0,
y,
1.0,
to_mq(accent.with_alpha(0.055)),
);
y += 7.0;
}
draw_rectangle(rect.x, rect.y, rect.width, 1.0, rgba8(255, 255, 255, 42));
draw_rectangle(
rect.x,
rect.bottom() - 1.0,
rect.width,
1.0,
rgba8(0, 0, 0, 80),
);
}
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) {
let layers = [(1.0, 0.0, 0.030), (2.0, 4.0, 0.018), (4.0, 10.0, 0.010)];
for (offset, spread, alpha) in layers {
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 * alpha),
);
}
}
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.88 } else { 0.80 })),
);
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.16 } else { 0.09 }),
),
);
draw_glass_rim(rect, radius, accent, focused);
draw_rounded_rect_lines(
rect,
radius,
if focused { 1.7 } else { 1.0 },
to_mq(accent.with_alpha(if focused { 0.70 } else { 0.34 })),
);
}
fn draw_glass_rim(rect: Rect, radius: f32, accent: Rgba, focused: bool) {
let highlight = Rgba::WHITE.with_alpha(if focused { 0.22 } else { 0.13 });
let lowlight = Rgba::BLACK.with_alpha(if focused { 0.24 } else { 0.16 });
let inset = 1.0;
let left = rect.x + radius * 0.78;
let right = rect.right() - radius * 0.55;
let top = rect.y + inset;
let bottom = rect.bottom() - inset;
draw_line(left, top, right, top, 1.0, to_mq(highlight));
draw_line(
rect.x + inset,
rect.y + radius * 0.80,
rect.x + inset,
rect.bottom() - radius * 0.70,
1.0,
to_mq(highlight.with_alpha(highlight.a * 0.50)),
);
draw_line(
rect.x + radius * 0.90,
bottom,
rect.right() - radius * 0.72,
bottom,
1.0,
to_mq(lowlight),
);
draw_line(
rect.right() - inset,
rect.y + radius * 0.90,
rect.right() - inset,
rect.bottom() - radius * 0.72,
1.0,
to_mq(lowlight.with_alpha(lowlight.a * 0.72)),
);
let glint = Rect::new(
rect.x + radius * 0.55,
rect.y + 2.0,
(rect.width * 0.18).min(120.0),
1.0,
);
draw_rectangle(
glint.x,
glint.y,
glint.width,
glint.height,
to_mq(accent.with_alpha(if focused { 0.18 } else { 0.09 })),
);
}
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_rounded_rect_lines(rect, rect.height * 0.5, 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_rounded_rect_lines(rect, 16.0, 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) {
with_demo_font(|font| {
draw_text_line_with_font(text, x, y, size, color, font);
});
}
pub fn draw_code_text_line(text: &str, x: f32, y: f32, size: f32, color: Color) {
with_demo_mono_font(|font| {
draw_text_line_with_font(text, x, y, size, color, font);
});
}
fn draw_text_line_with_font(
text: &str,
x: f32,
y: f32,
size: f32,
color: Color,
font: Option<&Font>,
) {
draw_text_ex(
text,
x + 1.0,
y + 1.0,
TextParams {
font,
font_size: size as u16,
color: Color::new(0.0, 0.0, 0.0, (color.a * 0.58).min(0.72)),
..Default::default()
},
);
draw_text_ex(
text,
x,
y,
TextParams {
font,
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_demo_text(&candidate, size).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
}
fn measure_demo_text(text: &str, size: f32) -> TextDimensions {
with_demo_font(|font| measure_text(text, font, size as u16, 1.0))
}
pub fn measure_demo_text_width(text: &str, size: f32) -> f32 {
measure_demo_text(text, size).width
}
pub fn measure_code_text_width(text: &str, size: f32) -> f32 {
with_demo_mono_font(|font| measure_text(text, font, size as u16, 1.0).width)
}
pub fn draw_rounded_rect(rect: Rect, radius: f32, color: Color) {
if !rect.is_finite() || rect.width <= 0.0 || rect.height <= 0.0 || !radius.is_finite() {
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);
}
pub fn draw_rounded_rect_lines(rect: Rect, radius: f32, thickness: f32, color: Color) {
if !rect.is_finite()
|| rect.width <= 0.0
|| rect.height <= 0.0
|| !radius.is_finite()
|| !thickness.is_finite()
|| thickness <= 0.0
{
return;
}
let radius = radius.min(rect.width * 0.5).min(rect.height * 0.5).max(0.0);
if radius <= 0.0 {
draw_rectangle_lines(rect.x, rect.y, rect.width, rect.height, thickness, color);
return;
}
draw_line(
rect.x + radius,
rect.y,
rect.right() - radius,
rect.y,
thickness,
color,
);
draw_line(
rect.right(),
rect.y + radius,
rect.right(),
rect.bottom() - radius,
thickness,
color,
);
draw_line(
rect.right() - radius,
rect.bottom(),
rect.x + radius,
rect.bottom(),
thickness,
color,
);
draw_line(
rect.x,
rect.bottom() - radius,
rect.x,
rect.y + radius,
thickness,
color,
);
draw_arc_lines(
vec2(rect.right() - radius, rect.y + radius),
radius,
-std::f32::consts::FRAC_PI_2,
0.0,
thickness,
color,
);
draw_arc_lines(
vec2(rect.right() - radius, rect.bottom() - radius),
radius,
0.0,
std::f32::consts::FRAC_PI_2,
thickness,
color,
);
draw_arc_lines(
vec2(rect.x + radius, rect.bottom() - radius),
radius,
std::f32::consts::FRAC_PI_2,
std::f32::consts::PI,
thickness,
color,
);
draw_arc_lines(
vec2(rect.x + radius, rect.y + radius),
radius,
std::f32::consts::PI,
std::f32::consts::PI * 1.5,
thickness,
color,
);
}
fn draw_arc_lines(center: Vec2, radius: f32, start: f32, end: f32, thickness: f32, color: Color) {
if !center.x.is_finite()
|| !center.y.is_finite()
|| !radius.is_finite()
|| !start.is_finite()
|| !end.is_finite()
|| !thickness.is_finite()
|| radius <= 0.0
|| thickness <= 0.0
{
return;
}
let segments = 10;
let mut previous = vec2(
center.x + start.cos() * radius,
center.y + start.sin() * radius,
);
for index in 1..=segments {
let t = index as f32 / segments as f32;
let angle = start + (end - start) * t;
let next = vec2(
center.x + angle.cos() * radius,
center.y + angle.sin() * radius,
);
draw_line(previous.x, previous.y, next.x, next.y, thickness, color);
previous = next;
}
}