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.
This commit is contained in:
parent
e459dd94ec
commit
14ad25227b
@ -37,7 +37,7 @@ use bevy_window::{SystemCursorIcon, Window};
|
||||
use tracing::warn;
|
||||
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
pub use crate::custom_cursor::CustomCursor;
|
||||
pub use crate::custom_cursor::{CustomCursor, CustomCursorImage};
|
||||
|
||||
pub(crate) struct CursorPlugin;
|
||||
|
||||
@ -91,14 +91,16 @@ fn update_cursors(
|
||||
|
||||
let cursor_source = match cursor.as_ref() {
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
CursorIcon::Custom(CustomCursor::Image {
|
||||
CursorIcon::Custom(CustomCursor::Image(c)) => {
|
||||
let CustomCursorImage {
|
||||
handle,
|
||||
texture_atlas,
|
||||
flip_x,
|
||||
flip_y,
|
||||
rect,
|
||||
hotspot,
|
||||
}) => {
|
||||
} = c;
|
||||
|
||||
let cache_key = CustomCursorCacheKey::Image {
|
||||
id: handle.id(),
|
||||
texture_atlas_layout_id: texture_atlas.as_ref().map(|a| a.layout.id()),
|
||||
@ -155,14 +157,15 @@ fn update_cursors(
|
||||
target_family = "wasm",
|
||||
target_os = "unknown"
|
||||
))]
|
||||
CursorIcon::Custom(CustomCursor::Url { url, hotspot }) => {
|
||||
let cache_key = CustomCursorCacheKey::Url(url.clone());
|
||||
CursorIcon::Custom(CustomCursor::Url(c)) => {
|
||||
let cache_key = CustomCursorCacheKey::Url(c.url.clone());
|
||||
|
||||
if cursor_cache.0.contains_key(&cache_key) {
|
||||
CursorSource::CustomCached(cache_key)
|
||||
} else {
|
||||
use crate::CustomCursorExtWebSys;
|
||||
let source = WinitCustomCursor::from_url(url.clone(), hotspot.0, hotspot.1);
|
||||
let source =
|
||||
WinitCustomCursor::from_url(c.url.clone(), c.hotspot.0, c.hotspot.1);
|
||||
CursorSource::Custom((cache_key, source))
|
||||
}
|
||||
}
|
||||
|
@ -2,46 +2,57 @@ use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{Assets, Handle};
|
||||
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasPlugin};
|
||||
use bevy_math::{ops, Rect, URect, UVec2, Vec2};
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use wgpu_types::TextureFormat;
|
||||
|
||||
use crate::{cursor::CursorIcon, state::CustomCursorCache};
|
||||
|
||||
/// Custom cursor image data.
|
||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)]
|
||||
pub enum CustomCursor {
|
||||
/// Image to use as a cursor.
|
||||
Image {
|
||||
/// The image must be in 8 bit int or 32 bit float rgba. PNG images
|
||||
/// work well for this.
|
||||
handle: Handle<Image>,
|
||||
/// The (optional) texture atlas used to render the image.
|
||||
texture_atlas: Option<TextureAtlas>,
|
||||
/// A custom cursor created from an image.
|
||||
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
|
||||
#[reflect(Debug, Default, Hash, PartialEq)]
|
||||
pub struct CustomCursorImage {
|
||||
/// Handle to the image to use as the cursor. The image must be in 8 bit int
|
||||
/// or 32 bit float rgba. PNG images work well for this.
|
||||
pub handle: Handle<Image>,
|
||||
/// An optional texture atlas used to render the image.
|
||||
pub texture_atlas: Option<TextureAtlas>,
|
||||
/// Whether the image should be flipped along its x-axis.
|
||||
flip_x: bool,
|
||||
pub flip_x: bool,
|
||||
/// Whether the image should be flipped along its y-axis.
|
||||
flip_y: bool,
|
||||
/// An optional rectangle representing the region of the image to
|
||||
/// render, instead of rendering the full image. This is an easy one-off
|
||||
/// alternative to using a [`TextureAtlas`].
|
||||
pub flip_y: bool,
|
||||
/// An optional rectangle representing the region of the image to render,
|
||||
/// instead of rendering the full image. This is an easy one-off alternative
|
||||
/// to using a [`TextureAtlas`].
|
||||
///
|
||||
/// When used with a [`TextureAtlas`], the rect is offset by the atlas's
|
||||
/// minimal (top-left) corner position.
|
||||
rect: Option<URect>,
|
||||
/// X and Y coordinates of the hotspot in pixels. The hotspot must be
|
||||
/// within the image bounds.
|
||||
hotspot: (u16, u16),
|
||||
},
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
/// A URL to an image to use as the cursor.
|
||||
Url {
|
||||
/// Web URL to an image to use as the cursor. PNGs preferred. Cursor
|
||||
pub rect: Option<URect>,
|
||||
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
|
||||
/// the image bounds.
|
||||
pub hotspot: (u16, u16),
|
||||
}
|
||||
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
/// A custom cursor created from a URL.
|
||||
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
|
||||
#[reflect(Debug, Default, Hash, PartialEq)]
|
||||
pub struct CustomCursorUrl {
|
||||
/// Web URL to an image to use as the cursor. PNGs are preferred. Cursor
|
||||
/// creation can fail if the image is invalid or not reachable.
|
||||
url: String,
|
||||
/// X and Y coordinates of the hotspot in pixels. The hotspot must be
|
||||
/// within the image bounds.
|
||||
hotspot: (u16, u16),
|
||||
},
|
||||
pub url: String,
|
||||
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
|
||||
/// the image bounds.
|
||||
pub hotspot: (u16, u16),
|
||||
}
|
||||
|
||||
/// Custom cursor image data.
|
||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)]
|
||||
pub enum CustomCursor {
|
||||
/// Use an image as the cursor.
|
||||
Image(CustomCursorImage),
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
/// Use a URL to an image as the cursor.
|
||||
Url(CustomCursorUrl),
|
||||
}
|
||||
|
||||
impl From<CustomCursor> for CursorIcon {
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy::winit::cursor::CustomCursor;
|
||||
use bevy::{prelude::*, winit::cursor::CursorIcon};
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
winit::cursor::{CursorIcon, CustomCursor, CustomCursorImage},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -39,7 +41,7 @@ fn setup_cursor_icon(
|
||||
let animation_config = AnimationConfig::new(0, 199, 1, 4);
|
||||
|
||||
commands.entity(*window).insert((
|
||||
CursorIcon::Custom(CustomCursor::Image {
|
||||
CursorIcon::Custom(CustomCursor::Image(CustomCursorImage {
|
||||
// Image to use as the cursor.
|
||||
handle: asset_server
|
||||
.load("cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png"),
|
||||
@ -56,7 +58,7 @@ fn setup_cursor_icon(
|
||||
// The hotspot is the point in the cursor image that will be
|
||||
// positioned at the mouse cursor's position.
|
||||
hotspot: (0, 0),
|
||||
}),
|
||||
})),
|
||||
animation_config,
|
||||
));
|
||||
}
|
||||
@ -112,15 +114,11 @@ impl AnimationConfig {
|
||||
/// `last_sprite_index`.
|
||||
fn execute_animation(time: Res<Time>, mut query: Query<(&mut AnimationConfig, &mut CursorIcon)>) {
|
||||
for (mut config, mut cursor_icon) in &mut query {
|
||||
if let CursorIcon::Custom(CustomCursor::Image {
|
||||
ref mut texture_atlas,
|
||||
..
|
||||
}) = *cursor_icon
|
||||
{
|
||||
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
|
||||
config.frame_timer.tick(time.delta());
|
||||
|
||||
if config.frame_timer.finished() {
|
||||
if let Some(atlas) = texture_atlas {
|
||||
if let Some(atlas) = image.texture_atlas.as_mut() {
|
||||
atlas.index += config.increment;
|
||||
|
||||
if atlas.index > config.last_sprite_index {
|
||||
@ -141,18 +139,19 @@ fn toggle_texture_atlas(
|
||||
) {
|
||||
if input.just_pressed(KeyCode::KeyT) {
|
||||
for mut cursor_icon in &mut query {
|
||||
if let CursorIcon::Custom(CustomCursor::Image {
|
||||
ref mut texture_atlas,
|
||||
..
|
||||
}) = *cursor_icon
|
||||
{
|
||||
*texture_atlas = match texture_atlas.take() {
|
||||
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
|
||||
match image.texture_atlas.take() {
|
||||
Some(a) => {
|
||||
// Save the current texture atlas.
|
||||
*cached_atlas = Some(a.clone());
|
||||
None
|
||||
}
|
||||
None => cached_atlas.take(),
|
||||
};
|
||||
None => {
|
||||
// Restore the cached texture atlas.
|
||||
if let Some(cached_a) = cached_atlas.take() {
|
||||
image.texture_atlas = Some(cached_a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,8 +163,8 @@ fn toggle_flip_x(
|
||||
) {
|
||||
if input.just_pressed(KeyCode::KeyX) {
|
||||
for mut cursor_icon in &mut query {
|
||||
if let CursorIcon::Custom(CustomCursor::Image { ref mut flip_x, .. }) = *cursor_icon {
|
||||
*flip_x = !*flip_x;
|
||||
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
|
||||
image.flip_x = !image.flip_x;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,8 +176,8 @@ fn toggle_flip_y(
|
||||
) {
|
||||
if input.just_pressed(KeyCode::KeyY) {
|
||||
for mut cursor_icon in &mut query {
|
||||
if let CursorIcon::Custom(CustomCursor::Image { ref mut flip_y, .. }) = *cursor_icon {
|
||||
*flip_y = !*flip_y;
|
||||
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
|
||||
image.flip_y = !image.flip_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -214,15 +213,15 @@ fn cycle_rect(input: Res<ButtonInput<KeyCode>>, mut query: Query<&mut CursorIcon
|
||||
];
|
||||
|
||||
for mut cursor_icon in &mut query {
|
||||
if let CursorIcon::Custom(CustomCursor::Image { ref mut rect, .. }) = *cursor_icon {
|
||||
if let CursorIcon::Custom(CustomCursor::Image(ref mut image)) = *cursor_icon {
|
||||
let next_rect = SECTIONS
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip_while(|&&corner| corner != *rect)
|
||||
.skip_while(|&&corner| corner != image.rect)
|
||||
.nth(1) // move to the next element
|
||||
.unwrap_or(&None);
|
||||
|
||||
*rect = *next_rect;
|
||||
image.rect = *next_rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! the mouse pointer in various ways.
|
||||
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
use bevy::winit::cursor::CustomCursor;
|
||||
use bevy::winit::cursor::{CustomCursor, CustomCursorImage};
|
||||
use bevy::{
|
||||
diagnostic::{FrameCount, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
prelude::*,
|
||||
@ -163,14 +163,11 @@ fn init_cursor_icons(
|
||||
SystemCursorIcon::Wait.into(),
|
||||
SystemCursorIcon::Text.into(),
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
CustomCursor::Image {
|
||||
CustomCursor::Image(CustomCursorImage {
|
||||
handle: asset_server.load("branding/icon.png"),
|
||||
texture_atlas: None,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
rect: None,
|
||||
hotspot: (128, 128),
|
||||
}
|
||||
..Default::default()
|
||||
})
|
||||
.into(),
|
||||
]));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user