Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/window/custom_cursor_image.rs
6595 views
1
//! Illustrates how to use a custom cursor image with a texture atlas and
2
//! animation.
3
4
use std::time::Duration;
5
6
use bevy::{
7
prelude::*,
8
window::{CursorIcon, CustomCursor, CustomCursorImage},
9
};
10
11
fn 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
31
fn 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
// Image to use as the cursor.
46
handle: asset_server
47
.load("cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png"),
48
// Optional texture atlas allows you to pick a section of the image
49
// and animate it.
50
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
// Optional section of the image to use as the cursor.
57
rect: None,
58
// The hotspot is the point in the cursor image that will be
59
// positioned at the mouse cursor's position.
60
hotspot: (0, 0),
61
})),
62
animation_config,
63
));
64
}
65
66
fn setup_camera(mut commands: Commands) {
67
commands.spawn(Camera3d::default());
68
}
69
70
fn setup_instructions(mut commands: Commands) {
71
commands.spawn((
72
Text::new(
73
"Press T to toggle the cursor's `texture_atlas`.\n
74
Press X to toggle the cursor's `flip_x` setting.\n
75
Press Y to toggle the cursor's `flip_y` setting.\n
76
Press C to cycle through the sections of the cursor's image using `rect`.",
77
),
78
Node {
79
position_type: PositionType::Absolute,
80
bottom: px(12),
81
left: px(12),
82
..default()
83
},
84
));
85
}
86
87
#[derive(Component)]
88
struct AnimationConfig {
89
first_sprite_index: usize,
90
last_sprite_index: usize,
91
increment: usize,
92
fps: u8,
93
frame_timer: Timer,
94
}
95
96
impl 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
112
/// This system loops through all the sprites in the [`CursorIcon`]'s
113
/// [`TextureAtlas`], from [`AnimationConfig`]'s `first_sprite_index` to
114
/// `last_sprite_index`.
115
fn 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.is_finished()
121
&& let Some(atlas) = image.texture_atlas.as_mut()
122
{
123
atlas.index += config.increment;
124
125
if atlas.index > config.last_sprite_index {
126
atlas.index = config.first_sprite_index;
127
}
128
129
config.frame_timer = AnimationConfig::timer_from_fps(config.fps);
130
}
131
}
132
}
133
}
134
135
fn toggle_texture_atlas(
136
input: Res<ButtonInput<KeyCode>>,
137
mut query: Query<&mut CursorIcon, With<Window>>,
138
mut cached_atlas: Local<Option<TextureAtlas>>, // this lets us restore the previous value
139
) {
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
// Save the current texture atlas.
146
*cached_atlas = Some(a.clone());
147
}
148
None => {
149
// Restore the cached texture atlas.
150
if let Some(cached_a) = cached_atlas.take() {
151
image.texture_atlas = Some(cached_a);
152
}
153
}
154
}
155
}
156
}
157
}
158
}
159
160
fn 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
173
fn 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
186
/// This system alternates the [`CursorIcon`]'s `rect` field between `None` and
187
/// 4 sections/rectangles of the cursor's image.
188
fn 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; // half the size of a tile in the texture atlas
194
195
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, // reset to None
213
];
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) // move to the next element
222
.unwrap_or(&None);
223
224
image.rect = *next_rect;
225
}
226
}
227
}
228
229