1use cgmath::{Quaternion, Vector2, Vector3, Zero};
2use glyphon::*;
3use wgpu::MultisampleState;
4
5use crate::{component_app::EngineComponent, create_pipeline, math::{quaternion::QuaternionExt, transforms::Transform}, primitives::{mesh::Mesh, textures::Texture, transforms::TransformRaw, vertices::Vertex}, render::{pipelines::Pipeline, render_engine::RenderEngine}, utils::resources::Handle};
6
7use self::{elements::{ElementInfo, UIElement}, style::PositionSetting, uniforms::UIInstance};
8
9pub mod elements;
10pub mod uniforms;
11pub mod style;
12
13const VERTICES: &[Vertex] = &[
15 Vertex { position: [ -1.0, -1.0, 0.0 ], tex_coords: [ 0.0, 1.0 ], normal: [0.0, 0.0, 0.0] },
16 Vertex { position: [ 1.0, -1.0, 0.0 ], tex_coords: [ 1.0, 1.0 ], normal: [0.0, 0.0, 0.0] },
17 Vertex { position: [ -1.0, 1.0, 0.0 ], tex_coords: [ 0.0, 0.0 ], normal: [0.0, 0.0, 0.0] },
18 Vertex { position: [ 1.0, 1.0, 0.0 ], tex_coords: [ 1.0, 0.0 ], normal: [0.0, 0.0, 0.0] }
19];
20
21const INDICES: &[u16] = &[
23 0, 1, 2,
24 1, 3, 2
25];
26
27pub struct UIEngine {
29 mesh: Handle<Mesh>,
30 default_texture: Handle<Texture>,
31 pub elements: Vec<UIElement>,
32
33 font_system: FontSystem,
35 font_cache: SwashCache,
36 text_atlas: TextAtlas,
37 text_renderer: TextRenderer
38}
39
40#[derive(Debug)]
42pub struct UIRenderInfo {
43 pub position: Vector2<f32>,
44 pub size: Vector2<f32>,
45 pub display_size: Vector2<f32>
46}
47
48#[include_wgsl_oil::include_wgsl_oil("ui.wgsl")]
50mod ui_shader {}
51
52impl EngineComponent<&mut RenderEngine> for UIEngine {
53 fn create(engine: &mut RenderEngine) -> Self {
54 create_pipeline! {
55 NAME => "forte.ui",
56 ENGINE => engine,
57 SHADER => ui_shader::SOURCE,
58 BUFFER_LAYOUTS => [Vertex::desc(), UIInstance::desc()],
59 BIND_GROUPS => [Texture::BIND_LAYOUT],
60 HAS_DEPTH => false
61 }
62
63 let mesh = engine.create_mesh("ui_engine_mesh", VERTICES, INDICES);
64 let default_texture = engine.create_texture("ui.blank", include_bytes!("empty.png"));
65
66 let font_system = FontSystem::new();
68 let font_cache = SwashCache::new();
69 let mut text_atlas = TextAtlas::new(&engine.device, &engine.queue, engine.config.format);
70 let text_renderer = TextRenderer::new(&mut text_atlas, &engine.device, MultisampleState::default(), None);
71
72 Self {
73 mesh,
74 default_texture, elements: Vec::new(),
75 font_system, font_cache,
76 text_atlas, text_renderer
77 }
78 }
79
80 fn update(&mut self, render_engine: &mut RenderEngine) {
81 let size = Vector2 { x: render_engine.size.width as f32, y: render_engine.size.height as f32 };
82 let mut text_areas = Vec::<TextArea>::new();
83 update_ui(render_engine, &UIRenderInfo { position: Vector2::zero(), size, display_size: size }, &self.elements, &mut text_areas, 0.5);
84 let _ = self.text_renderer.prepare(
85 &render_engine.device,
86 &render_engine.queue,
87 &mut self.font_system,
88 &mut self.text_atlas,
89 Resolution { width: render_engine.config.width, height: render_engine.config.height },
90 text_areas,
91 &mut self.font_cache
92 );
93 }
94
95 fn render<'rpass>(&'rpass mut self, render_engine: &'rpass RenderEngine, pass: &mut wgpu::RenderPass<'rpass>) {
96 render_ui(render_engine, pass, render_engine.mesh(&self.mesh), render_engine.texture(&self.default_texture), &self.elements);
97 let _ = self.text_renderer.render(&self.text_atlas, pass);
98 }
99
100 fn start(&mut self, _: &mut RenderEngine) {}
101 fn exit(&mut self, _: &mut RenderEngine) {}
102}
103
104fn render_ui<'rpass>(engine: &'rpass RenderEngine, pass: &mut wgpu::RenderPass<'rpass>, mesh: &'rpass Mesh, default_texture: &'rpass Texture, elements: &'rpass [UIElement]) {
105 elements.iter().for_each(|element| {
106 let texture = match &element.info {
107 ElementInfo::Image(texture) => engine.texture(texture),
108 _ => default_texture
109 };
110 pass.set_bind_group(0, &texture.bind_group, &[]);
111 pass.set_vertex_buffer(0, mesh.vertex_buf.slice(..));
112 pass.set_vertex_buffer(1, element.buffer.slice(..));
113 pass.set_index_buffer(mesh.index_buf.slice(..), wgpu::IndexFormat::Uint16);
114 pass.draw_indexed(0 .. mesh.num_indices, 0, 0 .. 1);
115
116 render_ui(engine, pass, mesh, default_texture, &element.children);
117 });
118}
119
120fn update_ui<'a>(engine: &RenderEngine, info: &UIRenderInfo, elements: &'a [UIElement], text_areas: &mut Vec<TextArea<'a>>, layer: f32) {
121 elements.iter().for_each(|element| {
122 let (position, size) = calculate_position_size(element, info);
124 let new_info = UIRenderInfo { position, size, display_size: info.display_size };
125
126 let pos_x = size.x * 0.5 + position.x;
128 let pos_y = size.y * 0.5 + position.y;
129 let transform = Transform {
130 position: Vector3 {
131 x: 2.0 * (pos_x / info.display_size.x) - 1.0,
132 y: 2.0 * (pos_y / info.display_size.y) - 1.0,
133 z: layer
134 },
135 rotation: Quaternion::euler_deg_z(element.style.rotation),
136 scale: Vector3 {
137 x: size.x / info.display_size.x,
138 y: size.y / info.display_size.y,
139 z: 0.0
140 }
141 };
142
143 let raw_transform = TransformRaw::from_generic(&transform).model;
145 let instance = UIInstance([
146 raw_transform[0],
147 raw_transform[1],
148 raw_transform[2],
149 raw_transform[3],
150 element.style.color.to_array(),
151 element.style.border_color.to_array(),
152 [
153 element.style.round.size(&info.display_size) / f32::max(size.x, size.y),
154 element.style.border.size(&info.display_size) / f32::max(size.x, size.y),
155 0.0,
156 0.0
157 ]
158 ]);
159
160 engine.queue.write_buffer(&element.buffer, 0, bytemuck::cast_slice(&instance.0));
162
163 match &element.info {
165 ElementInfo::Text(buffer, color) => {
166 text_areas.push(TextArea {
167 buffer,
168 left: position.x + 5.0,
169 top: info.display_size.y - position.y - size.y,
170 scale: 1.0,
171 bounds: TextBounds {
172 left: 0,
173 top: 0,
174 right: size.x as i32,
175 bottom: size.y as i32,
176 },
177 default_color: *color
178 });
179 },
180 _ => {}
181 }
182
183 update_ui(engine, &new_info, &element.children, text_areas, layer - 0.05);
185 });
186}
187
188fn calculate_position_size(element: &UIElement, info: &UIRenderInfo) -> (Vector2<f32>, Vector2<f32>) {
190 let size = element.min_size(&info.display_size);
192
193 let mut position = Vector2 {
195 x: info.position.x + ((info.size.x - size.x) * 0.5),
196 y: info.position.y + ((info.size.y - size.y) * 0.5)
197 };
198
199 if element.style.left_set() {
201 let offset = element.style.left.size(&info.display_size);
202 match element.style.position_setting {
203 PositionSetting::Parent => {
204 position.x = info.position.x + offset;
205 },
206 PositionSetting::Absolute => {
207 position.x = offset;
208 }
209 }
210 }
211 else if element.style.right_set() {
213 let offset = element.style.right.size(&info.display_size);
214 match element.style.position_setting {
215 PositionSetting::Parent => {
216 position.x = info.position.x + info.size.x - size.x - offset;
217 },
218 PositionSetting::Absolute => {
219 position.x = info.display_size.x - size.x - offset;
220 }
221 }
222 }
223
224 if element.style.top_set() {
226 let offset = element.style.top.size(&info.display_size);
227 match element.style.position_setting {
228 PositionSetting::Parent => {
229 position.y = info.position.y + info.size.y - size.y - offset;
230 },
231 PositionSetting::Absolute => {
232 position.y = info.display_size.y - size.y - offset;
233 }
234 }
235 } else if element.style.bottom_set() {
236 let offset = element.style.bottom.size(&info.display_size);
237 position.y = offset;
238 match element.style.position_setting {
239 PositionSetting::Parent => {
240 position.y = info.position.y + offset;
241 },
242 PositionSetting::Absolute => {
243 position.y = offset;
244 }
245 }
246 }
247
248 (position, size)
249}