1use std::time::Duration;
5
6use bevy::{
7 prelude::*,
8 winit::cursor::{CursorIcon, CustomCursor, CustomCursorImage},
9};
10
11fn main() {
12 App::new()
13 .add_plugins(DefaultPlugins)
14 .add_systems(
15 Startup,
16 (setup_cursor_icon, setup_camera, setup_instructions),
17 )
18 .add_systems(
19 Update,
20 (
21 execute_animation,
22 toggle_texture_atlas,
23 toggle_flip_x,
24 toggle_flip_y,
25 cycle_rect,
26 ),
27 )
28 .run();
29}
30
31fn setup_cursor_icon(
32 mut commands: Commands,
33 asset_server: Res<AssetServer>,
34 mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
35 window: Single<Entity, With<Window>>,
36) {
37 let layout =
38 TextureAtlasLayout::from_grid(UVec2::splat(64), 20, 10, Some(UVec2::splat(5)), None);
39 let texture_atlas_layout = texture_atlas_layouts.add(layout);
40
41 let animation_config = AnimationConfig::new(0, 199, 1, 4);
42
43 commands.entity(*window).insert((
44 CursorIcon::Custom(CustomCursor::Image(CustomCursorImage {
45 handle: asset_server
47 .load("cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png"),
48 texture_atlas: Some(TextureAtlas {
51 layout: texture_atlas_layout.clone(),
52 index: animation_config.first_sprite_index,
53 }),
54 flip_x: false,
55 flip_y: false,
56 rect: None,
58 hotspot: (0, 0),
61 })),
62 animation_config,
63 ));
64}
65
66fn setup_camera(mut commands: Commands) {
67 commands.spawn(Camera3d::default());
68}
69
70fn setup_instructions(mut commands: Commands) {
71 commands.spawn((
72 Text::new(
73 "Press T to toggle the cursor's `texture_atlas`.\n
74Press X to toggle the cursor's `flip_x` setting.\n
75Press Y to toggle the cursor's `flip_y` setting.\n
76Press C to cycle through the sections of the cursor's image using `rect`.",
77 ),
78 Node {
79 position_type: PositionType::Absolute,
80 bottom: Val::Px(12.0),
81 left: Val::Px(12.0),
82 ..default()
83 },
84 ));
85}
86
87#[derive(Component)]
88struct AnimationConfig {
89 first_sprite_index: usize,
90 last_sprite_index: usize,
91 increment: usize,
92 fps: u8,
93 frame_timer: Timer,
94}
95
96impl AnimationConfig {
97 fn new(first: usize, last: usize, increment: usize, fps: u8) -> Self {
98 Self {
99 first_sprite_index: first,
100 last_sprite_index: last,
101 increment,
102 fps,
103 frame_timer: Self::timer_from_fps(fps),
104 }
105 }
106
107 fn timer_from_fps(fps: u8) -> Timer {
108 Timer::new(Duration::from_secs_f32(1.0 / (fps as f32)), TimerMode::Once)
109 }
110}
111
112fn execute_animation(time: Res<Time>, mut query: Query<(&mut AnimationConfig, &mut CursorIcon)>) {
116 for (mut config, mut cursor_icon) in &mut query {
117 if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
118 config.frame_timer.tick(time.delta());
119
120 if config.frame_timer.finished() {
121 if let Some(atlas) = image.texture_atlas.as_mut() {
122 atlas.index += config.increment;
123
124 if atlas.index > config.last_sprite_index {
125 atlas.index = config.first_sprite_index;
126 }
127
128 config.frame_timer = AnimationConfig::timer_from_fps(config.fps);
129 }
130 }
131 }
132 }
133}
134
135fn toggle_texture_atlas(
136 input: Res<ButtonInput<KeyCode>>,
137 mut query: Query<&mut CursorIcon, With<Window>>,
138 mut cached_atlas: Local<Option<TextureAtlas>>, ) {
140 if input.just_pressed(KeyCode::KeyT) {
141 for mut cursor_icon in &mut query {
142 if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
143 match image.texture_atlas.take() {
144 Some(a) => {
145 *cached_atlas = Some(a.clone());
147 }
148 None => {
149 if let Some(cached_a) = cached_atlas.take() {
151 image.texture_atlas = Some(cached_a);
152 }
153 }
154 }
155 }
156 }
157 }
158}
159
160fn toggle_flip_x(
161 input: Res<ButtonInput<KeyCode>>,
162 mut query: Query<&mut CursorIcon, With<Window>>,
163) {
164 if input.just_pressed(KeyCode::KeyX) {
165 for mut cursor_icon in &mut query {
166 if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
167 image.flip_x = !image.flip_x;
168 }
169 }
170 }
171}
172
173fn toggle_flip_y(
174 input: Res<ButtonInput<KeyCode>>,
175 mut query: Query<&mut CursorIcon, With<Window>>,
176) {
177 if input.just_pressed(KeyCode::KeyY) {
178 for mut cursor_icon in &mut query {
179 if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
180 image.flip_y = !image.flip_y;
181 }
182 }
183 }
184}
185
186fn cycle_rect(input: Res<ButtonInput<KeyCode>>, mut query: Query<&mut CursorIcon, With<Window>>) {
189 if !input.just_pressed(KeyCode::KeyC) {
190 return;
191 }
192
193 const RECT_SIZE: u32 = 32; const SECTIONS: [Option<URect>; 5] = [
196 Some(URect {
197 min: UVec2::ZERO,
198 max: UVec2::splat(RECT_SIZE),
199 }),
200 Some(URect {
201 min: UVec2::new(RECT_SIZE, 0),
202 max: UVec2::new(2 * RECT_SIZE, RECT_SIZE),
203 }),
204 Some(URect {
205 min: UVec2::new(0, RECT_SIZE),
206 max: UVec2::new(RECT_SIZE, 2 * RECT_SIZE),
207 }),
208 Some(URect {
209 min: UVec2::new(RECT_SIZE, RECT_SIZE),
210 max: UVec2::splat(2 * RECT_SIZE),
211 }),
212 None, ];
214
215 for mut cursor_icon in &mut query {
216 if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
217 let next_rect = SECTIONS
218 .iter()
219 .cycle()
220 .skip_while(|&&corner| corner != image.rect)
221 .nth(1) .unwrap_or(&None);
223
224 image.rect = *next_rect;
225 }
226 }
227}