wgpu_canvas 5.0.5

A 2D WGPU renderer API for drawing text, shapes, and images with Rust.
Documentation
#![allow(clippy::multiple_bound_locations)]

use wgpu::{VertexBufferLayout, VertexStepMode, BufferAddress, VertexAttribute, VertexFormat};

use crate::{RgbaImage, Area, Color};
use crate::shape::Shape;
use std::sync::Arc;

pub trait Vertex: std::fmt::Debug + bytemuck::Pod + bytemuck::Zeroable{
    fn attributes() -> Vec<VertexFormat> where Self: Sized;

    fn layout() -> VertexBufferLayout<'static> where Self: Sized {
        let mut offset = 0;
        VertexBufferLayout {
            array_stride: std::mem::size_of::<Self>() as BufferAddress,
            step_mode: VertexStepMode::Vertex,
            attributes: Self::attributes().into_iter().enumerate().map(|(i, a)| {
                let va = VertexAttribute{
                    format: a,
                    offset,
                    shader_location: i as u32
                };
                offset += a.size();
                va
            }).collect::<Vec<_>>().leak(),
        }
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ShapeVertex {
    pub uv: [f32; 2],
    pub position: [f32; 2],
    pub size: [f32; 2],
    pub bounds: [f32; 4],
    pub z_index: f32,
    pub stroke: f32
}

impl Vertex for ShapeVertex {
    fn attributes() -> Vec<VertexFormat> {
        vec![
            VertexFormat::Float32x2, VertexFormat::Float32x2, VertexFormat::Float32x2,
            VertexFormat::Float32x4, VertexFormat::Float32, VertexFormat::Float32
        ]
    }
}

impl ShapeVertex {
    pub fn transform_point(width: f32, height: f32, p: [f32; 2]) -> [f32; 2] {
        let w = |x: f32| ((x / width) * 2.0) - 1.0;
        let h = |y: f32| 1.0 - ((y / height) * 2.0);
        [w(p[0]), h(p[1])]
    }
    pub fn transform(width: f32, height: f32, p: [[f32; 2]; 4]) -> [[f32; 2]; 4] {
        [
            Self::transform_point(width, height, p[0]),
            Self::transform_point(width, height, p[1]),
            Self::transform_point(width, height, p[2]),
            Self::transform_point(width, height, p[3])
        ]
    }

    pub fn new(width: f32, height: f32, z: u16, area: Area, shape: Shape) -> [ShapeVertex; 4] {
        let op = shape.positions(area.offset);
        let positions = Self::transform(width, height, op);
        let size = shape.wh();

        let stroke = shape.stroke();

        let bounds = area.bounds.unwrap_or((0.0, 0.0, width, height));
        let [bx, by] = Self::transform_point(width, height, [bounds.0, bounds.1]);
        let [bx2, by2] = Self::transform_point(width, height, [bounds.0+bounds.2, bounds.1+bounds.3]);
        let bounds = [bx, by, bx2, by2];

        let z_index = z as f32 / u16::MAX as f32;

        [
            ShapeVertex{uv: [0.0, 0.0], position: positions[0], size, bounds, z_index, stroke},
            ShapeVertex{uv: [size[0], 0.0], position: positions[1], size, bounds, z_index, stroke},
            ShapeVertex{uv: [0.0, size[1]], position: positions[2], size, bounds, z_index, stroke},
            ShapeVertex{uv: [size[0], size[1]], position: positions[3], size, bounds, z_index, stroke}
        ]
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RoundedRectangleVertex {
    pub shape: ShapeVertex,
    pub corner_radius: f32,
}

impl Vertex for RoundedRectangleVertex {
    fn attributes() -> Vec<VertexFormat> {
        [ShapeVertex::attributes(), vec![VertexFormat::Float32]].concat()
    }
}

impl RoundedRectangleVertex {
    pub fn new(width: f32, height: f32, z: u16, area: Area, shape: Shape, corner_radius: f32) -> [RoundedRectangleVertex; 4] {
        ShapeVertex::new(width, height, z, area, shape).into_iter().map(|shape|
            RoundedRectangleVertex{shape, corner_radius}
        ).collect::<Vec<_>>().try_into().unwrap()
    }
}


#[repr(packed, C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ColorVertex<V: Vertex = ShapeVertex> {
    pub shape: V,
    pub color: [f32; 4]
}

impl<V: Vertex> Vertex for ColorVertex<V> {
    fn attributes() -> Vec<VertexFormat> {
        [V::attributes(), vec![VertexFormat::Float32x4]].concat()
    }
}

impl<V: Vertex> ColorVertex<V> {
    pub fn new(shape: [V; 4], color: Color) -> [ColorVertex<V>; 4] {
        let c = |f: u8| if f == 0 {0.0} else {(((f as f32 / u8::MAX as f32) + 0.055) / 1.055).powf(2.4)};
        let color = [c(color.0), c(color.1), c(color.2), c(color.3)];
        shape.into_iter().map(|shape|
            ColorVertex{shape, color}
        ).collect::<Vec<_>>().try_into().unwrap()
    }
}

#[repr(packed, C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ImageVertex<V: Vertex = ShapeVertex> {
    pub color: ColorVertex<V>,
    pub texture: [f32; 2]
}

impl<V: Vertex> Vertex for ImageVertex<V> {
    fn attributes() -> Vec<VertexFormat> {
        [ColorVertex::<V>::attributes(), vec![VertexFormat::Float32x2]].concat()
    }
}

impl<V: Vertex> ImageVertex<V> {
    pub fn new(shape: [V; 4], image: &Arc<RgbaImage>, size: (f32, f32), color: Option<Color>) -> [ImageVertex<V>; 4] {
        let mut x = 0.0;
        let mut y = 0.0;
        let mut x2 = 1.0;
        let mut y2 = 1.0;

        let wi = image.width() as f32;
        let hi = image.height() as f32;
        let ws = size.0;
        let hs = size.1;

        let wr = ws / wi;
        let hr = hs / hi;

        if hr > wr {
            let d = (1.0-(wr / hr)) / 2.0;
            x = d;
            x2 = 1.0-d;
        } else {
            let d = (1.0-(hr / wr)) / 2.0;
            y = d;
            y2 = 1.0-d;
        }

        let color = ColorVertex::new(shape, color.unwrap_or_default());

        [
            ImageVertex{color: color[0], texture: [x, y]},
            ImageVertex{color: color[1], texture: [x2, y]},
            ImageVertex{color: color[2], texture: [x, y2]},
            ImageVertex{color: color[3], texture: [x2, y2]},
        ]
    }
}