diff --git a/bevy_kayak_ui/src/bevy_context.rs b/bevy_kayak_ui/src/bevy_context.rs index 6708d8e761dffcdf631338eef44594b5800483d2..5eb9cfe732618bfef4450c4c14a44fb4a3a7e7a8 100644 --- a/bevy_kayak_ui/src/bevy_context.rs +++ b/bevy_kayak_ui/src/bevy_context.rs @@ -6,11 +6,11 @@ use kayak_core::{ styles::{Style, StyleProp, Units}, }; -pub struct BevyContext<'a> { - pub kayak_context: Arc<RwLock<KayakContext<'a>>>, +pub struct BevyContext { + pub kayak_context: Arc<RwLock<KayakContext>>, } -impl<'a> BevyContext<'a> { +impl BevyContext { pub fn new<F: Fn(&mut Style, &mut KayakContext)>(width: f32, height: f32, f: F) -> Self { let mut app_styles = Style { render_command: StyleProp::Value(RenderCommand::Window), diff --git a/bevy_kayak_ui/src/lib.rs b/bevy_kayak_ui/src/lib.rs index ced795308fcbea7d33ee85587415dca11704289c..1f1d45ff7c38e3add19967a1d7e743095733ed21 100644 --- a/bevy_kayak_ui/src/lib.rs +++ b/bevy_kayak_ui/src/lib.rs @@ -1,9 +1,7 @@ use bevy::{ input::{mouse::MouseButtonInput, ElementState}, math::Vec2, - prelude::{ - EventReader, IntoExclusiveSystem, IntoSystem, MouseButton, Mut, Plugin, Res, ResMut, World, - }, + prelude::{EventReader, IntoExclusiveSystem, MouseButton, Plugin, Res, World}, render2::color::Color, window::{CursorMoved, Windows}, }; @@ -14,7 +12,8 @@ mod render; pub use bevy_context::BevyContext; pub use camera::*; -use kayak_core::{context::GlobalState, InputEvent}; +use kayak_core::InputEvent; +pub use render::unified::image::ImageManager; #[derive(Default)] pub struct BevyKayakUIPlugin; @@ -23,6 +22,7 @@ impl Plugin for BevyKayakUIPlugin { fn build(&self, app: &mut bevy::prelude::App) { app.add_plugin(render::BevyKayakUIRenderPlugin) .add_plugin(camera::KayakUICameraPlugin) + .add_system(process_events) .add_system(update.exclusive_system()); } } @@ -31,61 +31,50 @@ pub(crate) fn to_bevy_color(color: &kayak_core::color::Color) -> Color { Color::rgba(color.r, color.g, color.b, color.a) } -pub struct WrappedWorld<'a> { - world: &'a mut World, -} - -impl<'a> GlobalState for WrappedWorld<'a> {} - pub fn update(world: &mut World) { - let window_size = { - let windows = world.get_resource::<Windows>().unwrap(); - if let Some(window) = windows.get_primary() { - Vec2::new(window.width(), window.height()) - } else { - panic!("Couldn't find primary window!"); - } - }; - - let bevy_context = world.remove_resource::<BevyContext<'static>>().unwrap(); + let bevy_context = world.remove_resource::<BevyContext>().unwrap(); if let Ok(mut context) = bevy_context.kayak_context.write() { - let mut wrapped_world = WrappedWorld { world }; - context.render(&mut wrapped_world); + context.set_global_state(std::mem::take(world)); + context.render(); + *world = context.take_global_state::<World>().unwrap() } + world.insert_resource(bevy_context); +} + +pub fn process_events( + bevy_context: Res<BevyContext>, + windows: Res<Windows>, + mut cursor_moved_events: EventReader<CursorMoved>, + mut mouse_button_input_events: EventReader<MouseButtonInput>, +) { + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + if let Ok(mut context) = bevy_context.kayak_context.write() { let mut input_events = Vec::new(); - { - let mut cursor_moved_events = world - .get_resource_mut::<EventReader<CursorMoved>>() - .unwrap(); - for event in cursor_moved_events.iter() { - input_events.push(InputEvent::MouseMoved(( - event.position.x as f32, - window_size.y - event.position.y as f32, - ))); - } + for event in cursor_moved_events.iter() { + input_events.push(InputEvent::MouseMoved(( + event.position.x as f32, + window_size.y - event.position.y as f32, + ))); } - { - let mut mouse_button_input_events = world - .get_resource_mut::<EventReader<MouseButtonInput>>() - .unwrap(); - for event in mouse_button_input_events.iter() { - match event.button { - MouseButton::Left => { - if event.state == ElementState::Pressed { - input_events.push(InputEvent::MouseLeftClick); - } + for event in mouse_button_input_events.iter() { + match event.button { + MouseButton::Left => { + if event.state == ElementState::Pressed { + input_events.push(InputEvent::MouseLeftClick); } - _ => {} } + _ => {} } } context.process_events(input_events); } - - world.insert_resource(bevy_context); } diff --git a/bevy_kayak_ui/src/render/mod.rs b/bevy_kayak_ui/src/render/mod.rs index 6b41bf0457c2946305253858b7e5199855b0cba4..970ea9c859b141e90548d502c16562a44cb9a9d0 100644 --- a/bevy_kayak_ui/src/render/mod.rs +++ b/bevy_kayak_ui/src/render/mod.rs @@ -19,7 +19,7 @@ use self::ui_pass::TransparentUI; mod ui_pass; mod ui_pass_driver; -mod unified; +pub mod unified; pub mod node { pub const UI_PASS_DEPENDENCIES: &str = "ui_pass_dependencies"; diff --git a/bevy_kayak_ui/src/render/unified/font/extract.rs b/bevy_kayak_ui/src/render/unified/font/extract.rs index 585f124a89a3fc9f9bb2299df7e95597e5980908..5948efc52b5af61e915b904c7be8bf431dfcf9ca 100644 --- a/bevy_kayak_ui/src/render/unified/font/extract.rs +++ b/bevy_kayak_ui/src/render/unified/font/extract.rs @@ -14,7 +14,7 @@ use super::{font::KayakFont, font_mapping::FontMapping}; pub fn extract_texts( mut commands: Commands, - context: Res<BevyContext<'static>>, + context: Res<BevyContext>, mut fonts: ResMut<Assets<KayakFont>>, font_mapping: Res<FontMapping>, ) { @@ -137,6 +137,7 @@ pub fn extract_texts( quad_type: UIQuadType::Text, type_index: 0, border_radius: (0.0, 0.0, 0.0, 0.0), + image: None, }, }); diff --git a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs index f45898b206eac724845a3ff152defc20f0c621b3..572627267a4a4011ecd6518ec43fb8bb0906daa7 100644 --- a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs +++ b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs @@ -111,7 +111,7 @@ impl FontTextureCache { resource: BindingResource::Sampler(&gpu_image.sampler), }, ], - layout: &pipeline.image_layout, + layout: &pipeline.font_image_layout, }); self.bind_groups @@ -333,7 +333,7 @@ impl FontTextureCache { resource: BindingResource::Sampler(&gpu_image.sampler), }, ], - layout: &pipeline.image_layout, + layout: &pipeline.font_image_layout, }); bind_groups.insert(font_handle.clone_weak(), binding); diff --git a/bevy_kayak_ui/src/render/unified/image/extract.rs b/bevy_kayak_ui/src/render/unified/image/extract.rs new file mode 100644 index 0000000000000000000000000000000000000000..fee53b688ede10e56af934611176b295eb0c97c5 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/image/extract.rs @@ -0,0 +1,58 @@ +use bevy::{ + math::Vec2, + prelude::{Commands, Res}, + render2::color::Color, + sprite2::Rect, +}; +use kayak_core::render_primitive::RenderPrimitive; + +use crate::{ + render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType}, + BevyContext, ImageManager, +}; + +pub fn extract_images( + mut commands: Commands, + context: Res<BevyContext>, + image_manager: Res<ImageManager>, +) { + let render_commands = if let Ok(context) = context.kayak_context.read() { + context.widget_manager.build_render_primitives() + } else { + vec![] + }; + + let image_commands: Vec<&RenderPrimitive> = render_commands + .iter() + .filter(|command| matches!(command, RenderPrimitive::Image { .. })) + .collect::<Vec<_>>(); + + let mut extracted_quads = Vec::new(); + for render_primitive in image_commands { + let (layout, handle) = match render_primitive { + RenderPrimitive::Image { layout, handle } => (layout, handle), + _ => panic!(""), + }; + + extracted_quads.push(ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, layout.posy), + max: Vec2::new(layout.posx + layout.width, layout.posy + layout.height), + }, + color: Color::WHITE, + vertex_index: 0, + char_id: 0, + z_index: layout.z_index, + font_handle: None, + quad_type: UIQuadType::Image, + type_index: 0, + border_radius: (0.0, 0.0, 0.0, 0.0), + image: image_manager + .get_handle(handle) + .and_then(|a| Some(a.clone_weak())), + }, + }); + } + commands.spawn_batch(extracted_quads); +} diff --git a/bevy_kayak_ui/src/render/unified/image/image_manager.rs b/bevy_kayak_ui/src/render/unified/image/image_manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..b92b08e5ce9a0db06b16b2399cbd93aefce1c4db --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/image/image_manager.rs @@ -0,0 +1,34 @@ +use bevy::{prelude::Handle, render2::texture::Image, utils::HashMap}; + +#[derive(Debug, Clone)] +pub struct ImageManager { + count: u16, + mapping: HashMap<u16, Handle<Image>>, + reverse_mapping: HashMap<Handle<Image>, u16>, +} + +impl ImageManager { + pub fn new() -> Self { + Self { + count: 0, + mapping: HashMap::default(), + reverse_mapping: HashMap::default(), + } + } + + pub fn get(&mut self, image_handle: &Handle<Image>) -> u16 { + if let Some(id) = self.reverse_mapping.get(image_handle) { + return *id; + } else { + let id = self.count; + self.count += 1; + self.mapping.insert(id, image_handle.clone_weak()); + self.reverse_mapping.insert(image_handle.clone_weak(), id); + return id; + } + } + + pub fn get_handle(&self, id: &u16) -> Option<&Handle<Image>> { + self.mapping.get(id) + } +} diff --git a/bevy_kayak_ui/src/render/unified/image/mod.rs b/bevy_kayak_ui/src/render/unified/image/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..549b0970c0884f0c0d46223f54a25a8a2ef87cbd --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/image/mod.rs @@ -0,0 +1,19 @@ +use bevy::{ + prelude::Plugin, + render2::{RenderApp, RenderStage}, +}; + +mod extract; +mod image_manager; +pub use image_manager::ImageManager; + +pub struct ImageRendererPlugin; + +impl Plugin for ImageRendererPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.insert_resource(ImageManager::new()); + + let render_app = app.sub_app(RenderApp); + render_app.add_system_to_stage(RenderStage::Extract, extract::extract_images); + } +} diff --git a/bevy_kayak_ui/src/render/unified/mod.rs b/bevy_kayak_ui/src/render/unified/mod.rs index 37aeb58243aa976470811d76a83d949f8754ca34..c6ce51ce23c0d449835082a6f5eab7ec51138405 100644 --- a/bevy_kayak_ui/src/render/unified/mod.rs +++ b/bevy_kayak_ui/src/render/unified/mod.rs @@ -9,7 +9,10 @@ use crate::render::{ unified::pipeline::{DrawUI, QuadMeta, UnifiedPipeline}, }; +use self::pipeline::ImageBindGroups; + pub mod font; +pub mod image; mod pipeline; mod quad; @@ -26,6 +29,7 @@ impl Plugin for UnifiedRenderPlugin { let render_app = app.sub_app(RenderApp); render_app + .init_resource::<ImageBindGroups>() .init_resource::<UnifiedPipeline>() .init_resource::<QuadMeta>() .add_system_to_stage(RenderStage::Prepare, pipeline::prepare_quads) @@ -41,6 +45,7 @@ impl Plugin for UnifiedRenderPlugin { .add(draw_quad); app.add_plugin(font::TextRendererPlugin) - .add_plugin(quad::QuadRendererPlugin); + .add_plugin(quad::QuadRendererPlugin) + .add_plugin(image::ImageRendererPlugin); } } diff --git a/bevy_kayak_ui/src/render/unified/pipeline.rs b/bevy_kayak_ui/src/render/unified/pipeline.rs index f110ca4fd630842b5def4579c9e89c7c55378df9..483983c32551ca37914d26473d9c1e3b63fede5b 100644 --- a/bevy_kayak_ui/src/render/unified/pipeline.rs +++ b/bevy_kayak_ui/src/render/unified/pipeline.rs @@ -8,22 +8,26 @@ use bevy::{ prelude::{Bundle, Component, Entity, FromWorld, Handle, Query, Res, ResMut, World}, render2::{ color::Color, + render_asset::RenderAssets, render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, - BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent, - BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages, - BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, DynamicUniformVec, - FragmentState, FrontFace, MultisampleState, PolygonMode, PrimitiveState, - PrimitiveTopology, RenderPipelineCache, RenderPipelineDescriptor, Shader, ShaderStages, - TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute, - VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, + BlendComponent, BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, + BufferUsages, BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, + DynamicUniformVec, Extent3d, FragmentState, FrontFace, MultisampleState, PolygonMode, + PrimitiveState, PrimitiveTopology, RenderPipelineCache, RenderPipelineDescriptor, + SamplerDescriptor, Shader, ShaderStages, TextureDescriptor, TextureDimension, + TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor, + TextureViewDimension, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, + VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage}, + texture::{BevyDefault, GpuImage, Image}, view::{ViewUniformOffset, ViewUniforms}, }, sprite2::Rect, + utils::HashMap, }; use bytemuck::{Pod, Zeroable}; use crevice::std140::AsStd140; @@ -35,9 +39,11 @@ use crate::render::ui_pass::TransparentUI; pub struct UnifiedPipeline { view_layout: BindGroupLayout, types_layout: BindGroupLayout, - pub(crate) image_layout: BindGroupLayout, + pub(crate) font_image_layout: BindGroupLayout, + image_layout: BindGroupLayout, pipeline: CachedPipelineId, empty_font_texture: (GpuImage, BindGroup), + default_image: (GpuImage, BindGroup), } const QUAD_VERTEX_POSITIONS: &[Vec3] = &[ @@ -87,6 +93,33 @@ impl FromWorld for UnifiedPipeline { label: Some("ui_types_layout"), }); + // Used by fonts + let font_image_layout = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2Array, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, + ], + label: Some("text_image_layout"), + }); + let image_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ BindGroupLayoutEntry { @@ -95,7 +128,7 @@ impl FromWorld for UnifiedPipeline { ty: BindingType::Texture { multisampled: false, sample_type: TextureSampleType::Float { filterable: false }, - view_dimension: TextureViewDimension::D2Array, + view_dimension: TextureViewDimension::D2, }, count: None, }, @@ -109,7 +142,7 @@ impl FromWorld for UnifiedPipeline { count: None, }, ], - label: Some("text_image_layout"), + label: Some("image_layout"), }); let vertex_buffer_layout = VertexBufferLayout { @@ -139,7 +172,7 @@ impl FromWorld for UnifiedPipeline { ], }; - let empty_font_texture = FontTextureCache::get_empty(&render_device, &image_layout); + let empty_font_texture = FontTextureCache::get_empty(&render_device, &font_image_layout); let pipeline_desc = RenderPipelineDescriptor { vertex: VertexState { @@ -171,8 +204,9 @@ impl FromWorld for UnifiedPipeline { }), layout: Some(vec![ view_layout.clone(), - image_layout.clone(), + font_image_layout.clone(), types_layout.clone(), + image_layout.clone(), ]), primitive: PrimitiveState { front_face: FrontFace::Ccw, @@ -189,15 +223,68 @@ impl FromWorld for UnifiedPipeline { mask: !0, alpha_to_coverage_enabled: false, }, - label: Some("quad_pipeline".into()), + label: Some("unified_pipeline".into()), + }; + + let texture_descriptor = TextureDescriptor { + label: Some("font_texture_array"), + size: Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8UnormSrgb, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }; + + let sampler_descriptor = SamplerDescriptor::default(); + + let texture = render_device.create_texture(&texture_descriptor); + let sampler = render_device.create_sampler(&sampler_descriptor); + + let texture_view = texture.create_view(&TextureViewDescriptor { + label: Some("font_texture_array_view"), + format: None, + dimension: Some(TextureViewDimension::D2), + aspect: bevy::render2::render_resource::TextureAspect::All, + base_mip_level: 0, + base_array_layer: 0, + mip_level_count: None, + array_layer_count: None, + }); + + let image = GpuImage { + texture, + sampler, + texture_view, }; + let binding = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("text_image_bind_group"), + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&image.sampler), + }, + ], + layout: &image_layout, + }); + UnifiedPipeline { pipeline: pipeline_cache.queue(pipeline_desc), view_layout, - image_layout, + font_image_layout, empty_font_texture, types_layout, + image_layout, + default_image: (image, binding), } } } @@ -211,6 +298,7 @@ pub struct ExtractQuadBundle { pub enum UIQuadType { Quad, Text, + Image, } #[derive(Component)] @@ -224,6 +312,7 @@ pub struct ExtractedQuad { pub quad_type: UIQuadType, pub type_index: u32, pub border_radius: (f32, f32, f32, f32), + pub image: Option<Handle<Image>>, } #[repr(C)] @@ -259,6 +348,11 @@ impl Default for QuadMeta { } } +#[derive(Default)] +pub struct ImageBindGroups { + values: HashMap<Handle<Image>, BindGroup>, +} + pub fn prepare_quads( render_device: Res<RenderDevice>, render_queue: Res<RenderQueue>, @@ -266,7 +360,7 @@ pub fn prepare_quads( mut extracted_quads: Query<&mut ExtractedQuad>, ) { let extracted_sprite_len = extracted_quads.iter_mut().len(); - // dont create buffers when there are no quads + // don't create buffers when there are no quads if extracted_sprite_len == 0 { return; } @@ -275,6 +369,7 @@ pub fn prepare_quads( sprite_meta.types_buffer.reserve(2, &render_device); let quad_type_offset = sprite_meta.types_buffer.push(QuadType { t: 0 }); let text_type_offset = sprite_meta.types_buffer.push(QuadType { t: 1 }); + let image_type_offset = sprite_meta.types_buffer.push(QuadType { t: 2 }); sprite_meta .types_buffer .write_buffer(&render_device, &render_queue); @@ -292,6 +387,7 @@ pub fn prepare_quads( match extracted_sprite.quad_type { UIQuadType::Quad => extracted_sprite.type_index = quad_type_offset, UIQuadType::Text => extracted_sprite.type_index = text_type_offset, + UIQuadType::Image => extracted_sprite.type_index = image_type_offset, }; let bottom_left = Vec4::new( @@ -362,6 +458,9 @@ pub fn queue_quads( quad_pipeline: Res<UnifiedPipeline>, mut extracted_sprites: Query<(Entity, &ExtractedQuad)>, mut views: Query<&mut RenderPhase<TransparentUI>>, + mut image_bind_groups: ResMut<ImageBindGroups>, + unified_pipeline: Res<UnifiedPipeline>, + gpu_images: Res<RenderAssets<Image>>, ) { if let Some(type_binding) = sprite_meta.types_buffer.binding() { sprite_meta.types_bind_group = @@ -384,9 +483,34 @@ pub fn queue_quads( label: Some("quad_view_bind_group"), layout: &quad_pipeline.view_layout, })); + let draw_quad = draw_functions.read().get_id::<DrawUI>().unwrap(); for mut transparent_phase in views.iter_mut() { for (entity, quad) in extracted_sprites.iter_mut() { + if let Some(image_handle) = quad.image.as_ref() { + image_bind_groups + .values + .entry(image_handle.clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&image_handle).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView( + &gpu_image.texture_view, + ), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("ui_image_bind_group"), + layout: &unified_pipeline.image_layout, + }) + }); + } transparent_phase.add(TransparentUI { draw_function: draw_quad, pipeline: quad_pipeline.pipeline, @@ -404,6 +528,7 @@ pub struct DrawUI { SRes<UnifiedPipeline>, SRes<RenderPipelineCache>, SRes<FontTextureCache>, + SRes<ImageBindGroups>, SQuery<Read<ViewUniformOffset>>, SQuery<Read<ExtractedQuad>>, )>, @@ -425,8 +550,16 @@ impl Draw<TransparentUI> for DrawUI { view: Entity, item: &TransparentUI, ) { - let (quad_meta, unified_pipeline, pipelines, font_texture_cache, views, quads) = - self.params.get(world); + let ( + quad_meta, + unified_pipeline, + pipelines, + font_texture_cache, + image_bind_groups, + views, + quads, + ) = self.params.get(world); + let view_uniform = views.get(view).unwrap(); let quad_meta = quad_meta.into_inner(); let extracted_quad = quads.get(item.entity).unwrap(); @@ -445,20 +578,31 @@ impl Draw<TransparentUI> for DrawUI { &[extracted_quad.type_index], ); + let unified_pipeline = unified_pipeline.into_inner(); if let Some(font_handle) = extracted_quad.font_handle.as_ref() { if let Some(image_bindings) = font_texture_cache.into_inner().bind_groups.get(font_handle) { pass.set_bind_group(1, image_bindings, &[]); } else { - pass.set_bind_group( - 1, - &unified_pipeline.into_inner().empty_font_texture.1, - &[], - ); + pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]); } } else { - pass.set_bind_group(1, &unified_pipeline.into_inner().empty_font_texture.1, &[]); + pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]); + } + + if let Some(image_handle) = extracted_quad.image.as_ref() { + pass.set_bind_group( + 3, + &image_bind_groups + .into_inner() + .values + .get(image_handle) + .unwrap(), + &[], + ); + } else { + pass.set_bind_group(3, &unified_pipeline.default_image.1, &[]); } pass.draw( diff --git a/bevy_kayak_ui/src/render/unified/quad/extract.rs b/bevy_kayak_ui/src/render/unified/quad/extract.rs index 1290ae56286f9dbb7482a426ef7a6bf21aeddcf3..b81a7d0d9e18bec63f715dc0d4b210e7b61487b9 100644 --- a/bevy_kayak_ui/src/render/unified/quad/extract.rs +++ b/bevy_kayak_ui/src/render/unified/quad/extract.rs @@ -10,7 +10,7 @@ use crate::{ to_bevy_color, BevyContext, }; -pub fn extract_quads(mut commands: Commands, context: Res<BevyContext<'static>>) { +pub fn extract_quads(mut commands: Commands, context: Res<BevyContext>) { let render_commands = if let Ok(context) = context.kayak_context.read() { context.widget_manager.build_render_primitives() } else { @@ -47,6 +47,7 @@ pub fn extract_quads(mut commands: Commands, context: Res<BevyContext<'static>>) quad_type: UIQuadType::Quad, type_index: 0, border_radius: *border_radius, + image: None, }, }); } diff --git a/bevy_kayak_ui/src/render/unified/shader.wgsl b/bevy_kayak_ui/src/render/unified/shader.wgsl index 90a5af20ebd89422ce43604ca5bf70c367e7f87c..92b5463abf2909538a8d7ec6f1278d0961aa7f6d 100644 --- a/bevy_kayak_ui/src/render/unified/shader.wgsl +++ b/bevy_kayak_ui/src/render/unified/shader.wgsl @@ -13,7 +13,6 @@ struct QuadType { [[group(2), binding(0)]] var<uniform> quad_type: QuadType; - struct VertexOutput { [[builtin(position)]] position: vec4<f32>; [[location(0)]] color: vec4<f32>; @@ -44,12 +43,16 @@ fn vertex( } [[group(1), binding(0)]] -var sprite_texture: texture_2d_array<f32>; +var font_texture: texture_2d_array<f32>; [[group(1), binding(1)]] -var sprite_sampler: sampler; +var font_sampler: sampler; -let RADIUS: f32 = 0.1; +[[group(3), binding(0)]] +var image_texture: texture_2d<f32>; +[[group(3), binding(1)]] +var image_sampler: sampler; +let RADIUS: f32 = 0.1; fn sd_box_rounded( frag_coord: vec2<f32>, @@ -71,9 +74,6 @@ fn sd_box_rounded( [[stage(fragment)]] fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { - var pxRange = 2.5; - var tex_dimensions = textureDimensions(sprite_texture); - var msdfUnit = vec2<f32>(pxRange, pxRange) / vec2<f32>(f32(tex_dimensions.x), f32(tex_dimensions.y)); if (quad_type.t == 0) { var dist = sd_box_rounded( in.position.xy, @@ -89,7 +89,10 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { return vec4<f32>(in.color.rgb, dist); } if (quad_type.t == 1) { - var x = textureSample(sprite_texture, sprite_sampler, vec2<f32>(in.uv.x, 1.0 - in.uv.y), i32(in.uv.z)); + var px_range = 2.5; + var tex_dimensions = textureDimensions(font_texture); + var msdf_unit = vec2<f32>(px_range, px_range) / vec2<f32>(f32(tex_dimensions.x), f32(tex_dimensions.y)); + var x = textureSample(font_texture, font_sampler, vec2<f32>(in.uv.x, 1.0 - in.uv.y), i32(in.uv.z)); var v = max(min(x.r, x.g), min(max(x.r, x.g), x.b)); var c = v; //remap(v); @@ -103,10 +106,14 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { // var w = fwidth(c); // var a = smoothStep(0.5 - w, 0.5 + w, c); - var sigDist = (c - 0.5) * dot(msdfUnit, 0.5 / fwidth(in.uv.xy)); - var a = clamp(sigDist + 0.5, 0.0, 1.0); + var sig_dist = (c - 0.5) * dot(msdf_unit, 0.5 / fwidth(in.uv.xy)); + var a = clamp(sig_dist + 0.5, 0.0, 1.0); return vec4<f32>(in.color.rgb, a); } + if (quad_type.t == 2) { + var color = textureSample(image_texture, image_sampler, vec2<f32>(in.uv.x, in.uv.y)); + return vec4<f32>(color.rgb * in.color.rgb, color.a * in.color.a); + } return in.color; } \ No newline at end of file diff --git a/examples/counter.rs b/examples/counter.rs index cc68c29f9d3e098742bb8abda0c0612ba704cb7e..2d946636fd5a0c8df3f5ea2a756004935f95e975 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -7,6 +7,7 @@ use bevy::{ use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, UICameraBundle}; use kayak_components::{Button, Text, Window}; use kayak_core::{ + context::KayakContext, styles::{Style, StyleProp, Units}, EventType, Index, OnEvent, }; @@ -14,7 +15,7 @@ use kayak_ui::components::App; use kayak_ui::core::{rsx, widget}; #[widget] -fn Counter() { +fn Counter(context: &mut KayakContext) { let count = { let x = context.create_state(0i32).unwrap(); *x diff --git a/examples/image.rs b/examples/image.rs new file mode 100644 index 0000000000000000000000000000000000000000..82b571488c00812cdf44bd7713a2d89daec7beed --- /dev/null +++ b/examples/image.rs @@ -0,0 +1,55 @@ +use bevy::{ + math::Vec2, + prelude::{App as BevyApp, AssetServer, Commands, Handle, Res, ResMut}, + window::{WindowDescriptor, Windows}, + PipelinedDefaultPlugins, +}; +use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, ImageManager, UICameraBundle}; +use kayak_components::Image; +use kayak_core::Index; +use kayak_ui::components::App; +use kayak_ui::core::rsx; + +fn startup( + mut commands: Commands, + windows: Res<Windows>, + asset_server: Res<AssetServer>, + mut image_manager: ResMut<ImageManager>, +) { + commands.spawn_bundle(UICameraBundle::new()); + + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + + let handle: Handle<bevy::render2::texture::Image> = asset_server.load("panel.png"); + let ui_image_handle = image_manager.get(&handle); + + let context = BevyContext::new(window_size.x, window_size.y, |styles, context| { + // Hack to trick the proc macro for right now.. + let parent_id: Option<Index> = None; + rsx! { + <App styles={Some(styles.clone())}> + <Image handle={ui_image_handle} /> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} diff --git a/kayak_components/src/image.rs b/kayak_components/src/image.rs index f67f2934983a6a89ff3914bb366331ec90a96587..39cd65c8b5d4c25aa3c4edf30987d5b1ae440dd8 100644 --- a/kayak_components/src/image.rs +++ b/kayak_components/src/image.rs @@ -1,9 +1,14 @@ -use kayak_core::{component, rsx, Render, Update}; +use kayak_core::{ + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, Children, +}; -#[component] -pub fn Image<Children: Render + Update + Clone>(handle: u16, children: Children) { +#[widget] +pub fn Image(handle: u16, children: Children) { *styles = Some(Style { - render_command: StyleProp::Value(RenderCommand::Image { handle }), + render_command: StyleProp::Value(RenderCommand::Image { handle: *handle }), ..styles.clone().unwrap_or_default() }); diff --git a/kayak_components/src/lib.rs b/kayak_components/src/lib.rs index a9b99766d6e61c29c1503cd17f00736729100b40..47472bd1936959d912f6b79d91d9e5cf40feb7c6 100644 --- a/kayak_components/src/lib.rs +++ b/kayak_components/src/lib.rs @@ -2,6 +2,7 @@ mod app; mod background; mod button; mod clip; +mod image; mod text; mod window; @@ -9,5 +10,6 @@ pub use app::*; pub use background::*; pub use button::*; pub use clip::*; +pub use image::*; pub use text::*; pub use window::*; diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs index bcd89da329aec8ecaf96a8c2d6f6af4f066e5e6d..7e39265f163bc81aef7ab29994743e58ea513ced 100644 --- a/kayak_core/src/context.rs +++ b/kayak_core/src/context.rs @@ -1,21 +1,17 @@ -use std::collections::HashMap; - -use as_any::AsAny; use resources::Ref; +use std::collections::HashMap; use crate::{node::NodeIndex, widget_manager::WidgetManager, Event, EventType, Index, InputEvent}; -pub trait GlobalState: Send + Sync {} - -pub struct KayakContext<'a> { +pub struct KayakContext { component_states: HashMap<crate::Index, resources::Resources>, current_id: crate::Index, pub widget_manager: WidgetManager, last_mouse_position: (f32, f32), - global_state: Option<&'a mut dyn GlobalState>, + pub global_state: resources::Resources, } -impl<'a> std::fmt::Debug for KayakContext<'a> { +impl std::fmt::Debug for KayakContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("KayakContext") .field("current_id", &self.current_id) @@ -23,14 +19,14 @@ impl<'a> std::fmt::Debug for KayakContext<'a> { } } -impl<'a> KayakContext<'a> { +impl KayakContext { pub fn new() -> Self { Self { component_states: HashMap::new(), current_id: crate::Index::default(), widget_manager: WidgetManager::new(), last_mouse_position: (0.0, 0.0), - global_state: None, + global_state: resources::Resources::default(), } } @@ -83,9 +79,21 @@ impl<'a> KayakContext<'a> { } } - pub fn render(&mut self, global_state: &'a mut dyn GlobalState) { - self.global_state = Some(global_state); + pub fn set_global_state<T: resources::Resource>(&mut self, state: T) { + self.global_state.insert(state); + } + pub fn get_global_state<T: resources::Resource>( + &mut self, + ) -> Result<resources::RefMut<T>, resources::CantGetResource> { + self.global_state.get_mut::<T>() + } + + pub fn take_global_state<T: resources::Resource>(&mut self) -> Option<T> { + self.global_state.remove::<T>() + } + + pub fn render(&mut self) { let dirty_nodes = self.widget_manager.dirty_nodes.clone(); for node_index in dirty_nodes { if self @@ -104,8 +112,6 @@ impl<'a> KayakContext<'a> { self.widget_manager.render(); self.widget_manager.calculate_layout(); - - self.global_state = None; } pub fn process_events(&mut self, input_events: Vec<InputEvent>) { diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs index 268b555f0747cfb9eebce923067bba9fa3f08478..fe2cc7fdd442504763f3577a545d658567a24908 100644 --- a/kayak_core/src/lib.rs +++ b/kayak_core/src/lib.rs @@ -22,15 +22,10 @@ pub use input_event::*; pub use kayak_render_macros::{render, rsx, widget}; pub use widget::Widget; -pub type Children = Option< - Arc< - dyn for<'global_state> Fn( - Option<crate::Index>, - &mut crate::context::KayakContext, - ) + Send - + Sync, - >, ->; +pub use resources::Resources; + +pub type Children = + Option<Arc<dyn Fn(Option<crate::Index>, &mut crate::context::KayakContext) + Send + Sync>>; #[derive(Clone)] pub struct OnEvent( diff --git a/kayak_core/src/render_primitive.rs b/kayak_core/src/render_primitive.rs index 433c21687c34026412005f73906c85a24e62d140..457c75e4cfb5791651a653b2847281255fe519fc 100644 --- a/kayak_core/src/render_primitive.rs +++ b/kayak_core/src/render_primitive.rs @@ -24,6 +24,7 @@ pub enum RenderPrimitive { font: u16, }, Image { + layout: Rect, handle: u16, }, } @@ -34,6 +35,7 @@ impl RenderPrimitive { RenderPrimitive::Clip { layout, .. } => *layout = new_layout, RenderPrimitive::Quad { layout, .. } => *layout = new_layout, RenderPrimitive::Text { layout, .. } => *layout = new_layout, + RenderPrimitive::Image { layout, .. } => *layout = new_layout, _ => (), } } @@ -71,7 +73,10 @@ impl From<&Style> for RenderPrimitive { content, font, }, - RenderCommand::Image { handle } => Self::Image { handle }, + RenderCommand::Image { handle } => Self::Image { + layout: Rect::default(), + handle, + }, } } } diff --git a/kayak_render_macros/src/widget.rs b/kayak_render_macros/src/widget.rs index 96d0c703d7621ead8ad5486a71f92e408038bbb9..1368e1875e472706f59f014cb63c7f042cde7c8e 100644 --- a/kayak_render_macros/src/widget.rs +++ b/kayak_render_macros/src/widget.rs @@ -10,7 +10,6 @@ use crate::{tags::OpenTag, widget_attributes::WidgetAttributes}; #[derive(Debug, Clone)] pub struct Widget { - name: syn::Path, pub attributes: WidgetAttributes, pub children: Children, declaration: TokenStream, @@ -82,7 +81,6 @@ impl Widget { }; Ok(Widget { - name, attributes: open_tag.attributes, children, declaration,