diff --git a/Cargo.toml b/Cargo.toml index cc99cb4..d9d1523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ kiddo = { version = "4.2" } seldom_singleton = "0.2.0" bevy_turborand = { version = "0.9.0", optional = true } seldom_map_nav = { version = "0.7.0", optional = true } -seldom_pixel_macros = { version = "0.1.0", path = "macros" } +seldom_pixel_macros = { version = "0.2.0-dev", path = "macros" } seldom_state = { version = "0.11.0", optional = true } [dependencies.bevy] diff --git a/assets/filter/identity.px_filter.png b/assets/filter/identity.px_filter.png new file mode 100644 index 0000000..3acabf4 Binary files /dev/null and b/assets/filter/identity.px_filter.png differ diff --git a/assets/filter/identity.px_filter.png.meta b/assets/filter/identity.px_filter.png.meta new file mode 100644 index 0000000..067a9aa --- /dev/null +++ b/assets/filter/identity.px_filter.png.meta @@ -0,0 +1,12 @@ +( + meta_format_version: "1.0", + asset: Load( + loader: "seldom_pixel::filter::PxFilterLoader", + settings: ( + format: FromExtension, + is_srgb: true, + sampler: Default, + asset_usage: ("MAIN_WORLD | RENDER_WORLD"), + ), + ), +) \ No newline at end of file diff --git a/macros/Cargo.toml b/macros/Cargo.toml index b7a3001..fb5f77d 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "seldom_pixel_macros" -version = "0.1.0" +version = "0.2.0-dev" edition = "2021" -categories = [ "game-development" ] +categories = ["game-development"] description = "Macros for `seldom_pixel`" -keywords = [ "gamedev", "bevy", "graphics", "gui", "2d" ] +keywords = ["gamedev", "bevy", "graphics", "gui", "2d"] license = "MIT OR Apache-2.0" repository = "https://github.com/Seldom-SE/seldom_pixel" @@ -13,4 +13,4 @@ proc-macro = true [dependencies] quote = "1" -syn = "1" \ No newline at end of file +syn = "1" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c705c99..3ccc5e5 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -7,7 +7,7 @@ use quote::quote; use syn::{Error, Meta}; /// Derives required traits for a layer. Use as `#[px_layer]` on an item. Equivalent to -/// `#[derive(Clone, Component, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]`. +/// `#[derive(ExtractComponent, Component, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)]`. #[proc_macro_attribute] pub fn px_layer(args: TokenStream, input: TokenStream) -> TokenStream { let mut output = TokenStream::from(if !args.is_empty() { @@ -23,14 +23,15 @@ pub fn px_layer(args: TokenStream, input: TokenStream) -> TokenStream { } else { quote! { #[derive( - ::std::clone::Clone, + ::bevy::render::extract_component::ExtractComponent, ::bevy::prelude::Component, - ::std::fmt::Debug, - ::std::default::Default, - ::std::cmp::Eq, ::std::cmp::Ord, + ::std::cmp::PartialOrd, + ::std::cmp::Eq, ::std::cmp::PartialEq, - ::std::cmp::PartialOrd + ::std::clone::Clone, + ::std::default::Default, + ::std::fmt::Debug, )] } }); diff --git a/src/animation.rs b/src/animation.rs index 4acd820..7316df3 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -15,12 +15,6 @@ use crate::{ pub(crate) fn plug(app: &mut App) { app.add_plugins(ExtractResourcePlugin::::default()) - .configure_sets( - PostUpdate, - PxSet::FinishAnimations - .after(PxSet::LoadAssets) - .before(PxSet::Draw), - ) .add_systems( PostUpdate, ( @@ -145,6 +139,14 @@ pub(crate) trait AnimationAsset: Asset { fn max_frame_count(&self) -> usize; } +pub(crate) type AnimationComponents = ( + &'static PxAnimationDirection, + &'static PxAnimationDuration, + &'static PxAnimationFinishBehavior, + &'static PxAnimationFrameTransition, + &'static PxAnimationStart, +); + static DITHERING: &[u16] = &[ 0b0000_0000_0000_0000, 0b1000_0000_0000_0000, diff --git a/src/button.rs b/src/button.rs index d61a6ae..235149b 100644 --- a/src/button.rs +++ b/src/button.rs @@ -22,9 +22,7 @@ pub(crate) fn plug(app: &mut App) { apply_deferred .after(PxSet::AddButtonAssets) .before(PxSet::UpdateButtonAssets), - (update_button_sprites, update_button_filters) - .before(PxSet::Draw) - .in_set(PxSet::UpdateButtonAssets), + (update_button_sprites, update_button_filters).in_set(PxSet::UpdateButtonAssets), disable_buttons .run_if(resource_changed::) .run_if(resource_equals(PxEnableButtons(false))), diff --git a/src/camera.rs b/src/camera.rs index 59b9397..d466af9 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,4 +1,7 @@ -use bevy::render::extract_resource::{ExtractResource, ExtractResourcePlugin}; +use bevy::render::{ + extract_component::ExtractComponent, + extract_resource::{ExtractResource, ExtractResourcePlugin}, +}; use crate::prelude::*; @@ -12,7 +15,7 @@ pub(crate) fn plug(app: &mut App) { pub struct PxCamera(pub IVec2); /// Determines whether the entity is locked to the camera -#[derive(Clone, Component, Copy, Debug, Default)] +#[derive(ExtractComponent, Component, Clone, Copy, Default, Debug)] pub enum PxCanvas { /// The entity is drawn relative to the world, like terrain #[default] diff --git a/src/cursor.rs b/src/cursor.rs index f6917dd..544a4fa 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,34 +1,34 @@ //! Cursor -use bevy::window::PrimaryWindow; +use bevy::{ + render::extract_resource::{ExtractResource, ExtractResourcePlugin}, + window::PrimaryWindow, +}; use crate::{ filter::PxFilter, - image::PxImageSliceMut, prelude::*, - screen::{Screen, ScreenMarker}, + screen::{screen_scale, Screen}, set::PxSet, }; pub(crate) fn plug(app: &mut App) { - app.init_resource::() - .init_resource::() - .add_systems( - PreUpdate, - update_cursor_position.in_set(PxSet::UpdateCursorPosition), - ) - .configure_sets(PostUpdate, PxSet::DrawCursor.after(PxSet::Draw)) - .add_systems( - PostUpdate, - ( - change_cursor.before(PxSet::DrawCursor), - // draw_cursor.in_set(PxSet::DrawCursor), - ), - ); + app.add_plugins(( + ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), + )) + .init_resource::() + .init_resource::() + .add_systems( + PreUpdate, + update_cursor_position.in_set(PxSet::UpdateCursorPosition), + ) + .add_systems(PostUpdate, change_cursor); } /// Resource that defines whether to use an in-game cursor -#[derive(Debug, Default, Resource)] +#[derive(ExtractResource, Resource, Clone, Default, Debug)] pub enum PxCursor { /// Use the operating system's cursor #[default] @@ -48,22 +48,18 @@ pub enum PxCursor { /// Resource marking the cursor's position. Measured in pixels from the bottom-left of the screen. /// Contains [`None`] if the cursor is off-screen. The cursor's world position /// is the contained value plus [`PxCamera`]'s contained value. -#[derive(Debug, Default, Deref, DerefMut, Resource)] +#[derive(ExtractResource, Resource, Deref, DerefMut, Clone, Default, Debug)] pub struct PxCursorPosition(pub Option); fn update_cursor_position( mut move_events: EventReader, mut leave_events: EventReader, - screens: Query<&Transform, With>, cameras: Query<(&Camera, &GlobalTransform)>, screen: Res, mut position: ResMut, + windows: Query<&Window>, ) { - let Ok(screen_tf) = screens.get_single() else { - return; - }; - - if leave_events.read().next().is_some() { + if leave_events.read().last().is_some() { **position = None; return; } @@ -71,15 +67,26 @@ fn update_cursor_position( let Some(event) = move_events.read().last() else { return; }; + let Ok((camera, tf)) = cameras.get_single() else { return; }; + let Ok(window) = windows.get_single() else { + return; + }; + let Some(new_position) = camera.viewport_to_world_2d(tf, event.position) else { **position = None; return; }; - let new_position = new_position / screen_tf.scale.truncate() * screen.computed_size.as_vec2() + + let new_position = new_position + / screen_scale( + screen.computed_size, + Vec2::new(window.width(), window.height()), + ) + * screen.computed_size.as_vec2() + screen.computed_size.as_vec2() / 2.; **position = (new_position.cmpge(Vec2::ZERO).all() @@ -107,40 +114,25 @@ fn change_cursor( }; } -fn draw_cursor( - screen: Res, - cursor: Res, - cursor_pos: Res, - filters: Res>, - mouse: Res>, - mut images: ResMut>, -) { - if let PxCursor::Filter { - idle, - left_click, - right_click, - } = &*cursor - { - if let Some(cursor_pos) = **cursor_pos { - if let Some(PxFilter(filter)) = filters.get(if mouse.pressed(MouseButton::Left) { - left_click - } else if mouse.pressed(MouseButton::Right) { - right_click - } else { - idle - }) { - let mut image = - PxImageSliceMut::from_image_mut(images.get_mut(&screen.image).unwrap()); - - if let Some(pixel) = image.get_pixel_mut(IVec2::new( - cursor_pos.x as i32, - image.height() as i32 - 1 - cursor_pos.y as i32, - )) { - *pixel = filter - .get_pixel(IVec2::new(*pixel as i32, 0)) - .expect("filter is incorrect size"); - } - } +#[derive(Resource)] +pub(crate) enum CursorState { + Idle, + Left, + Right, +} + +impl ExtractResource for CursorState { + type Source = ButtonInput; + + fn extract_resource(source: &ButtonInput) -> Self { + use CursorState::*; + + if source.pressed(MouseButton::Left) { + Left + } else if source.pressed(MouseButton::Right) { + Right + } else { + Idle } } } diff --git a/src/filter.rs b/src/filter.rs index a309ada..81d2c9d 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -6,13 +6,14 @@ use anyhow::{Error, Result}; use bevy::{ asset::{io::Reader, AssetLoader, LoadContext}, render::{ - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, texture::{ImageLoader, ImageLoaderSettings}, + Extract, RenderApp, }, }; use crate::{ - animation::{draw_animation, Animation, AnimationAsset}, + animation::{draw_animation, Animation, AnimationAsset, AnimationComponents}, image::{PxImage, PxImageSliceMut}, palette::asset_palette, pixel::Pixel, @@ -20,10 +21,12 @@ use crate::{ prelude::*, }; -pub(crate) fn plug(app: &mut App) { +pub(crate) fn plug(app: &mut App) { app.add_plugins(RenderAssetPlugin::::default()) .init_asset::() - .init_asset_loader::(); + .init_asset_loader::() + .sub_app_mut(RenderApp) + .add_systems(ExtractSchedule, extract_filters::); } struct PxFilterLoader(ImageLoader); @@ -114,10 +117,6 @@ impl RenderAsset for PxFilter { type SourceAsset = Self; type Param = (); - fn asset_usage(_: &Self) -> RenderAssetUsages { - RenderAssetUsages::RENDER_WORLD - } - fn prepare_asset( source_asset: Self, &mut (): &mut (), @@ -168,8 +167,27 @@ impl PxFilter { } } +/// Function that can be used as a layer selection function in `PxFilterLayers`. Automatically +/// implemented for types with the bounds and `Clone`. +pub trait SelectLayerFn: 'static + Fn(&L) -> bool + Send + Sync { + /// Clones into a trait object + fn clone(&self) -> Box>; +} + +impl bool + Clone + Send + Sync> SelectLayerFn for T { + fn clone(&self) -> Box> { + Box::new(Clone::clone(self)) + } +} + +impl Clone for Box> { + fn clone(&self) -> Self { + SelectLayerFn::clone(&**self) + } +} + /// Determines which layers a filter appies to -#[derive(Component)] +#[derive(Component, Clone)] pub enum PxFilterLayers { /// Filter applies to a single layer Single { @@ -183,7 +201,7 @@ pub enum PxFilterLayers { /// Filter applies to a set list of layers Many(Vec), /// Filter applies to layers selected by the given function - Select(Box bool + Send + Sync>), + Select(Box>), } impl Default for PxFilterLayers { @@ -192,7 +210,7 @@ impl Default for PxFilterLayers { } } -impl bool + Send + Sync> From for PxFilterLayers { +impl bool + Clone + Send + Sync> From for PxFilterLayers { fn from(t: T) -> Self { Self::Select(Box::new(t)) } @@ -219,6 +237,31 @@ pub struct PxFilterBundle { pub layers: PxFilterLayers, /// A [`Visibility`] component pub visibility: Visibility, + /// An [`InheritedVisibility`] component + pub inherited_visibility: InheritedVisibility, +} + +pub(crate) type FilterComponents = ( + &'static Handle, + &'static PxFilterLayers, + Option, +); + +fn extract_filters( + filters: Extract, &InheritedVisibility), Without>>, + mut cmd: Commands, +) { + for ((filter, layers, animation), visibility) in &filters { + if !visibility.get() { + continue; + } + + let mut filter = cmd.spawn((filter.clone(), layers.clone())); + + if let Some((&direction, &duration, &on_finish, &frame_transition, &start)) = animation { + filter.insert((direction, duration, on_finish, frame_transition, start)); + } + } } pub(crate) fn draw_filter( diff --git a/src/lib.rs b/src/lib.rs index c13ac8a..876a87b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,6 @@ mod ui; use std::{marker::PhantomData, path::PathBuf}; -use bevy::render::view::RenderLayers; use position::PxLayer; use prelude::*; @@ -42,7 +41,6 @@ use prelude::*; pub struct PxPlugin { screen_size: ScreenSize, palette_path: PathBuf, - layers: RenderLayers, _l: PhantomData, } @@ -54,16 +52,9 @@ impl PxPlugin { Self { screen_size: screen_size.into(), palette_path: palette_path.into(), - layers: RenderLayers::default(), _l: PhantomData, } } - - /// Sets the render layers that `seldom_pixel` will draw to - pub fn with_render_layers(mut self, layers: RenderLayers) -> Self { - self.layers = layers; - self - } } impl Plugin for PxPlugin { @@ -73,13 +64,15 @@ impl Plugin for PxPlugin { button::plug, camera::plug, cursor::plug, - filter::plug, - map::plug, + filter::plug::, + #[cfg(feature = "line")] + line::plug::, + map::plug::, palette::plug(self.palette_path.clone()), position::plug, - screen::Plug::::new(self.screen_size, self.layers.clone()), - sprite::plug, - text::plug, + screen::Plug::::new(self.screen_size), + sprite::plug::, + text::plug::, #[cfg(feature = "particle")] (RngPlugin::default(), particle::plug::), )); diff --git a/src/line.rs b/src/line.rs index cc8114b..eddc38c 100644 --- a/src/line.rs +++ b/src/line.rs @@ -1,17 +1,23 @@ use std::time::Duration; +use bevy::render::{Extract, RenderApp}; use line_drawing::Bresenham; use crate::{ - animation::{draw_animation, Animation}, + animation::{draw_animation, Animation, AnimationComponents}, image::PxImageSliceMut, pixel::Pixel, position::{PxLayer, Spatial}, prelude::*, }; +pub(crate) fn plug(app: &mut App) { + app.sub_app_mut(RenderApp) + .add_systems(ExtractSchedule, extract_lines::); +} + /// Point list for a line -#[derive(Component, Debug, Default, Deref, DerefMut)] +#[derive(Component, Deref, DerefMut, Clone, Default, Debug)] pub struct PxLine(pub Vec); impl Spatial for PxLine { @@ -84,6 +90,33 @@ pub struct PxLineBundle { pub canvas: PxCanvas, /// A [`Visibility`] component pub visibility: Visibility, + /// An [`InheritedVisibility`] component + pub inherited_visibility: InheritedVisibility, +} + +pub(crate) type LineComponents = ( + &'static PxLine, + &'static Handle, + &'static PxFilterLayers, + &'static PxCanvas, + Option, +); + +fn extract_lines( + lines: Extract, &InheritedVisibility)>>, + mut cmd: Commands, +) { + for ((line, filter, layers, &canvas, animation), visibility) in &lines { + if !visibility.get() { + continue; + } + + let mut line = cmd.spawn((line.clone(), filter.clone(), layers.clone(), canvas)); + + if let Some((&direction, &duration, &on_finish, &frame_transition, &start)) = animation { + line.insert((direction, duration, on_finish, frame_transition, start)); + } + } } pub(crate) fn draw_line( diff --git a/src/map.rs b/src/map.rs index a25a4f4..06ca9c1 100644 --- a/src/map.rs +++ b/src/map.rs @@ -3,26 +3,28 @@ use std::mem::replace; use anyhow::{Error, Result}; use bevy::{ asset::{io::Reader, AssetLoader, LoadContext}, - ecs::system::SystemParamItem, render::{ - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, texture::{ImageLoader, ImageLoaderSettings}, + Extract, RenderApp, }, }; use serde::{Deserialize, Serialize}; use crate::{ - animation::AnimationAsset, + animation::{AnimationAsset, AnimationComponents}, image::PxImage, palette::asset_palette, position::{PxLayer, Spatial}, prelude::*, }; -pub(crate) fn plug(app: &mut App) { +pub(crate) fn plug(app: &mut App) { app.add_plugins(RenderAssetPlugin::::default()) .init_asset::() - .init_asset_loader::(); + .init_asset_loader::() + .sub_app_mut(RenderApp) + .add_systems(ExtractSchedule, (extract_maps::, extract_tiles)); } #[derive(Serialize, Deserialize)] @@ -142,10 +144,6 @@ impl RenderAsset for PxTileset { type SourceAsset = Self; type Param = (); - fn asset_usage(_: &Self) -> RenderAssetUsages { - RenderAssetUsages::RENDER_WORLD - } - fn prepare_asset( source_asset: Self, &mut (): &mut (), @@ -243,10 +241,12 @@ pub struct PxMapBundle { pub canvas: PxCanvas, /// A [`Visibility`] component pub visibility: Visibility, + /// An [`InheritedVisibility`] component + pub inherited_visibility: InheritedVisibility, } /// A tile. Must be added to tiles added to [`PxMap`]. -#[derive(Component, Default, Debug)] +#[derive(Component, Clone, Default, Debug)] pub struct PxTile { /// The index to the tile texture in the tileset pub texture: u32, @@ -265,4 +265,63 @@ pub struct PxTileBundle { pub tile: PxTile, /// A [`Visibility`] component pub visibility: Visibility, + /// An [`InheritedVisibility`] component + pub inherited_visibility: InheritedVisibility, +} + +pub(crate) type MapComponents = ( + &'static PxMap, + &'static Handle, + &'static PxPosition, + &'static L, + &'static PxCanvas, + Option, + Option<&'static Handle>, +); + +fn extract_maps( + maps: Extract, &InheritedVisibility)>>, + mut cmd: Commands, +) { + for ((map, tileset, &position, layer, &canvas, animation, filter), visibility) in &maps { + if !visibility.get() { + continue; + } + + let mut map = cmd.spawn(( + map.clone(), + tileset.clone(), + position, + layer.clone(), + canvas, + )); + + if let Some((&direction, &duration, &on_finish, &frame_transition, &start)) = animation { + map.insert((direction, duration, on_finish, frame_transition, start)); + } + + if let Some(filter) = filter { + map.insert(filter.clone()); + } + } +} + +pub(crate) type TileComponents = (&'static PxTile, Option<&'static Handle>); + +fn extract_tiles( + tiles: Extract>, + mut cmd: Commands, +) { + for ((tile, filter), visibility, entity) in &tiles { + if !visibility.get() { + continue; + } + + let mut entity = cmd.get_or_spawn(entity); + entity.insert(tile.clone()); + + if let Some(filter) = filter { + entity.insert(filter.clone()); + } + } } diff --git a/src/position.rs b/src/position.rs index ca23407..23f48e3 100644 --- a/src/position.rs +++ b/src/position.rs @@ -2,6 +2,8 @@ use std::fmt::Debug; +use bevy::render::extract_component::ExtractComponent; + use crate::{math::Diagonal, prelude::*, screen::Screen, set::PxSet}; // TODO Try to use traits instead of macros when we get the new solver @@ -38,29 +40,28 @@ pub(crate) fn plug(app: &mut App) { update_position_to_sub.in_set(PxSet::UpdatePosToSubPos), ) .chain(), + ) + .add_systems( + PostUpdate, + ( + align_to_screen!( + (&PxMap, &Handle), + Res>, + |(map, tileset), tilesets: &Res>| { + Some((map, tilesets.get(tileset)?).frame_size()) + } + ), + align_to_screen!(&Handle, Res>, |sprite, + sprites: &Res< + Assets, + >| { + Some(sprites.get(sprite)?.frame_size()) + }), + align_to_screen!(&PxRect, (), |rect: &PxRect, &()| Some(rect.frame_size())), + #[cfg(feature = "line")] + align_to_screen!(&PxLine, (), |line: &PxLine, &()| Some(line.frame_size())), + ), ); - // .add_systems( - // PostUpdate, - // ( - // align_to_screen!( - // (&PxMap, &Handle), - // Res>, - // |(map, tileset), tilesets: &Res>| { - // Some((map, tilesets.get(tileset)?).frame_size()) - // } - // ), - // align_to_screen!(&Handle, Res>, |sprite, - // sprites: &Res< - // Assets, - // >| { - // Some(sprites.get(sprite)?.frame_size()) - // }), - // align_to_screen!(&PxRect, (), |rect: &PxRect, &()| Some(rect.frame_size())), - // #[cfg(feature = "line")] - // align_to_screen!(&PxLine, (), |line: &PxLine, &()| Some(line.frame_size())), - // ) - // .before(PxSet::Draw), - // ); } pub(crate) trait Spatial { @@ -74,7 +75,7 @@ impl Spatial for &'_ T { } /// The position of an entity -#[derive(Clone, Component, Copy, Debug, Default, Deref, DerefMut)] +#[derive(ExtractComponent, Component, Deref, DerefMut, Clone, Copy, Default, Debug)] pub struct PxPosition(pub IVec2); impl From for PxPosition { @@ -87,12 +88,12 @@ impl From for PxPosition { /// or derive/implement the required traits manually. The layers will be rendered in the order /// defined by the [`PartialOrd`] implementation. So, lower values will be in the back /// and vice versa. -pub trait PxLayer: Clone + Component + Debug + Default + Ord {} +pub trait PxLayer: ExtractComponent + Component + Ord + Clone + Default + Debug {} -impl PxLayer for L {} +impl PxLayer for L {} /// How a sprite is positioned relative to its [`PxPosition`]. It defaults to [`PxAnchor::Center`]. -#[derive(Clone, Component, Copy, Debug, Default)] +#[derive(ExtractComponent, Component, Clone, Copy, Default, Debug)] pub enum PxAnchor { /// Center #[default] diff --git a/src/screen.rs b/src/screen.rs index f1b6211..56910f8 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -3,44 +3,43 @@ use std::{collections::BTreeMap, marker::PhantomData}; use bevy::{ - core_pipeline::{ - core_2d::graph::{Core2d, Node2d}, - fullscreen_vertex_shader::fullscreen_shader_vertex_state, - }, + core_pipeline::core_2d::graph::{Core2d, Node2d}, render::{ + extract_resource::{ExtractResource, ExtractResourcePlugin}, render_asset::RenderAssets, render_graph::{ NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner, }, render_resource::{ - binding_types::{sampler, texture_2d}, - AsBindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, - CachedRenderPipelineId, ColorTargetState, ColorWrites, Extent3d, FragmentState, + binding_types::{texture_2d, uniform_buffer}, + BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId, + ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d, FragmentState, ImageDataLayout, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, - RenderPipelineDescriptor, Sampler, SamplerBindingType, ShaderRef, ShaderStages, - TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, - TextureViewDescriptor, TextureViewDimension, + RenderPipelineDescriptor, ShaderStages, ShaderType, TextureDimension, TextureFormat, + TextureSampleType, TextureViewDescriptor, TextureViewDimension, VertexState, }, renderer::{RenderContext, RenderDevice, RenderQueue}, texture::{BevyDefault, TextureFormatPixelInfo}, - view::{RenderLayers, ViewTarget}, - RenderApp, + view::ViewTarget, + Render, RenderApp, RenderSet, }, - sprite::{Material2d, MaterialMesh2dBundle}, window::{PrimaryWindow, WindowResized}, }; #[cfg(feature = "line")] -use crate::line::draw_line; +use crate::line::{draw_line, LineComponents}; use crate::{ - animation::{copy_animation_params, draw_spatial, LastUpdate, PxAnimationStart}, - filter::draw_filter, + animation::{copy_animation_params, draw_spatial, LastUpdate}, + cursor::{CursorState, PxCursorPosition}, + filter::{draw_filter, FilterComponents}, image::{PxImage, PxImageSliceMut}, - map::PxTile, + map::{MapComponents, PxTile, TileComponents}, math::RectExt, palette::{PaletteHandle, PaletteParam}, position::PxLayer, prelude::*, + sprite::SpriteComponents, + text::TextComponents, }; const SCREEN_SHADER_HANDLE: Handle = @@ -48,15 +47,13 @@ const SCREEN_SHADER_HANDLE: Handle = pub(crate) struct Plug { size: ScreenSize, - layers: RenderLayers, _l: PhantomData, } impl Plug { - pub(crate) fn new(size: ScreenSize, layers: RenderLayers) -> Self { + pub(crate) fn new(size: ScreenSize) -> Self { Self { size, - layers, _l: PhantomData, } } @@ -64,21 +61,19 @@ impl Plug { impl Plugin for Plug { fn build(&self, app: &mut App) { - app.world_mut().resource_mut::>().insert( - SCREEN_SHADER_HANDLE.id(), - Shader::from_wgsl(include_str!("screen.wgsl"), "screen.wgsl"), - ); - app.add_systems(Startup, insert_screen(self.size)) - .add_systems(Update, init_screen(self.layers.clone())) - .add_systems( - PostUpdate, - ( - (update_screen, (clear_screen, resize_screen)).chain(), - update_screen_palette, - ), - ) - .sub_app_mut(RenderApp) - .add_render_graph_node::>>(Core2d, PxRender) + app.add_plugins(ExtractResourcePlugin::::default()) + .add_systems(Startup, insert_screen(self.size)) + .add_systems(Update, init_screen) + .add_systems(PostUpdate, (resize_screen, update_screen_palette)) + .world_mut() + .resource_mut::>() + .insert( + SCREEN_SHADER_HANDLE.id(), + Shader::from_wgsl(include_str!("screen.wgsl"), "screen.wgsl"), + ); + + app.sub_app_mut(RenderApp) + .add_render_graph_node::>>(Core2d, PxRender) .add_render_graph_edges( Core2d, ( @@ -86,7 +81,9 @@ impl Plugin for Plug { PxRender, Node2d::EndMainPassPostProcessing, ), - ); + ) + .init_resource::() + .add_systems(Render, prepare_uniform.in_set(RenderSet::Prepare)); } fn finish(&self, app: &mut App) { @@ -128,12 +125,13 @@ impl ScreenSize { } } -/// The image that `seldom_pixel` draws to -#[derive(Clone, Resource)] +/// Metadata for the image that `seldom_pixel` draws to +#[derive(ExtractResource, Resource, Clone, Debug)] pub struct Screen { - pub(crate) image: Handle, pub(crate) size: ScreenSize, pub(crate) computed_size: UVec2, + window_aspect_ratio: f32, + pub(crate) palette: [Vec3; 256], } impl Screen { @@ -143,24 +141,7 @@ impl Screen { } } -#[derive(Component)] -pub(crate) struct ScreenMarker; - -#[derive(AsBindGroup, Asset, Clone, Reflect)] -struct ScreenMaterial { - #[uniform(0)] - palette: [Vec3; 256], - #[texture(1, sample_type = "u_int")] - image: Handle, -} - -impl Material2d for ScreenMaterial { - fn fragment_shader() -> ShaderRef { - SCREEN_SHADER_HANDLE.into() - } -} - -fn screen_scale(screen_size: UVec2, window_size: Vec2) -> Vec2 { +pub(crate) fn screen_scale(screen_size: UVec2, window_size: Vec2) -> Vec2 { let aspect = screen_size.y as f32 / screen_size.x as f32; Vec2::from(match window_size.y > aspect * window_size.x { @@ -169,145 +150,82 @@ fn screen_scale(screen_size: UVec2, window_size: Vec2) -> Vec2 { }) } -fn insert_screen( - size: ScreenSize, -) -> impl Fn(ResMut>, Query<&Window, With>, Commands) { - move |mut images, windows, mut commands| { +fn insert_screen(size: ScreenSize) -> impl Fn(Query<&Window, With>, Commands) { + move |windows, mut commands| { let window = windows.single(); - let computed_size = size.compute(Vec2::new(window.width(), window.height())); commands.insert_resource(Screen { - image: images.add(Image { - data: vec![0; (computed_size.x * computed_size.y) as usize], - texture_descriptor: TextureDescriptor { - label: None, - size: Extent3d { - width: computed_size.x, - height: computed_size.y, - ..default() - }, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: TextureFormat::R8Uint, - usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, - view_formats: &[TextureFormat::R8Uint], - }, - ..default() - }), size, - computed_size, + computed_size: size.compute(Vec2::new(window.width(), window.height())), + window_aspect_ratio: window.width() / window.height(), + palette: [Vec3::ZERO; 256], }); } } -fn init_screen( - layers: RenderLayers, -) -> impl Fn( - PaletteParam, - ResMut>, - Query<(), With>, - Res, - ResMut>, - Query<(Entity, &Window), With>, - EventWriter, - Commands, -) { - move |palette, - mut screen_materials, - screens, - screen, - mut meshes, - windows, - mut window_resized, - mut commands| { - if screens.iter().next().is_some() { - return; - } - - let Some(palette) = palette.get() else { - return; - }; +fn init_screen(mut initialized: Local, palette: PaletteParam, mut screen: ResMut) { + if *initialized { + return; + } - let mut screen_palette = [Vec3::ZERO; 256]; + let Some(palette) = palette.get() else { + return; + }; - for (i, [r, g, b]) in palette.colors.iter().enumerate() { - screen_palette[i] = Color::srgb_u8(*r, *g, *b).to_linear().to_vec3(); - } + let mut screen_palette = [Vec3::ZERO; 256]; - let (entity, window) = windows.single(); - let calculated_screen_scale = screen_scale( - screen.computed_size, - Vec2::new(window.width(), window.height()), - ) - .extend(1.); - - commands.spawn(( - ScreenMarker, - layers.clone(), - MaterialMesh2dBundle { - mesh: meshes.add(Rectangle::default()).into(), - material: screen_materials.add(ScreenMaterial { - image: screen.image.clone(), - palette: screen_palette, - }), - transform: Transform::from_scale(calculated_screen_scale), - // Ensure transform matches global_transform to ensure correct rendering for WASM - global_transform: GlobalTransform::from_scale(calculated_screen_scale), - ..default() - }, - Name::new("Screen"), - )); - - // I do not know why, but the screen does not display unless the window has been resized - window_resized.send(WindowResized { - window: entity, - width: window.width(), - height: window.height(), - }); + for (i, [r, g, b]) in palette.colors.iter().enumerate() { + screen_palette[i] = Color::srgb_u8(*r, *g, *b).to_linear().to_vec3(); } + + screen.palette = screen_palette; + + *initialized = false; } -fn resize_screen( - mut window_resized: EventReader, - mut screens: Query<&mut Transform, With>, - mut screen: ResMut, - mut images: ResMut>, -) { +fn resize_screen(mut window_resized: EventReader, mut screen: ResMut) { if let Some(window_resized) = window_resized.read().last() { - let window_size = Vec2::new(window_resized.width, window_resized.height); - let computed_size = screen.size.compute(window_size); - - if computed_size != screen.computed_size { - images.get_mut(&screen.image).unwrap().resize(Extent3d { - width: computed_size.x, - height: computed_size.y, - ..default() - }); - } + screen.computed_size = screen + .size + .compute(Vec2::new(window_resized.width, window_resized.height)); + screen.window_aspect_ratio = window_resized.width / window_resized.height; + } +} - screen.computed_size = computed_size; +#[derive(ShaderType)] +struct PxUniform { + palette: [Vec3; 256], + fit_factor: Vec2, +} - let mut transform = screens.single_mut(); +#[derive(Resource, Deref, DerefMut, Default)] +struct PxUniformBuffer(DynamicUniformBuffer); - transform.scale = screen_scale( - computed_size, - Vec2::new(window_resized.width, window_resized.height), - ) - .extend(1.); - } -} +fn prepare_uniform( + mut buffer: ResMut, + screen: Res, + device: Res, + queue: Res, +) { + let Some(mut writer) = buffer.get_writer(1, &device, &queue) else { + return; + }; -fn clear_screen(screen: Res, mut images: ResMut>) { - for pixel in images.get_mut(&screen.image).unwrap().data.iter_mut() { - *pixel = 0; - } + let aspect_ratio_ratio = + screen.computed_size.x as f32 / screen.computed_size.y as f32 / screen.window_aspect_ratio; + writer.write(&PxUniform { + palette: screen.palette, + fit_factor: if aspect_ratio_ratio > 1. { + Vec2::new(1., 1. / aspect_ratio_ratio) + } else { + Vec2::new(aspect_ratio_ratio, 1.) + }, + }); } #[derive(Resource)] struct PxPipeline { layout: BindGroupLayout, - sampler: Sampler, id: CachedRenderPipelineId, } @@ -320,20 +238,23 @@ impl FromWorld for PxPipeline { &BindGroupLayoutEntries::sequential( ShaderStages::FRAGMENT, ( - texture_2d(TextureSampleType::Float { filterable: true }), - sampler(SamplerBindingType::Filtering), texture_2d(TextureSampleType::Uint), + uniform_buffer::(false).visibility(ShaderStages::VERTEX_FRAGMENT), ), ), ); Self { - sampler: render_device.create_sampler(&default()), id: world.resource_mut::().queue_render_pipeline( RenderPipelineDescriptor { label: Some("px_pipeline".into()), layout: vec![layout.clone()], - vertex: fullscreen_shader_vertex_state(), + vertex: VertexState { + shader: SCREEN_SHADER_HANDLE, + shader_defs: Vec::new(), + entry_point: "vertex".into(), + buffers: Vec::new(), + }, fragment: Some(FragmentState { shader: SCREEN_SHADER_HANDLE, shader_defs: Vec::new(), @@ -358,94 +279,17 @@ impl FromWorld for PxPipeline { #[derive(RenderLabel, Hash, Eq, PartialEq, Clone, Debug)] struct PxRender; -struct PxNode { - maps: QueryState<( - &'static PxMap, - &'static Handle, - &'static PxPosition, - &'static L, - &'static PxCanvas, - &'static Visibility, - Option<( - &'static PxAnimationDirection, - &'static PxAnimationDuration, - &'static PxAnimationFinishBehavior, - &'static PxAnimationFrameTransition, - &'static PxAnimationStart, - )>, - Option<&'static Handle>, - )>, - tiles: QueryState<( - &'static PxTile, - &'static Visibility, - Option<&'static Handle>, - )>, - sprites: QueryState<( - &'static Handle, - &'static PxPosition, - &'static PxAnchor, - &'static L, - &'static PxCanvas, - &'static Visibility, - Option<( - &'static PxAnimationDirection, - &'static PxAnimationDuration, - &'static PxAnimationFinishBehavior, - &'static PxAnimationFrameTransition, - &'static PxAnimationStart, - )>, - Option<&'static Handle>, - )>, - texts: QueryState<( - &'static PxText, - &'static Handle, - &'static PxRect, - &'static PxAnchor, - &'static L, - &'static PxCanvas, - &'static Visibility, - Option<( - &'static PxAnimationDirection, - &'static PxAnimationDuration, - &'static PxAnimationFinishBehavior, - &'static PxAnimationFrameTransition, - &'static PxAnimationStart, - )>, - Option<&'static Handle>, - )>, +struct PxRenderNode { + maps: QueryState>, + tiles: QueryState, + sprites: QueryState>, + texts: QueryState>, #[cfg(feature = "line")] - lines: QueryState<( - &PxLine, - &Handle, - &PxFilterLayers, - &PxCanvas, - &Visibility, - Option<( - &PxAnimationDirection, - &PxAnimationDuration, - &PxAnimationFinishBehavior, - &PxAnimationFrameTransition, - &PxAnimationStart, - )>, - )>, - filters: QueryState< - ( - &'static Handle, - &'static PxFilterLayers, - &'static Visibility, - Option<( - &'static PxAnimationDirection, - &'static PxAnimationDuration, - &'static PxAnimationFinishBehavior, - &'static PxAnimationFrameTransition, - &'static PxAnimationStart, - )>, - ), - Without, - >, + lines: QueryState>, + filters: QueryState, Without>, } -impl FromWorld for PxNode { +impl FromWorld for PxRenderNode { fn from_world(world: &mut World) -> Self { Self { maps: world.query(), @@ -459,7 +303,7 @@ impl FromWorld for PxNode { } } -impl ViewNode for PxNode { +impl ViewNode for PxRenderNode { type ViewQuery = &'static ViewTarget; fn update(&mut self, world: &mut World) { @@ -481,16 +325,17 @@ impl ViewNode for PxNode { ) -> Result<(), NodeRunError> { let &camera = world.resource::(); let &LastUpdate(last_update) = world.resource::(); + let screen = world.resource::(); let mut image = Image::new_fill( Extent3d { - width: 1, - height: 1, + width: screen.computed_size.x, + height: screen.computed_size.y, depth_or_array_layers: 1, }, TextureDimension::D2, - &[255, 0, 255, 255], - TextureFormat::Rgba8Uint, + &[0], + TextureFormat::R8Uint, default(), ); @@ -501,13 +346,9 @@ impl ViewNode for PxNode { let mut layer_contents = BTreeMap::<_, (Vec<_>, Vec<_>, Vec<_>, (), Vec<_>, (), Vec<_>)>::default(); - for (map, tileset, position, layer, canvas, visibility, animation, filter) in + for (map, tileset, position, layer, canvas, animation, filter) in self.maps.iter_manual(world) { - if let Visibility::Hidden = visibility { - continue; - } - if let Some((maps, _, _, _, _, _, _)) = layer_contents.get_mut(layer) { maps.push((map, tileset, position, canvas, animation, filter)); } else { @@ -526,13 +367,9 @@ impl ViewNode for PxNode { } } - for (sprite, position, anchor, layer, canvas, visibility, animation, filter) in + for (sprite, position, anchor, layer, canvas, animation, filter) in self.sprites.iter_manual(world) { - if let Visibility::Hidden = visibility { - continue; - } - if let Some((_, sprites, _, _, _, _, _)) = layer_contents.get_mut(layer) { sprites.push((sprite, position, anchor, canvas, animation, filter)); } else { @@ -551,13 +388,9 @@ impl ViewNode for PxNode { } } - for (text, typeface, rect, alignment, layer, canvas, visibility, animation, filter) in + for (text, typeface, rect, alignment, layer, canvas, animation, filter) in self.texts.iter_manual(world) { - if let Visibility::Hidden = visibility { - continue; - } - if let Some((_, _, texts, _, _, _, _)) = layer_contents.get_mut(layer) { texts.push((text, typeface, rect, alignment, canvas, animation, filter)); } else { @@ -577,7 +410,7 @@ impl ViewNode for PxNode { } #[cfg(feature = "line")] - for (line, filter, layers, canvas, visibility, animation) in self.lines.iter_manual(world) { + for (line, filter, layers, canvas, animation) in self.lines.iter_manual(world) { for (layer, clip) in match layers { PxFilterLayers::Single { layer, clip } => vec![(layer.clone(), *clip)], PxFilterLayers::Many(layers) => { @@ -595,9 +428,9 @@ impl ViewNode for PxNode { layer_contents.get_mut(&layer) { if clip { clip_lines } else { over_lines } - .push((line, filter, canvas, visibility, animation)); + .push((line, filter, canvas, animation)); } else { - let lines = vec![(line, filter, canvas, visibility, animation)]; + let lines = vec![(line, filter, canvas, animation)]; layer_contents.insert( layer, @@ -632,11 +465,7 @@ impl ViewNode for PxNode { let typefaces = world.resource::>(); let filters = world.resource::>(); - for (filter, layers, visibility, animation) in self.filters.iter_manual(world) { - if let Visibility::Hidden = visibility { - continue; - } - + for (filter, layers, animation) in self.filters.iter_manual(world) { for (layer, clip) in match layers { PxFilterLayers::Single { layer, clip } => vec![(layer.clone(), *clip)], PxFilterLayers::Many(layers) => { @@ -709,14 +538,11 @@ impl ViewNode for PxNode { continue; }; - let (&PxTile { texture }, visibility, tile_filter) = self - .tiles - .get_manual(world, tile) - .expect("entity in map is not a valid tile"); - - if let Visibility::Hidden = visibility { + let Ok((&PxTile { texture }, tile_filter)) = + self.tiles.get_manual(world, tile) + else { continue; - } + }; let Some(tile) = tileset.tileset.get(texture as usize) else { error!("tile texture index out of bounds: the len is {}, but the index is {texture}", tileset.tileset.len()); @@ -928,18 +754,16 @@ impl ViewNode for PxNode { // This is where I draw the line! /j #[cfg(feature = "line")] - for (line, filter, canvas, visibility, animation) in clip_lines { - if let Visibility::Visible | Visibility::Inherited = visibility { - if let Some(filter) = filters.get(filter) { - draw_line( - line, - filter, - &mut layer_image.slice_all_mut(), - *canvas, - copy_animation_params(animation, &time), - *camera, - ); - } + for (line, filter, canvas, animation) in clip_lines { + if let Some(filter) = filters.get(filter) { + draw_line( + line, + filter, + &mut layer_image.slice_all_mut(), + *canvas, + copy_animation_params(animation, last_update), + camera, + ); } } @@ -956,18 +780,16 @@ impl ViewNode for PxNode { image_slice.draw(&layer_image); #[cfg(feature = "line")] - for (line, filter, canvas, visibility, animation) in over_lines { - if let Visibility::Visible | Visibility::Inherited = visibility { - if let Some(filter) = filterns.get(filter) { - draw_line( - line, - filter, - &mut image_slice, - *canvas, - copy_animation_params(animation, &time), - *camera, - ); - } + for (line, filter, canvas, animation) in over_lines { + if let Some(filter) = filters.get(filter) { + draw_line( + line, + filter, + &mut image_slice, + *canvas, + copy_animation_params(animation, last_update), + camera, + ); } } @@ -982,6 +804,38 @@ impl ViewNode for PxNode { } } + let cursor = world.resource::(); + + if let PxCursor::Filter { + idle, + left_click, + right_click, + } = world.resource() + { + if let Some(cursor_pos) = **world.resource::() { + if let Some(PxFilter(filter)) = filters.get(match cursor { + CursorState::Idle => idle, + CursorState::Left => left_click, + CursorState::Right => right_click, + }) { + let mut image = PxImageSliceMut::from_image_mut(&mut image); + + if let Some(pixel) = image.get_pixel_mut(IVec2::new( + cursor_pos.x as i32, + image.height() as i32 - 1 - cursor_pos.y as i32, + )) { + *pixel = filter + .get_pixel(IVec2::new(*pixel as i32, 0)) + .expect("filter is incorrect size"); + } + } + } + } + + let Some(uniform_binding) = world.resource::().binding() else { + return Ok(()); + }; + let texture = render_context .render_device() .create_texture(&image.texture_descriptor); @@ -1019,11 +873,7 @@ impl ViewNode for PxNode { let bind_group = render_context.render_device().create_bind_group( "px_bind_group", &px_pipeline.layout, - &BindGroupEntries::sequential(( - post_process.source, - &px_pipeline.sampler, - &texture_view, - )), + &BindGroupEntries::sequential((&texture_view, uniform_binding.clone())), ); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { @@ -1040,27 +890,17 @@ impl ViewNode for PxNode { render_pass.set_render_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[]); - render_pass.draw(0..3, 0..1); + render_pass.draw(0..6, 0..1); Ok(()) } } -fn update_screen( - screen_materials: Query<&Handle>, - mut asset_events: EventWriter>, -) { - for handle in &screen_materials { - asset_events.send(AssetEvent::Modified { id: handle.id() }); - } -} - fn update_screen_palette( mut waiting_for_load: Local, - screen_materials: Query<&Handle>, palette_handle: Res, + mut screen: ResMut, palette: PaletteParam, - mut screen_material_assets: ResMut>, ) { if !palette_handle.is_changed() && !*waiting_for_load { return; @@ -1077,12 +917,7 @@ fn update_screen_palette( screen_palette[i] = Color::srgb_u8(*r, *g, *b).to_linear().to_vec3(); } - for screen_material in &screen_materials { - screen_material_assets - .get_mut(screen_material) - .unwrap() - .palette = screen_palette; - } + screen.palette = screen_palette; *waiting_for_load = false; } diff --git a/src/screen.wgsl b/src/screen.wgsl index 4291df5..bee91bb 100644 --- a/src/screen.wgsl +++ b/src/screen.wgsl @@ -1,20 +1,25 @@ -// struct VertexOutput { -// @location(0) world_position: vec4, -// @location(1) world_normal: vec3, -// @location(2) uv: vec2, -// }; +struct PxUniform { + palette: array, 256>, + fit_factor: vec2, +}; -// struct ScreenMaterial { -// palette: array, 256>, -// }; +@group(0) @binding(0) var texture: texture_2d; +@group(0) @binding(1) var uniform: PxUniform; -#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput +struct VertexOut { + @builtin(position) position: vec4, + @location(0) uv: vec2, +}; -@group(0) @binding(0) var out_texture: texture_2d; -@group(0) @binding(1) var out_sampler: sampler; -@group(0) @binding(2) var in_texture: texture_2d; +var VERTEX_U: array = array(0., 0., 1., 0., 1., 1.); -@fragment -fn fragment(input: FullscreenVertexOutput) -> @location(0) vec4 { - return vec4((textureLoad(in_texture, vec2(0, 0), 0))) / 255.0; +@vertex fn vertex(@builtin(vertex_index) index: u32) -> VertexOut { + let uv = vec2(VERTEX_U[index], f32(index & 1)); + return VertexOut(vec4((uv - 0.5) * vec2(2., -2.) * uniform.fit_factor, 0., 1.), uv); +} + +@fragment fn fragment(vert: VertexOut) -> @location(0) vec4 { + return vec4(uniform.palette[ + textureLoad(texture, vec2(vec2(textureDimensions(texture)) * vert.uv), 0).r + ], 1.); } diff --git a/src/set.rs b/src/set.rs index 8aeee9f..e4b2d68 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1,8 +1,9 @@ -//! Stages used by this crate +//! Sets used by this crate use crate::prelude::*; -/// Stages used by this crate +// TODO Many of these aren't necessary anymore +/// Sets used by this crate #[derive(Clone, Debug, Eq, Hash, PartialEq, SystemSet)] pub enum PxSet { // `PreUpdate` @@ -12,8 +13,6 @@ pub enum PxSet { UpdateCursorPosition, // `PostUpdate` - /// `seldom_pixel` assets are loaded. In [`CoreSet::PostUpdate`]. - LoadAssets, /// New buttons have assets added to them. In [`CoreSet::PostUpdate`]. AddButtonAssets, /// Button assets are updated. In [`CoreSet::PostUpdate`]. @@ -23,8 +22,4 @@ pub enum PxSet { /// Update particle emitters. In [`CoreSet::PostUpdate`]. #[cfg(feature = "particle")] UpdateEmitters, - /// The screen is drawn. In [`CoreSet::PostUpdate`]. - Draw, - /// The cursor is drawn. In [`CoreSet::PostUpdate`]. - DrawCursor, } diff --git a/src/sprite.rs b/src/sprite.rs index 9363bab..3286919 100644 --- a/src/sprite.rs +++ b/src/sprite.rs @@ -4,8 +4,9 @@ use anyhow::{Error, Result}; use bevy::{ asset::{io::Reader, AssetLoader, LoadContext}, render::{ - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, texture::{ImageLoader, ImageLoaderSettings}, + Extract, RenderApp, }, tasks::{ComputeTaskPool, ParallelSliceMut}, }; @@ -13,20 +14,21 @@ use kiddo::{ImmutableKdTree, SquaredEuclidean}; use serde::{Deserialize, Serialize}; use crate::{ - animation::{Animation, AnimationAsset}, + animation::{Animation, AnimationAsset, AnimationComponents}, image::{PxImage, PxImageSliceMut}, palette::{asset_palette, PaletteParam}, pixel::Pixel, position::{PxLayer, Spatial}, prelude::*, - set::PxSet, }; -pub(crate) fn plug(app: &mut App) { +pub(crate) fn plug(app: &mut App) { app.add_plugins(RenderAssetPlugin::::default()) .init_asset::() .init_asset_loader::() - .add_systems(PostUpdate, image_to_sprite.before(PxSet::Draw)); + .add_systems(PostUpdate, image_to_sprite) + .sub_app_mut(RenderApp) + .add_systems(ExtractSchedule, extract_sprites::); } #[derive(Serialize, Deserialize)] @@ -95,10 +97,6 @@ impl RenderAsset for PxSprite { type SourceAsset = Self; type Param = (); - fn asset_usage(_: &Self) -> RenderAssetUsages { - RenderAssetUsages::RENDER_WORLD - } - fn prepare_asset( source_asset: Self, &mut (): &mut (), @@ -169,6 +167,8 @@ pub struct PxSpriteBundle { pub canvas: PxCanvas, /// A [`Visibility`] component pub visibility: Visibility, + /// An [`InheritedVisibility`] component + pub inherited_visibility: InheritedVisibility, } fn srgb_to_linear(c: f32) -> f32 { @@ -517,3 +517,34 @@ fn image_to_sprite( }); }); } + +pub(crate) type SpriteComponents = ( + &'static Handle, + &'static PxPosition, + &'static PxAnchor, + &'static L, + &'static PxCanvas, + Option, + Option<&'static Handle>, +); + +fn extract_sprites( + sprites: Extract, &InheritedVisibility)>>, + mut cmd: Commands, +) { + for ((sprite, &position, &anchor, layer, &canvas, animation, filter), visibility) in &sprites { + if !visibility.get() { + continue; + } + + let mut sprite = cmd.spawn((sprite.clone(), position, anchor, layer.clone(), canvas)); + + if let Some((&direction, &duration, &on_finish, &frame_transition, &start)) = animation { + sprite.insert((direction, duration, on_finish, frame_transition, start)); + } + + if let Some(filter) = filter { + sprite.insert(filter.clone()); + } + } +} diff --git a/src/text.rs b/src/text.rs index e0fecb8..4355944 100644 --- a/src/text.rs +++ b/src/text.rs @@ -2,26 +2,32 @@ use anyhow::{anyhow, Error, Result}; use bevy::{ asset::{io::Reader, AssetLoader, LoadContext}, render::{ - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, texture::{ImageLoader, ImageLoaderSettings}, + Extract, RenderApp, }, utils::HashMap, }; use serde::{Deserialize, Serialize}; use crate::{ - animation::AnimationAsset, image::PxImage, palette::asset_palette, position::PxLayer, + animation::{AnimationAsset, AnimationComponents}, + image::PxImage, + palette::asset_palette, + position::PxLayer, prelude::*, }; -pub(crate) fn plug(app: &mut App) { +pub(crate) fn plug(app: &mut App) { app.add_plugins(RenderAssetPlugin::::default()) .init_asset::() - .init_asset_loader::(); + .init_asset_loader::() + .sub_app_mut(RenderApp) + .add_systems(ExtractSchedule, extract_texts::); } /// Text to be drawn on the screen -#[derive(Component, Debug, Default, Deref, DerefMut)] +#[derive(Component, Clone, Deref, DerefMut, Default, Debug)] pub struct PxText(pub String); impl> From for PxText { @@ -167,10 +173,6 @@ impl RenderAsset for PxTypeface { type SourceAsset = Self; type Param = (); - fn asset_usage(_: &Self) -> RenderAssetUsages { - RenderAssetUsages::RENDER_WORLD - } - fn prepare_asset( source_asset: Self, &mut (): &mut (), @@ -202,4 +204,47 @@ pub struct PxTextBundle { pub canvas: PxCanvas, /// A [`Visibility`] component pub visibility: Visibility, + /// An [`InheritedVisibility`] component + pub inherited_visibility: InheritedVisibility, +} + +pub(crate) type TextComponents = ( + &'static PxText, + &'static Handle, + &'static PxRect, + &'static PxAnchor, + &'static L, + &'static PxCanvas, + Option, + Option<&'static Handle>, +); + +fn extract_texts( + texts: Extract, &InheritedVisibility)>>, + mut cmd: Commands, +) { + for ((text, typeface, &rect, &alignment, layer, &canvas, animation, filter), visibility) in + &texts + { + if !visibility.get() { + continue; + } + + let mut text = cmd.spawn(( + text.clone(), + typeface.clone(), + rect, + alignment, + layer.clone(), + canvas, + )); + + if let Some((&direction, &duration, &on_finish, &frame_transition, &start)) = animation { + text.insert((direction, duration, on_finish, frame_transition, start)); + } + + if let Some(filter) = filter { + text.insert(filter.clone()); + } + } } diff --git a/src/ui.rs b/src/ui.rs index 6be5688..8f3b047 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,7 +1,7 @@ use crate::{position::Spatial, prelude::*}; /// UI is displayed within these bounds -#[derive(Component, Debug, Default, Deref, DerefMut)] +#[derive(Component, Deref, DerefMut, Clone, Copy, Default, Debug)] pub struct PxRect(pub IRect); impl From for PxRect {