raui-app 0.70.17

RAUI application layer to focus only on making UI
Documentation
pub mod app;
pub(crate) mod asset_manager;
pub mod components;
pub(crate) mod interactions;
pub mod render_worker;
pub(crate) mod text_measurements;

use crate::{
    asset_manager::AssetsManager,
    components::canvas::{CanvasProps, canvas},
};
use bytemuck::{Pod, Zeroable};
use raui_core::{
    application::Application,
    widget::{FnWidget, utils::Color},
};
use raui_tesselate_renderer::{TesselateBatch, TesselateBatchConverter, TesselateVertex};
use spitfire_fontdue::TextVertex;
use spitfire_glow::{
    graphics::{
        Texture, {GraphicsBatch, Shader},
    },
    renderer::{
        GlowBlending, GlowTextureFiltering, GlowUniformValue, GlowVertexAttrib, GlowVertexAttribs,
    },
};
use vek::Rect;

#[cfg(not(target_arch = "wasm32"))]
pub use glutin::{dpi, event, window};
#[cfg(target_arch = "wasm32")]
pub use winit::{dpi, event, window};

pub mod third_party {
    pub use spitfire_fontdue;
    pub use spitfire_glow;
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Vertex {
    pub position: [f32; 2],
    pub uv: [f32; 3],
    pub color: [f32; 4],
}

impl Default for Vertex {
    fn default() -> Self {
        Self {
            position: Default::default(),
            uv: Default::default(),
            color: [1.0, 1.0, 1.0, 1.0],
        }
    }
}

impl GlowVertexAttribs for Vertex {
    const ATTRIBS: &'static [(&'static str, GlowVertexAttrib)] = &[
        (
            "a_position",
            GlowVertexAttrib::Float {
                channels: 2,
                normalized: false,
            },
        ),
        (
            "a_uv",
            GlowVertexAttrib::Float {
                channels: 3,
                normalized: false,
            },
        ),
        (
            "a_color",
            GlowVertexAttrib::Float {
                channels: 4,
                normalized: false,
            },
        ),
    ];
}

impl TesselateVertex for Vertex {
    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: [f32; 4]) {
        self.position = position;
        self.uv = tex_coord;
        self.color = color;
    }

    fn transform(&mut self, matrix: vek::Mat4<f32>) {
        let result = matrix.mul_point(vek::Vec3 {
            x: self.position[0],
            y: self.position[1],
            z: 0.0,
        });
        self.position[0] = result.x;
        self.position[1] = result.y;
    }
}

impl TextVertex<Color> for Vertex {
    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: Color) {
        self.position = position;
        self.uv = tex_coord;
        self.color = [color.r, color.g, color.b, color.a];
    }
}

pub(crate) struct TesselateToGraphics<'a> {
    colored_shader: &'a Shader,
    textured_shader: &'a Shader,
    text_shader: &'a Shader,
    #[cfg(debug_assertions)]
    debug_shader: Option<&'a Shader>,
    glyphs_texture: &'a Texture,
    missing_texture: &'a Texture,
    assets: &'a AssetsManager,
    clip_stack: Vec<Rect<i32, i32>>,
    viewport_height: i32,
    projection_view_matrix: [f32; 16],
}

impl TesselateBatchConverter<GraphicsBatch> for TesselateToGraphics<'_> {
    fn convert(&mut self, batch: TesselateBatch) -> Option<GraphicsBatch> {
        match batch {
            TesselateBatch::Color => Some(GraphicsBatch {
                shader: Some(self.colored_shader.clone()),
                blending: GlowBlending::Alpha,
                scissor: self.clip_stack.last().copied(),
                ..Default::default()
            }),
            TesselateBatch::Image { id } => {
                let id = AssetsManager::parse_image_id(&id).0;
                Some(GraphicsBatch {
                    shader: Some(self.textured_shader.clone()),
                    textures: vec![(
                        self.assets
                            .textures
                            .get(id)
                            .map(|texture| texture.texture.clone())
                            .unwrap_or_else(|| self.missing_texture.clone()),
                        GlowTextureFiltering::Linear,
                    )],
                    blending: GlowBlending::Alpha,
                    scissor: self.clip_stack.last().copied(),
                    ..Default::default()
                })
            }
            TesselateBatch::Text => Some(GraphicsBatch {
                shader: Some(self.text_shader.clone()),
                textures: vec![(self.glyphs_texture.clone(), GlowTextureFiltering::Linear)],
                blending: GlowBlending::Alpha,
                scissor: self.clip_stack.last().copied(),
                ..Default::default()
            }),
            TesselateBatch::Procedural {
                id,
                images,
                parameters,
            } => Some(GraphicsBatch {
                shader: self
                    .assets
                    .shaders
                    .get(&id)
                    .map(|shader| shader.shader.clone()),
                uniforms: parameters
                    .into_iter()
                    .map(|(k, v)| (k.into(), GlowUniformValue::F1(v)))
                    .chain((0..images.len()).map(|index| {
                        (
                            if index > 0 {
                                format!("u_image{index}").into()
                            } else {
                                "u_image".into()
                            },
                            GlowUniformValue::I1(index as _),
                        )
                    }))
                    .chain(std::iter::once((
                        "u_projection_view".into(),
                        GlowUniformValue::M4(self.projection_view_matrix),
                    )))
                    .collect(),
                textures: images
                    .into_iter()
                    .filter_map(|id| {
                        Some((
                            self.assets.textures.get(&id)?.texture.to_owned(),
                            GlowTextureFiltering::Linear,
                        ))
                    })
                    .collect(),
                scissor: self.clip_stack.last().copied(),
                ..Default::default()
            }),
            TesselateBatch::ClipPush { x, y, w, h } => {
                self.clip_stack.push(vek::Rect {
                    x: x as _,
                    y: self.viewport_height - y as i32 - h as i32,
                    w: w as _,
                    h: h as _,
                });
                None
            }
            TesselateBatch::ClipPop => {
                self.clip_stack.pop();
                None
            }
            TesselateBatch::Debug => Some(GraphicsBatch {
                shader: self.debug_shader.cloned(),
                wireframe: true,
                ..Default::default()
            }),
        }
    }
}

pub fn setup(app: &mut Application) {
    app.register_props::<CanvasProps>("CanvasProps");

    app.register_component("canvas", FnWidget::pointer(canvas));
}