Skip to content

Commit 14ad252

Browse files
authored
Make CustomCursor variants CustomCursorImage/CustomCursorUrl structs (#17518)
# Objective - Make `CustomCursor::Image` easier to work with by splitting the enum variants off into `CustomCursorImage` and `CustomCursorUrl` structs and deriving `Default` on those structs. - Refs #17276. ## Testing - Ran two examples: `cargo run --example custom_cursor_image --features=custom_cursor` and `cargo run --example window_settings --features=custom_cursor` - CI. --- ## Migration Guide The `CustomCursor` enum's variants now hold instances of `CustomCursorImage` or `CustomCursorUrl`. Update your uses of `CustomCursor` accordingly.
1 parent e459dd9 commit 14ad252

File tree

4 files changed

+87
-77
lines changed

4 files changed

+87
-77
lines changed

crates/bevy_winit/src/cursor.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use bevy_window::{SystemCursorIcon, Window};
3737
use tracing::warn;
3838

3939
#[cfg(feature = "custom_cursor")]
40-
pub use crate::custom_cursor::CustomCursor;
40+
pub use crate::custom_cursor::{CustomCursor, CustomCursorImage};
4141

4242
pub(crate) struct CursorPlugin;
4343

@@ -91,14 +91,16 @@ fn update_cursors(
9191

9292
let cursor_source = match cursor.as_ref() {
9393
#[cfg(feature = "custom_cursor")]
94-
CursorIcon::Custom(CustomCursor::Image {
95-
handle,
96-
texture_atlas,
97-
flip_x,
98-
flip_y,
99-
rect,
100-
hotspot,
101-
}) => {
94+
CursorIcon::Custom(CustomCursor::Image(c)) => {
95+
let CustomCursorImage {
96+
handle,
97+
texture_atlas,
98+
flip_x,
99+
flip_y,
100+
rect,
101+
hotspot,
102+
} = c;
103+
102104
let cache_key = CustomCursorCacheKey::Image {
103105
id: handle.id(),
104106
texture_atlas_layout_id: texture_atlas.as_ref().map(|a| a.layout.id()),
@@ -155,14 +157,15 @@ fn update_cursors(
155157
target_family = "wasm",
156158
target_os = "unknown"
157159
))]
158-
CursorIcon::Custom(CustomCursor::Url { url, hotspot }) => {
159-
let cache_key = CustomCursorCacheKey::Url(url.clone());
160+
CursorIcon::Custom(CustomCursor::Url(c)) => {
161+
let cache_key = CustomCursorCacheKey::Url(c.url.clone());
160162

161163
if cursor_cache.0.contains_key(&cache_key) {
162164
CursorSource::CustomCached(cache_key)
163165
} else {
164166
use crate::CustomCursorExtWebSys;
165-
let source = WinitCustomCursor::from_url(url.clone(), hotspot.0, hotspot.1);
167+
let source =
168+
WinitCustomCursor::from_url(c.url.clone(), c.hotspot.0, c.hotspot.1);
166169
CursorSource::Custom((cache_key, source))
167170
}
168171
}

crates/bevy_winit/src/custom_cursor.rs

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,57 @@ use bevy_app::{App, Plugin};
22
use bevy_asset::{Assets, Handle};
33
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasPlugin};
44
use bevy_math::{ops, Rect, URect, UVec2, Vec2};
5-
use bevy_reflect::Reflect;
5+
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
66
use wgpu_types::TextureFormat;
77

88
use crate::{cursor::CursorIcon, state::CustomCursorCache};
99

10+
/// A custom cursor created from an image.
11+
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
12+
#[reflect(Debug, Default, Hash, PartialEq)]
13+
pub struct CustomCursorImage {
14+
/// Handle to the image to use as the cursor. The image must be in 8 bit int
15+
/// or 32 bit float rgba. PNG images work well for this.
16+
pub handle: Handle<Image>,
17+
/// An optional texture atlas used to render the image.
18+
pub texture_atlas: Option<TextureAtlas>,
19+
/// Whether the image should be flipped along its x-axis.
20+
pub flip_x: bool,
21+
/// Whether the image should be flipped along its y-axis.
22+
pub flip_y: bool,
23+
/// An optional rectangle representing the region of the image to render,
24+
/// instead of rendering the full image. This is an easy one-off alternative
25+
/// to using a [`TextureAtlas`].
26+
///
27+
/// When used with a [`TextureAtlas`], the rect is offset by the atlas's
28+
/// minimal (top-left) corner position.
29+
pub rect: Option<URect>,
30+
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
31+
/// the image bounds.
32+
pub hotspot: (u16, u16),
33+
}
34+
35+
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
36+
/// A custom cursor created from a URL.
37+
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
38+
#[reflect(Debug, Default, Hash, PartialEq)]
39+
pub struct CustomCursorUrl {
40+
/// Web URL to an image to use as the cursor. PNGs are preferred. Cursor
41+
/// creation can fail if the image is invalid or not reachable.
42+
pub url: String,
43+
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
44+
/// the image bounds.
45+
pub hotspot: (u16, u16),
46+
}
47+
1048
/// Custom cursor image data.
1149
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)]
1250
pub enum CustomCursor {
13-
/// Image to use as a cursor.
14-
Image {
15-
/// The image must be in 8 bit int or 32 bit float rgba. PNG images
16-
/// work well for this.
17-
handle: Handle<Image>,
18-
/// The (optional) texture atlas used to render the image.
19-
texture_atlas: Option<TextureAtlas>,
20-
/// Whether the image should be flipped along its x-axis.
21-
flip_x: bool,
22-
/// Whether the image should be flipped along its y-axis.
23-
flip_y: bool,
24-
/// An optional rectangle representing the region of the image to
25-
/// render, instead of rendering the full image. This is an easy one-off
26-
/// alternative to using a [`TextureAtlas`].
27-
///
28-
/// When used with a [`TextureAtlas`], the rect is offset by the atlas's
29-
/// minimal (top-left) corner position.
30-
rect: Option<URect>,
31-
/// X and Y coordinates of the hotspot in pixels. The hotspot must be
32-
/// within the image bounds.
33-
hotspot: (u16, u16),
34-
},
51+
/// Use an image as the cursor.
52+
Image(CustomCursorImage),
3553
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
36-
/// A URL to an image to use as the cursor.
37-
Url {
38-
/// Web URL to an image to use as the cursor. PNGs preferred. Cursor
39-
/// creation can fail if the image is invalid or not reachable.
40-
url: String,
41-
/// X and Y coordinates of the hotspot in pixels. The hotspot must be
42-
/// within the image bounds.
43-
hotspot: (u16, u16),
44-
},
54+
/// Use a URL to an image as the cursor.
55+
Url(CustomCursorUrl),
4556
}
4657

4758
impl From<CustomCursor> for CursorIcon {

examples/window/custom_cursor_image.rs

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
44
use std::time::Duration;
55

6-
use bevy::winit::cursor::CustomCursor;
7-
use bevy::{prelude::*, winit::cursor::CursorIcon};
6+
use bevy::{
7+
prelude::*,
8+
winit::cursor::{CursorIcon, CustomCursor, CustomCursorImage},
9+
};
810

911
fn main() {
1012
App::new()
@@ -39,7 +41,7 @@ fn setup_cursor_icon(
3941
let animation_config = AnimationConfig::new(0, 199, 1, 4);
4042

4143
commands.entity(*window).insert((
42-
CursorIcon::Custom(CustomCursor::Image {
44+
CursorIcon::Custom(CustomCursor::Image(CustomCursorImage {
4345
// Image to use as the cursor.
4446
handle: asset_server
4547
.load("cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png"),
@@ -56,7 +58,7 @@ fn setup_cursor_icon(
5658
// The hotspot is the point in the cursor image that will be
5759
// positioned at the mouse cursor's position.
5860
hotspot: (0, 0),
59-
}),
61+
})),
6062
animation_config,
6163
));
6264
}
@@ -112,15 +114,11 @@ impl AnimationConfig {
112114
/// `last_sprite_index`.
113115
fn execute_animation(time: Res<Time>, mut query: Query<(&mut AnimationConfig, &mut CursorIcon)>) {
114116
for (mut config, mut cursor_icon) in &mut query {
115-
if let CursorIcon::Custom(CustomCursor::Image {
116-
ref mut texture_atlas,
117-
..
118-
}) = *cursor_icon
119-
{
117+
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
120118
config.frame_timer.tick(time.delta());
121119

122120
if config.frame_timer.finished() {
123-
if let Some(atlas) = texture_atlas {
121+
if let Some(atlas) = image.texture_atlas.as_mut() {
124122
atlas.index += config.increment;
125123

126124
if atlas.index > config.last_sprite_index {
@@ -141,18 +139,19 @@ fn toggle_texture_atlas(
141139
) {
142140
if input.just_pressed(KeyCode::KeyT) {
143141
for mut cursor_icon in &mut query {
144-
if let CursorIcon::Custom(CustomCursor::Image {
145-
ref mut texture_atlas,
146-
..
147-
}) = *cursor_icon
148-
{
149-
*texture_atlas = match texture_atlas.take() {
142+
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
143+
match image.texture_atlas.take() {
150144
Some(a) => {
145+
// Save the current texture atlas.
151146
*cached_atlas = Some(a.clone());
152-
None
153147
}
154-
None => cached_atlas.take(),
155-
};
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+
}
156155
}
157156
}
158157
}
@@ -164,8 +163,8 @@ fn toggle_flip_x(
164163
) {
165164
if input.just_pressed(KeyCode::KeyX) {
166165
for mut cursor_icon in &mut query {
167-
if let CursorIcon::Custom(CustomCursor::Image { ref mut flip_x, .. }) = *cursor_icon {
168-
*flip_x = !*flip_x;
166+
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
167+
image.flip_x = !image.flip_x;
169168
}
170169
}
171170
}
@@ -177,8 +176,8 @@ fn toggle_flip_y(
177176
) {
178177
if input.just_pressed(KeyCode::KeyY) {
179178
for mut cursor_icon in &mut query {
180-
if let CursorIcon::Custom(CustomCursor::Image { ref mut flip_y, .. }) = *cursor_icon {
181-
*flip_y = !*flip_y;
179+
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
180+
image.flip_y = !image.flip_y;
182181
}
183182
}
184183
}
@@ -214,15 +213,15 @@ fn cycle_rect(input: Res<ButtonInput<KeyCode>>, mut query: Query<&mut CursorIcon
214213
];
215214

216215
for mut cursor_icon in &mut query {
217-
if let CursorIcon::Custom(CustomCursor::Image { ref mut rect, .. }) = *cursor_icon {
216+
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
218217
let next_rect = SECTIONS
219218
.iter()
220219
.cycle()
221-
.skip_while(|&&corner| corner != *rect)
220+
.skip_while(|&&corner| corner != image.rect)
222221
.nth(1) // move to the next element
223222
.unwrap_or(&None);
224223

225-
*rect = *next_rect;
224+
image.rect = *next_rect;
226225
}
227226
}
228227
}

examples/window/window_settings.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! the mouse pointer in various ways.
33
44
#[cfg(feature = "custom_cursor")]
5-
use bevy::winit::cursor::CustomCursor;
5+
use bevy::winit::cursor::{CustomCursor, CustomCursorImage};
66
use bevy::{
77
diagnostic::{FrameCount, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
88
prelude::*,
@@ -163,14 +163,11 @@ fn init_cursor_icons(
163163
SystemCursorIcon::Wait.into(),
164164
SystemCursorIcon::Text.into(),
165165
#[cfg(feature = "custom_cursor")]
166-
CustomCursor::Image {
166+
CustomCursor::Image(CustomCursorImage {
167167
handle: asset_server.load("branding/icon.png"),
168-
texture_atlas: None,
169-
flip_x: false,
170-
flip_y: false,
171-
rect: None,
172168
hotspot: (128, 128),
173-
}
169+
..Default::default()
170+
})
174171
.into(),
175172
]));
176173
}

0 commit comments

Comments
 (0)