1#![deny(missing_docs)]
2
3pub use megaui;
58
59mod input;
60mod megaui_node;
61mod transform_node;
62
63use crate::{input::process_input, megaui_node::MegaUiNode, transform_node::MegaUiTransformNode};
64use bevy::{
65 app::{stage, AppBuilder, EventReader, Plugin},
66 asset::{Assets, Handle, HandleUntyped},
67 ecs::IntoSystem,
68 log,
69 reflect::TypeUuid,
70 render::{
71 pipeline::{
72 BlendDescriptor, BlendFactor, BlendOperation, ColorStateDescriptor, ColorWrite,
73 CompareFunction, CullMode, DepthStencilStateDescriptor, FrontFace, IndexFormat,
74 PipelineDescriptor, RasterizationStateDescriptor, StencilStateDescriptor,
75 StencilStateFaceDescriptor,
76 },
77 render_graph::{base, base::Msaa, RenderGraph, WindowSwapChainNode, WindowTextureNode},
78 shader::{Shader, ShaderStage, ShaderStages},
79 texture::{Extent3d, Texture, TextureDimension, TextureFormat},
80 },
81 window::{CursorMoved, ReceivedCharacter},
82};
83use megaui::Vector2;
84use std::collections::HashMap;
85
86pub const MEGAUI_PIPELINE_HANDLE: HandleUntyped =
88 HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 9404026720151354217);
89pub const MEGAUI_TRANSFORM_RESOURCE_BINDING_NAME: &str = "MegaUiTransform";
91pub const MEGAUI_TEXTURE_RESOURCE_BINDING_NAME: &str = "MegaUiTexture_texture";
93
94pub struct MegaUiPlugin;
96
97#[derive(Clone, Debug, PartialEq)]
99pub struct MegaUiSettings {
100 pub scale_factor: f64,
114}
115
116impl Default for MegaUiSettings {
117 fn default() -> Self {
118 Self { scale_factor: 1.0 }
119 }
120}
121
122pub struct MegaUiContext {
126 pub ui: megaui::Ui,
128 ui_draw_lists: Vec<megaui::DrawList>,
129 font_texture: Handle<Texture>,
130 megaui_textures: HashMap<u32, Handle<Texture>>,
131
132 mouse_position: (f32, f32),
133 cursor: EventReader<CursorMoved>,
134 received_character: EventReader<ReceivedCharacter>,
135}
136
137impl MegaUiContext {
138 fn new(ui: megaui::Ui, font_texture: Handle<Texture>) -> Self {
139 Self {
140 ui,
141 ui_draw_lists: Vec::new(),
142 font_texture,
143 megaui_textures: Default::default(),
144 mouse_position: (0.0, 0.0),
145 cursor: Default::default(),
146 received_character: Default::default(),
147 }
148 }
149
150 pub fn draw_window(
153 &mut self,
154 id: megaui::Id,
155 position: Vector2,
156 size: Vector2,
157 params: impl Into<Option<WindowParams>>,
158 f: impl FnOnce(&mut megaui::Ui),
159 ) {
160 let params = params.into();
161
162 megaui::widgets::Window::new(id, position, size)
163 .label(params.as_ref().map_or("", |params| ¶ms.label))
164 .titlebar(params.as_ref().map_or(true, |params| params.titlebar))
165 .movable(params.as_ref().map_or(true, |params| params.movable))
166 .close_button(params.as_ref().map_or(false, |params| params.close_button))
167 .ui(&mut self.ui, f);
168 }
169
170 pub fn set_megaui_texture(&mut self, id: u32, texture: Handle<Texture>) {
178 log::debug!("Set megaui texture: {:?}", texture);
179 self.megaui_textures.insert(id, texture);
180 }
181
182 pub fn remove_megaui_texture(&mut self, id: u32) {
184 let texture_handle = self.megaui_textures.remove(&id);
185 log::debug!("Remove megaui texture: {:?}", texture_handle);
186 }
187
188 fn remove_texture(&mut self, texture_handle: &Handle<Texture>) {
190 log::debug!("Removing megaui handles: {:?}", texture_handle);
191 self.megaui_textures = self
192 .megaui_textures
193 .iter()
194 .map(|(id, texture)| (*id, texture.clone()))
195 .filter(|(_, texture)| texture != texture_handle)
196 .collect();
197 }
198}
199
200#[derive(Debug, Clone, Eq, PartialEq)]
202pub struct WindowParams {
203 pub label: String,
205 pub movable: bool,
207 pub close_button: bool,
209 pub titlebar: bool,
211}
212
213impl Default for WindowParams {
214 fn default() -> WindowParams {
215 WindowParams {
216 label: "".to_string(),
217 movable: true,
218 close_button: false,
219 titlebar: true,
220 }
221 }
222}
223
224#[derive(Debug, Default, Clone, PartialEq)]
225struct WindowSize {
226 physical_width: f32,
227 physical_height: f32,
228 scale_factor: f32,
229}
230
231impl WindowSize {
232 fn new(physical_width: f32, physical_height: f32, scale_factor: f32) -> Self {
233 Self {
234 physical_width,
235 physical_height,
236 scale_factor,
237 }
238 }
239
240 #[inline]
241 fn width(&self) -> f32 {
242 self.physical_width / self.scale_factor
243 }
244
245 #[inline]
246 fn height(&self) -> f32 {
247 self.physical_height / self.scale_factor
248 }
249}
250
251impl MegaUiContext {
252 fn render_draw_lists(&mut self) {
253 self.ui_draw_lists.clear();
254 self.ui.render(&mut self.ui_draw_lists);
255 }
256}
257
258pub mod node {
260 pub const MEGAUI_PASS: &str = "megaui_pass";
262 pub const MEGAUI_TRANSFORM: &str = "megaui_transform";
264}
265
266impl Plugin for MegaUiPlugin {
267 fn build(&self, app: &mut AppBuilder) {
268 app.add_system_to_stage(stage::PRE_UPDATE, process_input.system());
269
270 let resources = app.resources_mut();
271
272 let ui = megaui::Ui::new();
273 let font_texture = {
274 let mut assets = resources.get_mut::<Assets<Texture>>().unwrap();
275 assets.add(Texture::new(
276 Extent3d::new(ui.font_atlas.texture.width, ui.font_atlas.texture.height, 1),
277 TextureDimension::D2,
278 ui.font_atlas.texture.data.clone(),
279 TextureFormat::Rgba8Unorm,
280 ))
281 };
282 resources.get_or_insert_with(MegaUiSettings::default);
283 resources.insert(WindowSize::new(0.0, 0.0, 0.0));
284 resources.insert_thread_local(MegaUiContext::new(ui, font_texture.clone()));
285
286 let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
287 let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap();
288 let msaa = resources.get::<Msaa>().unwrap();
289
290 pipelines.set_untracked(
291 MEGAUI_PIPELINE_HANDLE,
292 build_megaui_pipeline(&mut shaders, msaa.samples),
293 );
294 let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
295
296 render_graph.add_node(node::MEGAUI_PASS, MegaUiNode::new(&msaa, font_texture));
297 render_graph
298 .add_node_edge(base::node::MAIN_PASS, node::MEGAUI_PASS)
299 .unwrap();
300
301 render_graph
302 .add_slot_edge(
303 base::node::PRIMARY_SWAP_CHAIN,
304 WindowSwapChainNode::OUT_TEXTURE,
305 node::MEGAUI_PASS,
306 if msaa.samples > 1 {
307 "color_resolve_target"
308 } else {
309 "color_attachment"
310 },
311 )
312 .unwrap();
313
314 render_graph
315 .add_slot_edge(
316 base::node::MAIN_DEPTH_TEXTURE,
317 WindowTextureNode::OUT_TEXTURE,
318 node::MEGAUI_PASS,
319 "depth",
320 )
321 .unwrap();
322
323 if msaa.samples > 1 {
324 render_graph
325 .add_slot_edge(
326 base::node::MAIN_SAMPLED_COLOR_ATTACHMENT,
327 WindowSwapChainNode::OUT_TEXTURE,
328 node::MEGAUI_PASS,
329 "color_attachment",
330 )
331 .unwrap();
332 }
333
334 render_graph.add_system_node(node::MEGAUI_TRANSFORM, MegaUiTransformNode::new());
336 render_graph
337 .add_node_edge(node::MEGAUI_TRANSFORM, node::MEGAUI_PASS)
338 .unwrap();
339 }
340}
341
342fn build_megaui_pipeline(shaders: &mut Assets<Shader>, sample_count: u32) -> PipelineDescriptor {
343 PipelineDescriptor {
344 rasterization_state: Some(RasterizationStateDescriptor {
345 front_face: FrontFace::Cw,
346 cull_mode: CullMode::None,
347 depth_bias: 0,
348 depth_bias_slope_scale: 0.0,
349 depth_bias_clamp: 0.0,
350 clamp_depth: false,
351 }),
352 depth_stencil_state: Some(DepthStencilStateDescriptor {
353 format: TextureFormat::Depth32Float,
354 depth_write_enabled: true,
355 depth_compare: CompareFunction::LessEqual,
356 stencil: StencilStateDescriptor {
357 front: StencilStateFaceDescriptor::IGNORE,
358 back: StencilStateFaceDescriptor::IGNORE,
359 read_mask: 0,
360 write_mask: 0,
361 },
362 }),
363 color_states: vec![ColorStateDescriptor {
364 format: TextureFormat::default(),
365 color_blend: BlendDescriptor {
366 src_factor: BlendFactor::SrcAlpha,
367 dst_factor: BlendFactor::OneMinusSrcAlpha,
368 operation: BlendOperation::Add,
369 },
370 alpha_blend: BlendDescriptor {
371 src_factor: BlendFactor::OneMinusDstAlpha,
372 dst_factor: BlendFactor::One,
373 operation: BlendOperation::Add,
374 },
375 write_mask: ColorWrite::ALL,
376 }],
377 index_format: IndexFormat::Uint16,
378 sample_count,
379 ..PipelineDescriptor::new(ShaderStages {
380 vertex: shaders.add(Shader::from_glsl(
381 ShaderStage::Vertex,
382 if cfg!(target_arch = "wasm32") {
383 include_str!("megaui.es.vert")
384 } else {
385 include_str!("megaui.vert")
386 },
387 )),
388 fragment: Some(shaders.add(Shader::from_glsl(
389 ShaderStage::Fragment,
390 if cfg!(target_arch = "wasm32") {
391 include_str!("megaui.es.frag")
392 } else {
393 include_str!("megaui.frag")
394 },
395 ))),
396 })
397 }
398}