1pub mod app;
2pub(crate) mod asset_manager;
3pub mod components;
4pub(crate) mod interactions;
5pub mod render_worker;
6pub(crate) mod text_measurements;
7
8use crate::{
9 asset_manager::AssetsManager,
10 components::canvas::{CanvasProps, canvas},
11};
12use bytemuck::{Pod, Zeroable};
13use raui_core::{
14 application::Application,
15 widget::{FnWidget, utils::Color},
16};
17use raui_tesselate_renderer::{TesselateBatch, TesselateBatchConverter, TesselateVertex};
18use spitfire_fontdue::TextVertex;
19use spitfire_glow::{
20 graphics::{
21 Texture, {GraphicsBatch, Shader},
22 },
23 renderer::{
24 GlowBlending, GlowTextureFiltering, GlowUniformValue, GlowVertexAttrib, GlowVertexAttribs,
25 },
26};
27use vek::Rect;
28
29#[cfg(not(target_arch = "wasm32"))]
30pub use glutin::{dpi, event, window};
31#[cfg(target_arch = "wasm32")]
32pub use winit::{dpi, event, window};
33
34pub mod third_party {
35 pub use spitfire_fontdue;
36 pub use spitfire_glow;
37}
38
39#[derive(Debug, Clone, Copy, Pod, Zeroable)]
40#[repr(C)]
41pub struct Vertex {
42 pub position: [f32; 2],
43 pub uv: [f32; 3],
44 pub color: [f32; 4],
45}
46
47impl Default for Vertex {
48 fn default() -> Self {
49 Self {
50 position: Default::default(),
51 uv: Default::default(),
52 color: [1.0, 1.0, 1.0, 1.0],
53 }
54 }
55}
56
57impl GlowVertexAttribs for Vertex {
58 const ATTRIBS: &'static [(&'static str, GlowVertexAttrib)] = &[
59 (
60 "a_position",
61 GlowVertexAttrib::Float {
62 channels: 2,
63 normalized: false,
64 },
65 ),
66 (
67 "a_uv",
68 GlowVertexAttrib::Float {
69 channels: 3,
70 normalized: false,
71 },
72 ),
73 (
74 "a_color",
75 GlowVertexAttrib::Float {
76 channels: 4,
77 normalized: false,
78 },
79 ),
80 ];
81}
82
83impl TesselateVertex for Vertex {
84 fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: [f32; 4]) {
85 self.position = position;
86 self.uv = tex_coord;
87 self.color = color;
88 }
89
90 fn transform(&mut self, matrix: vek::Mat4<f32>) {
91 let result = matrix.mul_point(vek::Vec3 {
92 x: self.position[0],
93 y: self.position[1],
94 z: 0.0,
95 });
96 self.position[0] = result.x;
97 self.position[1] = result.y;
98 }
99}
100
101impl TextVertex<Color> for Vertex {
102 fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: Color) {
103 self.position = position;
104 self.uv = tex_coord;
105 self.color = [color.r, color.g, color.b, color.a];
106 }
107}
108
109pub(crate) struct TesselateToGraphics<'a> {
110 colored_shader: &'a Shader,
111 textured_shader: &'a Shader,
112 text_shader: &'a Shader,
113 #[cfg(debug_assertions)]
114 debug_shader: Option<&'a Shader>,
115 glyphs_texture: &'a Texture,
116 missing_texture: &'a Texture,
117 assets: &'a AssetsManager,
118 clip_stack: Vec<Rect<i32, i32>>,
119 viewport_height: i32,
120 projection_view_matrix: [f32; 16],
121}
122
123impl TesselateBatchConverter<GraphicsBatch> for TesselateToGraphics<'_> {
124 fn convert(&mut self, batch: TesselateBatch) -> Option<GraphicsBatch> {
125 match batch {
126 TesselateBatch::Color => Some(GraphicsBatch {
127 shader: Some(self.colored_shader.clone()),
128 blending: GlowBlending::Alpha,
129 scissor: self.clip_stack.last().copied(),
130 ..Default::default()
131 }),
132 TesselateBatch::Image { id } => {
133 let id = AssetsManager::parse_image_id(&id).0;
134 Some(GraphicsBatch {
135 shader: Some(self.textured_shader.clone()),
136 textures: vec![(
137 self.assets
138 .textures
139 .get(id)
140 .map(|texture| texture.texture.clone())
141 .unwrap_or_else(|| self.missing_texture.clone()),
142 GlowTextureFiltering::Linear,
143 )],
144 blending: GlowBlending::Alpha,
145 scissor: self.clip_stack.last().copied(),
146 ..Default::default()
147 })
148 }
149 TesselateBatch::Text => Some(GraphicsBatch {
150 shader: Some(self.text_shader.clone()),
151 textures: vec![(self.glyphs_texture.clone(), GlowTextureFiltering::Linear)],
152 blending: GlowBlending::Alpha,
153 scissor: self.clip_stack.last().copied(),
154 ..Default::default()
155 }),
156 TesselateBatch::Procedural {
157 id,
158 images,
159 parameters,
160 } => Some(GraphicsBatch {
161 shader: self
162 .assets
163 .shaders
164 .get(&id)
165 .map(|shader| shader.shader.clone()),
166 uniforms: parameters
167 .into_iter()
168 .map(|(k, v)| (k.into(), GlowUniformValue::F1(v)))
169 .chain((0..images.len()).map(|index| {
170 (
171 if index > 0 {
172 format!("u_image{index}").into()
173 } else {
174 "u_image".into()
175 },
176 GlowUniformValue::I1(index as _),
177 )
178 }))
179 .chain(std::iter::once((
180 "u_projection_view".into(),
181 GlowUniformValue::M4(self.projection_view_matrix),
182 )))
183 .collect(),
184 textures: images
185 .into_iter()
186 .filter_map(|id| {
187 Some((
188 self.assets.textures.get(&id)?.texture.to_owned(),
189 GlowTextureFiltering::Linear,
190 ))
191 })
192 .collect(),
193 scissor: self.clip_stack.last().copied(),
194 ..Default::default()
195 }),
196 TesselateBatch::ClipPush { x, y, w, h } => {
197 self.clip_stack.push(vek::Rect {
198 x: x as _,
199 y: self.viewport_height - y as i32 - h as i32,
200 w: w as _,
201 h: h as _,
202 });
203 None
204 }
205 TesselateBatch::ClipPop => {
206 self.clip_stack.pop();
207 None
208 }
209 TesselateBatch::Debug => Some(GraphicsBatch {
210 shader: self.debug_shader.cloned(),
211 wireframe: true,
212 ..Default::default()
213 }),
214 }
215 }
216}
217
218pub fn setup(app: &mut Application) {
219 app.register_props::<CanvasProps>("CanvasProps");
220
221 app.register_component("canvas", FnWidget::pointer(canvas));
222}