waterui-shape 0.5.0

Shape primitives and filled shapes for WaterUI using Lyon tessellation
Documentation
struct Uniforms {
    color: vec4<f32>,
    dimensions_and_progress: vec4<f32>, // width, height, progress, _pad
    shape_types: vec4<f32>,             // from_type, to_type, _pad, _pad
    from_radii: vec4<f32>,              // tl, tr, br, bl
    to_radii: vec4<f32>,                // tl, tr, br, bl
}

@group(0) @binding(0) var<uniform> uniforms: Uniforms;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
}

@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
    var positions = array<vec2<f32>, 6>(
        vec2<f32>(-1.0, -1.0),
        vec2<f32>( 1.0, -1.0),
        vec2<f32>(-1.0,  1.0),
        vec2<f32>(-1.0,  1.0),
        vec2<f32>( 1.0, -1.0),
        vec2<f32>( 1.0,  1.0),
    );
    let pos = positions[vertex_index];

    var out: VertexOutput;
    out.position = vec4<f32>(pos, 0.0, 1.0);
    out.uv = pos * 0.5 + 0.5;
    out.uv.y = 1.0 - out.uv.y;
    return out;
}

fn sd_rect(p: vec2<f32>, b: vec2<f32>) -> f32 {
    let d = abs(p) - b;
    return length(max(d, vec2<f32>(0.0))) + min(max(d.x, d.y), 0.0);
}

fn sd_circle(p: vec2<f32>, r: f32) -> f32 {
    return length(p) - r;
}

fn sd_rounded_box(p: vec2<f32>, b: vec2<f32>, r: vec4<f32>) -> f32 {
    // Radii are ordered TL, TR, BR, BL.
    let radius_val = select(
        select(r.x, r.y, p.x > 0.0),
        select(r.w, r.z, p.x > 0.0),
        p.y > 0.0
    );
    let q = abs(p) - b + vec2<f32>(radius_val);
    return min(max(q.x, q.y), 0.0) + length(max(q, vec2<f32>(0.0))) - radius_val;
}

fn shape_distance(shape_type: u32, p: vec2<f32>, size: vec2<f32>, radii: vec4<f32>) -> f32 {
    if (shape_type == 0u) { // Rect
        return sd_rect(p, size * 0.5);
    }
    if (shape_type == 1u) { // Circle
        return sd_circle(p, min(size.x, size.y) * 0.5);
    }
    if (shape_type == 2u) { // Ellipse
        let semi = size * 0.5;
        return (length(p / semi) - 1.0) * min(semi.x, semi.y);
    }
    if (shape_type == 3u) { // RoundedRect
        let min_dim = min(size.x, size.y);
        return sd_rounded_box(p, size * 0.5, radii * min_dim);
    }
    if (shape_type == 4u) { // Capsule
        let r = min(size.x, size.y) * 0.5;
        return sd_rounded_box(p, size * 0.5, vec4<f32>(r));
    }
    return sd_rect(p, size * 0.5);
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let size = uniforms.dimensions_and_progress.xy;
    let progress = clamp(uniforms.dimensions_and_progress.z, 0.0, 1.0);
    let from_shape = u32(uniforms.shape_types.x + 0.5);
    let to_shape = u32(uniforms.shape_types.y + 0.5);
    let p = (in.uv * size) - (size * 0.5);

    let from_dist = shape_distance(from_shape, p, size, uniforms.from_radii);
    let to_dist = shape_distance(to_shape, p, size, uniforms.to_radii);
    let dist = mix(from_dist, to_dist, progress);

    // Pixel-accurate edge smoothing derived from signed-distance derivatives.
    let aa = max(fwidth(dist), 0.5);
    let alpha = 1.0 - smoothstep(-aa, aa, dist);
    return vec4<f32>(uniforms.color.rgb, uniforms.color.a * alpha);
}