Skip to content

Commit

Permalink
switch to Bevy 0.16-dev / main and animate ani example (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgi388 authored Jan 16, 2025
1 parent 307f5b6 commit 047602f
Show file tree
Hide file tree
Showing 9 changed files with 769 additions and 326 deletions.
815 changes: 543 additions & 272 deletions Cargo.lock

Large diffs are not rendered by default.

31 changes: 24 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ license = "MIT OR Apache-2.0"
default = []
serde = ["dep:serde", "bitflags/serde", "ico/serde"]

# TODO: Change 0.16.0-dev to 0.16.0 once Bevy 0.16 is released.
[dependencies]
bevy_app = "0.15"
bevy_asset = "0.15"
bevy_image = "0.15"
bevy_reflect = "0.15"
bevy_sprite = "0.15"
bevy_app = "0.16.0-dev"
bevy_ecs = "0.16.0-dev"
bevy_asset = "0.16.0-dev"
bevy_image = "0.16.0-dev"
bevy_math = "0.16.0-dev"
bevy_reflect = "0.16.0-dev"
bevy_winit = { version = "0.16.0-dev", features = ["custom_cursor"] }
bitflags = { version = "2.7", default-features = false }
byteorder = "1.5"
ico = "0.4"
Expand All @@ -24,16 +27,30 @@ riff = "2"
serde = { version = "1", optional = true, default-features = false }
thiserror = "2"

# TODO: Change 0.16.0-dev to 0.16.0 once Bevy 0.16 is released.
[dev-dependencies]
bevy = { version = "0.15", default-features = false, features = [
"bevy_sprite",
bevy = { version = "0.16.0-dev", default-features = false, features = [
"bevy_ui",
"bevy_window",
"bevy_winit",
"custom_cursor",
"default_font",
"multi_threaded",
"png",
"x11",
] }
ron = "0.8"

[package.metadata.docs.rs]
all-features = true

# TODO: Remove once Bevy 0.16 is released.
[patch.crates-io]
bevy = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
bevy_app = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
bevy_asset = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
bevy_ecs = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
bevy_image = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
bevy_math = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
bevy_reflect = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
bevy_winit = { git = "https://github.com/bevyengine/bevy.git", branch = "main" }
106 changes: 87 additions & 19 deletions examples/ani.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use std::time::Duration;

use bevy::{
prelude::*,
winit::cursor::{CursorIcon, CustomCursor},
};
use bevy_cursor_kit::prelude::*;
use bevy_cursor_kit::{ani::animation::AnimationDuration, prelude::*};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(CursorAssetPlugin)
.add_systems(Startup, setup)
.add_systems(Update, insert_cursor)
.add_systems(Update, (insert_cursor, animate_cursor))
.run();
}

Expand Down Expand Up @@ -42,23 +44,89 @@ fn insert_cursor(
return;
};

let texture_atlas_index = 0;

commands
.entity(*window)
.insert(CursorIcon::Custom(CustomCursor::Image {
handle: c.image.clone(),
// TODO: Update for > Bevy 0.15.
//
// texture_atlas: Some(TextureAtlas {
// layout: c.texture_atlas_layout.clone(),
// index: texture_atlas_index,
// }),
// flip_x: false,
// flip_y: false,
// rect: None,
hotspot: c.hotspot_or_default(texture_atlas_index),
}));
commands.entity(*window).insert((
CursorIcon::Custom(CustomCursorImageBuilder::from_animated_cursor(c, None).build()),
c.hotspots.clone(),
AnimationConfig::new(
0,
c.animation.clips[0].atlas_indices.len() - 1,
match c.animation.clips[0].duration {
AnimationDuration::PerFrame(millis) => Duration::from_millis(millis as u64),
AnimationDuration::PerRepetition(_) => panic!("PerRepetition not supported"),
},
),
));

*setup = true;
}
#[derive(Component, Debug, Reflect)]
#[reflect(Debug, Component)]
struct AnimationConfig {
first_sprite_index: usize,
last_sprite_index: usize,
duration_per_frame: Duration,
frame_timer: Timer,
}

impl AnimationConfig {
fn new(first: usize, last: usize, duration_per_frame: Duration) -> Self {
Self {
first_sprite_index: first,
last_sprite_index: last,
duration_per_frame,
frame_timer: Timer::new(duration_per_frame, TimerMode::Once),
}
}
}

/// This system loops through all the sprites in the [`CursorIcon`]'s
/// [`TextureAtlas`], from [`AnimationConfig`]'s `first_sprite_index` to
/// `last_sprite_index`.
fn animate_cursor(
time: Res<Time>,
mut query: Query<(&mut CursorIcon, &CursorHotspots, &mut AnimationConfig)>,
) {
for (mut cursor_icon, hotspots, mut config) in &mut query {
let CursorIcon::Custom(CustomCursor::Image {
ref mut texture_atlas,
ref mut hotspot,
..
}) = *cursor_icon
else {
continue;
};

config.frame_timer.tick(time.delta());

if !config.frame_timer.finished() {
continue;
}

let Some(atlas) = texture_atlas else {
continue;
};

let mut new_index = atlas.index + 1;

if new_index > config.last_sprite_index {
new_index = config.first_sprite_index;
}

if new_index != atlas.index {
atlas.index = new_index;

info!("Changed to sprite index {}", atlas.index);
}

config.frame_timer = Timer::new(config.duration_per_frame, TimerMode::Once);

// Animation frames may have different hotspots, so we need to update
// the hotspot for each frame.
let new_hotspot = hotspots.get_or_default(atlas.index);
if new_hotspot != *hotspot {
*hotspot = new_hotspot;

info!("Changed to hotspot {:?}", hotspot);
}
}
}
25 changes: 4 additions & 21 deletions examples/cur.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use bevy::{
prelude::*,
winit::cursor::{CursorIcon, CustomCursor},
};
use bevy::{prelude::*, winit::cursor::CursorIcon};
use bevy_cursor_kit::prelude::*;

fn main() {
Expand Down Expand Up @@ -42,23 +39,9 @@ fn insert_cursor(
return;
};

let texture_atlas_index = 0;

commands
.entity(*window)
.insert(CursorIcon::Custom(CustomCursor::Image {
handle: c.image.clone(),
// TODO: Update for > Bevy 0.15.
//
// texture_atlas: Some(TextureAtlas {
// layout: c.texture_atlas_layout.clone(),
// index: texture_atlas_index,
// }),
// flip_x: false,
// flip_y: false,
// rect: None,
hotspot: c.hotspot_or_default(texture_atlas_index),
}));
commands.entity(*window).insert(CursorIcon::Custom(
CustomCursorImageBuilder::from_static_cursor(c, None).build(),
));

*setup = true;
}
3 changes: 1 addition & 2 deletions src/ani/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ use std::time::Duration;

use bevy_app::prelude::*;
use bevy_asset::{io::Reader, prelude::*, AssetLoader, LoadContext, RenderAssetUsages};
use bevy_image::Image;
use bevy_image::{Image, TextureAtlasBuilder, TextureAtlasBuilderError, TextureAtlasLayout};
use bevy_reflect::prelude::*;
use bevy_sprite::{TextureAtlasBuilder, TextureAtlasBuilderError, TextureAtlasLayout};
use ico::ResourceType;
use image::{DynamicImage, ImageBuffer};
use thiserror::Error;
Expand Down
101 changes: 101 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use bevy_asset::Handle;
use bevy_image::{Image, TextureAtlas};
use bevy_math::URect;
use bevy_reflect::prelude::*;
use bevy_winit::cursor::CustomCursor;

use crate::{ani::asset::AnimatedCursor, cur::asset::StaticCursor};

/// A builder for a [`CustomCursor::Image`].
#[derive(Debug, Default, Reflect)]
#[reflect(Debug, Default)]
pub struct CustomCursorImageBuilder {
handle: Handle<Image>,
texture_atlas: Option<TextureAtlas>,
flip_x: bool,
flip_y: bool,
rect: Option<URect>,
hotspot: (u16, u16),
}

impl CustomCursorImageBuilder {
/// Create a builder from a [`StaticCursor`].
///
/// The `index` parameter is used to select the texture atlas index to use.
/// If `None`, index 0 is used.
pub fn from_static_cursor(c: &StaticCursor, index: Option<usize>) -> Self {
Self {
handle: c.image.clone(),
texture_atlas: Some(TextureAtlas {
layout: c.texture_atlas_layout.clone(),
index: index.unwrap_or(0),
}),
hotspot: c.hotspot_or_default(index.unwrap_or(0)),
..Default::default()
}
}

/// Create a builder from an [`AnimatedCursor`].
///
/// The `index` parameter is used to select the texture atlas index to use.
/// If `None`, index 0 is used.
pub fn from_animated_cursor(c: &AnimatedCursor, index: Option<usize>) -> Self {
Self {
handle: c.image.clone(),
texture_atlas: Some(TextureAtlas {
layout: c.texture_atlas_layout.clone(),
index: index.unwrap_or(0),
}),
hotspot: c.hotspot_or_default(index.unwrap_or(0)),
..Default::default()
}
}

/// Set the handle.
pub fn handle(mut self, handle: Handle<Image>) -> Self {
self.handle = handle;
self
}

/// Set the texture atlas.
pub fn texture_atlas(mut self, texture_atlas: Option<TextureAtlas>) -> Self {
self.texture_atlas = texture_atlas;
self
}

/// Set whether to flip horizontally.
pub fn flip_x(mut self, flip_x: bool) -> Self {
self.flip_x = flip_x;
self
}

/// Set whether to flip vertically.
pub fn flip_y(mut self, flip_y: bool) -> Self {
self.flip_y = flip_y;
self
}

/// Set the rectangle.
pub fn rect(mut self, rect: URect) -> Self {
self.rect = Some(rect);
self
}

/// Set the hotspot.
pub fn hotspot(mut self, hotspot: (u16, u16)) -> Self {
self.hotspot = hotspot;
self
}

/// Build the `CustomCursor::Image`.
pub fn build(self) -> CustomCursor {
CustomCursor::Image {
handle: self.handle,
texture_atlas: self.texture_atlas,
flip_x: self.flip_x,
flip_y: self.flip_y,
rect: self.rect,
hotspot: self.hotspot,
}
}
}
3 changes: 1 addition & 2 deletions src/cur/asset.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use bevy_app::prelude::*;
use bevy_asset::{io::Reader, prelude::*, AssetLoader, LoadContext, RenderAssetUsages};
use bevy_image::Image;
use bevy_image::{Image, TextureAtlasBuilder, TextureAtlasBuilderError, TextureAtlasLayout};
use bevy_reflect::prelude::*;
use bevy_sprite::{TextureAtlasBuilder, TextureAtlasBuilderError, TextureAtlasLayout};
use ico::ResourceType;
use image::{DynamicImage, ImageBuffer};
use thiserror::Error;
Expand Down
5 changes: 3 additions & 2 deletions src/hotspot.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashMap;

use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Expand All @@ -12,9 +13,9 @@ use serde::{Deserialize, Serialize};
///
/// A hotspot is defined as a pair of `(x, y)` coordinates, where `(0, 0)` is
/// the top-left corner of the cursor's image.
#[derive(Debug, Clone, Default, Reflect)]
#[derive(Clone, Component, Debug, Default, Reflect)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[reflect(Debug, Default)]
#[reflect(Component, Debug, Default)]
#[cfg_attr(feature = "serde", reflect(Deserialize, Serialize))]
pub struct CursorHotspots {
/// The default hotspot for the cursor.
Expand Down
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ use bevy_app::prelude::*;
use crate::{ani::asset::AnimatedCursorAssetPlugin, cur::asset::StaticCursorAssetPlugin};

pub mod ani;
mod builder;
pub mod cur;
pub mod hotspot;

pub mod prelude {
#[doc(hidden)]
pub use crate::{ani::asset::*, cur::asset::*, CursorAssetPlugin};
pub use crate::{
ani::asset::AnimatedCursor, builder::CustomCursorImageBuilder, cur::asset::StaticCursor,
hotspot::CursorHotspots, CursorAssetPlugin,
};
}

pub struct CursorAssetPlugin;
Expand Down

0 comments on commit 047602f

Please sign in to comment.