diff --git a/examples/image.rs b/examples/image.rs index ec2cd59cae5215c3d26f11876671632457424482..c2dabf99899968d6c8d28f7d0773ad80883520aa 100644 --- a/examples/image.rs +++ b/examples/image.rs @@ -17,7 +17,7 @@ fn startup( rsx! { <KayakAppBundle> <KImageBundle - image={Image(image.clone())} + image={KImage(image.clone())} style={KStyle { position_type: StyleProp::Value(KPositionType::SelfDirected), left: StyleProp::Value(Units::Pixels(10.0)), diff --git a/examples/render_target.rs b/examples/render_target.rs new file mode 100644 index 0000000000000000000000000000000000000000..763d5c69723a430fdd5d3bf07aa6fd5c79028d92 --- /dev/null +++ b/examples/render_target.rs @@ -0,0 +1,144 @@ +use bevy::{ + prelude::*, + render::{ + camera::RenderTarget, + render_resource::{ + Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + }, +}; +use kayak_ui::{ + prelude::{widgets::*, *}, + CameraUIKayak, +}; + +// Marks the main pass cube, to which the texture is applied. +#[derive(Component)] +struct MainPassCube; + +fn startup( + mut commands: Commands, + mut font_mapping: ResMut<FontMapping>, + asset_server: Res<AssetServer>, + mut meshes: ResMut<Assets<Mesh>>, + mut materials: ResMut<Assets<StandardMaterial>>, + mut images: ResMut<Assets<Image>>, +) { + font_mapping.set_default(asset_server.load("roboto.kayak_font")); + + let size = Extent3d { + width: 1024, + height: 1024, + ..default() + }; + + // This is the texture that will be rendered to. + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + }, + ..default() + }; + + // fill image.data with zeroes + image.resize(size); + + let image_handle = images.add(image); + + commands.spawn(UICameraBundle { + camera: Camera { + priority: -1, + target: RenderTarget::Image(image_handle.clone()), + ..Camera::default() + }, + camera_ui: CameraUIKayak { + clear_color: bevy::core_pipeline::clear_color::ClearColorConfig::Default, + }, + ..UICameraBundle::new() + }); + + let mut widget_context = KayakRootContext::new(); + let parent_id = None; + rsx! { + <KayakAppBundle + styles={KStyle { + padding: Edge::all(Units::Stretch(1.0)).into(), + ..Default::default() + }} + > + <TextWidgetBundle + text={TextProps { + size: 150.0, + content: "Hello World".into(), + ..Default::default() + }} + /> + </KayakAppBundle> + } + commands.insert_resource(widget_context); + + // Setup 3D scene + // Light + commands.spawn(PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + }); + + let cube_size = 4.0; + let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size))); + + // This material has the texture that has been rendered. + let material_handle = materials.add(StandardMaterial { + base_color_texture: Some(image_handle), + reflectance: 0.02, + unlit: false, + ..default() + }); + + // Main pass cube, with material containing the rendered first pass texture. + commands + .spawn(PbrBundle { + mesh: cube_handle, + material: material_handle, + transform: Transform { + translation: Vec3::new(0.0, 0.0, 1.5), + rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0), + ..default() + }, + ..default() + }) + .insert(MainPassCube); + + // The main pass camera. + commands.spawn(Camera3dBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) + .looking_at(Vec3::default(), Vec3::Y), + ..default() + }); +} + +/// Rotates the outer cube (main pass) +fn cube_rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<MainPassCube>>) { + for mut transform in &mut query { + transform.rotate_x(1.0 * time.delta_seconds()); + transform.rotate_y(0.7 * time.delta_seconds()); + } +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(KayakContextPlugin) + .add_plugin(KayakWidgets) + .add_startup_system(startup) + .add_system(cube_rotator_system) + .run() +} diff --git a/src/camera/camera.rs b/src/camera/camera.rs index edd4761b7852df77b75de35581082c6e7560ded3..d7d990fa7bc232baaf00e82b8c3281def6b79c2b 100644 --- a/src/camera/camera.rs +++ b/src/camera/camera.rs @@ -1,4 +1,5 @@ use bevy::{ + core_pipeline::clear_color::ClearColorConfig, ecs::query::QueryItem, prelude::{Bundle, Component, GlobalTransform, Transform, With}, render::{ @@ -13,9 +14,11 @@ use super::ortho::UIOrthographicProjection; /// Kayak UI's default UI camera. #[derive(Component, Clone, Default)] -pub struct CameraUiKayak; +pub struct CameraUIKayak { + pub clear_color: ClearColorConfig, +} -impl ExtractComponent for CameraUiKayak { +impl ExtractComponent for CameraUIKayak { type Query = &'static Self; type Filter = With<Camera>; @@ -35,7 +38,7 @@ pub struct UICameraBundle { pub frustum: Frustum, pub transform: Transform, pub global_transform: GlobalTransform, - pub marker: CameraUiKayak, + pub camera_ui: CameraUIKayak, } impl UICameraBundle { @@ -73,7 +76,9 @@ impl UICameraBundle { transform, // camera_2d: Camera2d::default(), global_transform: Default::default(), - marker: CameraUiKayak, + camera_ui: CameraUIKayak { + clear_color: ClearColorConfig::None, + }, } } } diff --git a/src/camera/mod.rs b/src/camera/mod.rs index b5698994e0dcd51fdb8cc7486b98c9fa34d61d6f..60e86c38d313c7d30e88d0dc490618246882de3e 100644 --- a/src/camera/mod.rs +++ b/src/camera/mod.rs @@ -6,7 +6,7 @@ use bevy::{ mod camera; mod ortho; -pub use camera::{CameraUiKayak, UICameraBundle}; +pub use camera::{CameraUIKayak, UICameraBundle}; pub(crate) use ortho::UIOrthographicProjection; pub struct KayakUICameraPlugin; @@ -18,6 +18,6 @@ impl Plugin for KayakUICameraPlugin { bevy::render::camera::camera_system::<UIOrthographicProjection>, ) .add_plugin(CameraProjectionPlugin::<UIOrthographicProjection>::default()) - .add_plugin(ExtractComponentPlugin::<CameraUiKayak>::default()); + .add_plugin(ExtractComponentPlugin::<CameraUIKayak>::default()); } } diff --git a/src/render/mod.rs b/src/render/mod.rs index 40bbbe75855aba8092424f37328e8521231c4708..001531bf9a14511a8d1b17fe5542f1eadd583997 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -9,7 +9,7 @@ use bevy::{ use crate::{ render::{ui_pass::MainPassUINode, unified::UnifiedRenderPlugin}, - CameraUiKayak, + CameraUIKayak, }; use self::{extract::BevyKayakUIExtractPlugin, ui_pass::TransparentUI}; @@ -167,7 +167,7 @@ fn get_ui_graph(render_app: &mut App) -> RenderGraph { pub fn extract_core_pipeline_camera_phases( mut commands: Commands, - active_camera: Extract<Query<Entity, With<CameraUiKayak>>>, + active_camera: Extract<Query<Entity, With<CameraUIKayak>>>, ) { if let Ok(entity) = active_camera.get_single() { commands diff --git a/src/render/ui_pass.rs b/src/render/ui_pass.rs index 559536748a0556c619081471e526e0533089b260..70ed3337c8b8b7e7967a16bdbfcd4926c5d7d24b 100644 --- a/src/render/ui_pass.rs +++ b/src/render/ui_pass.rs @@ -1,4 +1,6 @@ +use bevy::core_pipeline::clear_color::ClearColorConfig; use bevy::ecs::prelude::*; +use bevy::prelude::ClearColor; use bevy::render::render_phase::{DrawFunctionId, PhaseItem}; use bevy::render::render_resource::CachedRenderPipelineId; use bevy::render::{ @@ -10,6 +12,8 @@ use bevy::render::{ }; use bevy::utils::FloatOrd; +use crate::CameraUIKayak; + pub struct TransparentUI { pub sort_key: FloatOrd, pub entity: Entity, @@ -32,8 +36,14 @@ impl PhaseItem for TransparentUI { } pub struct MainPassUINode { - query: - QueryState<(&'static RenderPhase<TransparentUI>, &'static ViewTarget), With<ExtractedView>>, + query: QueryState< + ( + &'static RenderPhase<TransparentUI>, + &'static ViewTarget, + &'static CameraUIKayak, + ), + With<ExtractedView>, + >, } impl MainPassUINode { @@ -41,7 +51,7 @@ impl MainPassUINode { pub fn new(world: &mut World) -> Self { Self { - query: QueryState::new(world), + query: world.query_filtered(), } } } @@ -64,7 +74,8 @@ impl Node for MainPassUINode { let view_entity = graph.get_input_entity(Self::IN_VIEW)?; // adapted from bevy itself; // see: <https://github.com/bevyengine/bevy/commit/09a3d8abe062984479bf0e99fcc1508bb722baf6> - let (transparent_phase, target) = match self.query.get_manual(world, view_entity) { + let (transparent_phase, target, camera_ui) = match self.query.get_manual(world, view_entity) + { Ok(it) => it, _ => return Ok(()), }; @@ -73,7 +84,13 @@ impl Node for MainPassUINode { let pass_descriptor = RenderPassDescriptor { label: Some("main_transparent_pass_UI"), color_attachments: &[Some(target.get_unsampled_color_attachment(Operations { - load: LoadOp::Load, + load: match camera_ui.clear_color { + ClearColorConfig::Default => { + LoadOp::Clear(world.resource::<ClearColor>().0.into()) + } + ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), + ClearColorConfig::None => LoadOp::Load, + }, store: true, }))], depth_stencil_attachment: None, diff --git a/src/widgets/app.rs b/src/widgets/app.rs index 9051362f0c5417e092894049fb6d0964a5a97563..8aa40a18504529547ed7ed89904b5b20e8d3d461 100644 --- a/src/widgets/app.rs +++ b/src/widgets/app.rs @@ -7,6 +7,7 @@ use crate::{ prelude::KayakWidgetContext, styles::{KStyle, RenderCommand, StyleProp}, widget::{EmptyState, Widget, WidgetParam}, + CameraUIKayak, }; #[derive(Component, Default, Clone, PartialEq)] @@ -62,14 +63,29 @@ pub fn app_render( _: Commands, windows: Res<Windows>, mut query: Query<(&mut KStyle, &KChildren)>, + camera: Query<&Camera, With<CameraUIKayak>>, ) -> bool { - let primary_window = windows.get_primary().unwrap(); + let (mut width, mut height) = (0.0, 0.0); + + if let Ok(camera) = camera.get_single() { + if let Some(size) = camera.logical_viewport_size() { + width = size.x; + height = size.y; + } + } + + if width == 0.0 { + let primary_window = windows.get_primary().unwrap(); + width = primary_window.width(); + height = primary_window.height(); + } + if let Ok((mut app_style, children)) = query.get_mut(entity) { - if app_style.width != StyleProp::Value(Units::Pixels(primary_window.width())) { - app_style.width = StyleProp::Value(Units::Pixels(primary_window.width())); + if app_style.width != StyleProp::Value(Units::Pixels(width)) { + app_style.width = StyleProp::Value(Units::Pixels(width)); } - if app_style.height != StyleProp::Value(Units::Pixels(primary_window.height())) { - app_style.height = StyleProp::Value(Units::Pixels(primary_window.height())); + if app_style.height != StyleProp::Value(Units::Pixels(height)) { + app_style.height = StyleProp::Value(Units::Pixels(height)); } app_style.render_command = StyleProp::Value(RenderCommand::Layout); children.process(&widget_context, Some(entity)); diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 375d52dfd6839adfb618b0746c1f99735879f669..eb85fa1c9aac3fb93027e2324cff6c79e0b856ad 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -10,13 +10,13 @@ use crate::{ /// Renders a bevy image asset within the GUI /// The rendered image respects the styles. #[derive(Component, PartialEq, Clone, Default)] -pub struct Image(pub Handle<bevy::prelude::Image>); +pub struct KImage(pub Handle<bevy::prelude::Image>); -impl Widget for Image {} +impl Widget for KImage {} #[derive(Bundle)] pub struct KImageBundle { - pub image: Image, + pub image: KImage, pub style: KStyle, pub widget_name: WidgetName, } @@ -26,14 +26,14 @@ impl Default for KImageBundle { Self { image: Default::default(), style: Default::default(), - widget_name: Image::default().get_name(), + widget_name: KImage::default().get_name(), } } } pub fn image_render( In((_widget_context, entity)): In<(KayakWidgetContext, Entity)>, - mut query: Query<(&mut KStyle, &Image)>, + mut query: Query<(&mut KStyle, &KImage)>, ) -> bool { if let Ok((mut style, image)) = query.get_mut(entity) { style.render_command = StyleProp::Value(RenderCommand::Image { diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 019759133340be8cd1885bbf7af9e7ae91aa8b5e..586c634c30c368906b914f7fa79bca70c7418b20 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -40,7 +40,7 @@ pub use background::{Background, BackgroundBundle}; pub use button::{KButton, KButtonBundle}; pub use clip::{Clip, ClipBundle}; pub use element::{Element, ElementBundle}; -pub use image::{Image, KImageBundle}; +pub use image::{KImage, KImageBundle}; pub use nine_patch::{NinePatch, NinePatchBundle}; pub use scroll::{ scroll_bar::{ScrollBarBundle, ScrollBarProps}, @@ -93,7 +93,7 @@ fn add_widget_systems(mut context: ResMut<KayakRootContext>) { context.add_widget_data::<KWindow, KWindowState>(); context.add_widget_data::<Background, EmptyState>(); context.add_widget_data::<Clip, EmptyState>(); - context.add_widget_data::<Image, EmptyState>(); + context.add_widget_data::<KImage, EmptyState>(); context.add_widget_data::<TextureAtlasProps, EmptyState>(); context.add_widget_data::<NinePatch, EmptyState>(); context.add_widget_data::<Element, EmptyState>(); @@ -130,8 +130,8 @@ fn add_widget_systems(mut context: ResMut<KayakRootContext>) { clip_render, ); context.add_widget_system( - Image::default().get_name(), - widget_update::<Image, EmptyState>, + KImage::default().get_name(), + widget_update::<KImage, EmptyState>, image_render, ); context.add_widget_system(