use bevy::{
ecs::system::SystemId,
prelude::*,
reflect::Reflect,
render::{
camera::{RenderTarget, Viewport},
render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
},
view::RenderLayers,
},
};
use crate::ScrollerGenerator;
#[derive(Reflect, Default, Debug, Clone)]
pub enum ScrollerDirection {
#[default]
Forward,
Backward,
}
impl ScrollerDirection {
pub fn as_f32(&self) -> f32 {
(*self).clone().into()
}
}
impl From<ScrollerDirection> for f32 {
fn from(value: ScrollerDirection) -> Self {
match value {
ScrollerDirection::Forward => 1.,
ScrollerDirection::Backward => -1.,
}
}
}
#[derive(Copy, Clone, Component)]
pub struct ScrollerItem {
pub size: Vec2,
pub parent: Entity,
}
#[derive(Component)]
pub struct OnScrollerInit(pub SystemId<Entity>);
#[derive(Copy, Clone, Default, Component, Reflect)]
pub struct ScrollerSize {
pub size: Vec2,
}
#[derive(Default, Debug, Component, Clone, Reflect)]
#[reflect(Component, Default)]
pub struct Scroller {
pub start: f32,
pub end: f32,
pub speed: f32,
pub direction: ScrollerDirection,
pub is_paused: bool,
pub spawn_edge: f32,
pub render_layer: Option<u8>,
pub texture_handle: Handle<Image>,
}
impl Scroller {
pub fn get_free_space(&self) -> f32 {
(self.start - self.spawn_edge) * -self.direction.as_f32()
}
pub fn new_item_needed(&self) -> bool {
self.get_free_space() > self.speed * 3.
}
pub fn get_next_item_position(&self, item: &ScrollerItem) -> Vec2 {
Vec2 {
x: self.spawn_edge - item.size.x / 2. * self.direction.as_f32(),
..default()
}
}
}
#[derive(Component, Debug)]
pub struct NeedInitialFilling;
#[derive(Bundle)]
pub struct ScrollerBundle<G: ScrollerGenerator + Send + Sync + Component> {
pub scroller: Scroller,
pub spatial: SpatialBundle,
pub generator: G,
}
impl<G: ScrollerGenerator + Send + Sync + Component> Default for ScrollerBundle<G> {
fn default() -> Self {
Self {
scroller: Scroller::default(),
generator: G::default(),
spatial: SpatialBundle {
visibility: Visibility::Hidden,
..default()
},
}
}
}
pub struct UnnamedScrollerIndex(pub u32);
impl Default for UnnamedScrollerIndex {
fn default() -> Self {
Self(1)
}
}
pub fn init(
mut scroller_index: Local<UnnamedScrollerIndex>,
mut commands: Commands,
mut q_added_scroller: Query<
(
Entity,
&mut Scroller,
&ScrollerSize,
Option<&Name>,
&Transform,
),
Added<ScrollerSize>,
>,
mut images: ResMut<Assets<Image>>,
) {
for (entity, mut scroller, scroller_size, maybe_name, transform) in q_added_scroller.iter_mut() {
let name = match maybe_name {
Some(name) => name.to_string(),
None => {
let name = format!("Scroller #{}", scroller_index.0);
commands.entity(entity).insert(Name::new(name.clone()));
scroller_index.0 += 1;
name
}
};
commands.entity(entity).insert(SpatialBundle {
transform: *transform,
..Default::default()
});
debug!("Init scroller: {name}");
scroller.end = scroller_size.size.x / 2. * scroller.direction.as_f32();
scroller.start = -scroller.end;
scroller.spawn_edge = scroller.end;
commands.entity(entity).insert(NeedInitialFilling);
if let Some(render_layer) = scroller.render_layer {
let size = Extent3d {
width: scroller_size.size.x as u32,
height: scroller_size.size.y as u32,
..default()
};
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,
view_formats: &[],
},
..default()
};
image.resize(size);
let image_handle = images.add(image); scroller.texture_handle = image_handle.clone();
commands.entity(entity).with_children(|parent| {
parent.spawn((
Camera2dBundle {
camera: Camera {
viewport: Some(Viewport {
physical_size: scroller_size.size.as_uvec2(),
..Default::default()
}),
order: -1,
target: RenderTarget::Image(image_handle.clone()),
..default()
},
..default()
},
RenderLayers::layer(render_layer),
Name::new("Scroller Camera"),
));
parent.spawn((
SpriteBundle {
texture: image_handle,
..Default::default()
},
Name::new("Scroller Camera texture"),
));
});
}
}
}
pub fn on_items_added(
mut commands: Commands,
mut q_added: Query<(&ScrollerItem, &mut Transform, &mut Visibility, Entity), Added<ScrollerItem>>,
mut q_scroller: Query<(&mut Scroller, Entity)>,
) {
for (scroller_item, mut transform, mut visibility, scroller_item_entity) in q_added.iter_mut() {
if let Ok((mut scroller, scroller_entity)) = q_scroller.get_mut(scroller_item.parent) {
let translation = scroller.get_next_item_position(scroller_item).extend(0.);
transform.translation = translation;
*visibility = Visibility::Inherited;
if let Some(render_layer) = scroller.render_layer {
commands
.entity(scroller_item_entity)
.insert(RenderLayers::layer(render_layer));
}
scroller.spawn_edge -= scroller_item.size.x * scroller.direction.as_f32();
commands
.entity(scroller_entity)
.add_child(scroller_item_entity);
}
}
}
#[cfg(feature = "dev")]
pub fn scroller_debug(
q_scroller_item: Query<(&GlobalTransform, &ScrollerItem, Option<&Visibility>)>,
q_scroller: Query<(&GlobalTransform, &Scroller, &ScrollerSize)>,
mut gizmos: Gizmos,
) {
for (global_transform, item, visibility) in q_scroller_item.iter() {
if let Some(visibility) = visibility {
if visibility != Visibility::Hidden {
let (scale, rotation, translation) = global_transform.to_scale_rotation_translation();
gizmos.rect_2d(
translation.truncate(),
rotation.to_axis_angle().1,
item.size * scale.truncate(),
Color::BLUE,
);
}
}
}
for (global_transform, scroller, scroller_size) in q_scroller.iter() {
let (scale, rotation, translation) = global_transform.to_scale_rotation_translation();
gizmos.line_2d(
Vec2::new(scroller.spawn_edge, scroller_size.size.y / -2. - 20.) * scale.truncate(), Vec2::new(scroller.spawn_edge, scroller_size.size.y / 2. + 20.) * scale.truncate(), Color::RED,
);
gizmos.rect_2d(
translation.truncate(),
rotation.to_axis_angle().1,
Vec2::new(scroller_size.size.x, scroller_size.size.y) * scale.truncate(),
Color::GREEN,
);
}
}
pub fn update(
mut commands: Commands,
mut q_scroller: Query<(
&mut Scroller,
&mut Visibility,
Option<&NeedInitialFilling>,
Option<&OnScrollerInit>,
Entity,
)>,
mut q_item: Query<(&mut Transform, Entity, &ScrollerItem)>,
time_fixed: Res<Time<Virtual>>,
) {
let step: f32 = 1. / 60.;
let delta = time_fixed.delta_seconds();
for (mut scroller, mut visibility, maybe_need_filling, maybe_on_init, scroller_entity) in
q_scroller.iter_mut()
{
if maybe_need_filling.is_some() && !scroller.new_item_needed() {
*visibility = Visibility::Inherited;
commands
.entity(scroller_entity)
.remove::<NeedInitialFilling>();
if let Some(on_init) = maybe_on_init {
commands.run_system_with_input(on_init.0, scroller_entity);
}
}
if !scroller.is_paused {
let update_step = delta / step * scroller.speed * scroller.direction.as_f32();
scroller.spawn_edge += update_step;
q_item
.iter_mut()
.filter(|(_, _, item)| item.parent == scroller_entity)
.for_each(|(mut transform, _, _)| {
transform.translation.x += update_step;
})
}
}
}
pub fn delete_items(
mut commands: Commands,
q_scroller_item: Query<(&ScrollerItem, Entity, &Transform)>,
q_scroller: Query<&Scroller>,
) {
for (scroller_item, entity, transform) in q_scroller_item.iter() {
if let Ok(scroller) = q_scroller.get(scroller_item.parent) {
if (scroller.end - transform.translation.x
+ scroller_item.size.x / 2. * scroller.direction.as_f32())
* scroller.direction.as_f32()
< 0.
{
commands.entity(entity).despawn_recursive();
}
}
}
}
#[cfg(test)]
mod test {
}