diff --git a/crates/bevy_winit/src/cursor.rs b/crates/bevy_winit/src/cursor.rs index 2dbcb12d7c..6e601f2429 100644 --- a/crates/bevy_winit/src/cursor.rs +++ b/crates/bevy_winit/src/cursor.rs @@ -8,7 +8,7 @@ use crate::{ use crate::{ custom_cursor::{ calculate_effective_rect, extract_and_transform_rgba_pixels, extract_rgba_pixels, - CustomCursorPlugin, + transform_hotspot, CustomCursorPlugin, }, state::{CustomCursorCache, CustomCursorCacheKey}, WinitCustomCursor, @@ -124,10 +124,13 @@ fn update_cursors( let (rect, needs_sub_image) = calculate_effective_rect(&texture_atlases, image, texture_atlas, rect); - let maybe_rgba = if *flip_x || *flip_y || needs_sub_image { - extract_and_transform_rgba_pixels(image, *flip_x, *flip_y, rect) + let (maybe_rgba, hotspot) = if *flip_x || *flip_y || needs_sub_image { + ( + extract_and_transform_rgba_pixels(image, *flip_x, *flip_y, rect), + transform_hotspot(*hotspot, *flip_x, *flip_y, rect), + ) } else { - extract_rgba_pixels(image) + (extract_rgba_pixels(image), *hotspot) }; let Some(rgba) = maybe_rgba else { diff --git a/crates/bevy_winit/src/custom_cursor.rs b/crates/bevy_winit/src/custom_cursor.rs index cc5854e7ed..fe6538b915 100644 --- a/crates/bevy_winit/src/custom_cursor.rs +++ b/crates/bevy_winit/src/custom_cursor.rs @@ -17,8 +17,14 @@ pub struct CustomCursorImage { /// An optional texture atlas used to render the image. pub texture_atlas: Option, /// Whether the image should be flipped along its x-axis. + /// + /// If true, the cursor's `hotspot` automatically flips along with the + /// image. pub flip_x: bool, /// Whether the image should be flipped along its y-axis. + /// + /// If true, the cursor's `hotspot` automatically flips along with the + /// image. 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 @@ -29,6 +35,10 @@ pub struct CustomCursorImage { pub rect: Option, /// X and Y coordinates of the hotspot in pixels. The hotspot must be within /// the image bounds. + /// + /// If you are flipping the image using `flip_x` or `flip_y`, you don't need + /// to adjust this field to account for the flip because it is adjusted + /// automatically. pub hotspot: (u16, u16), } @@ -184,6 +194,28 @@ pub(crate) fn extract_and_transform_rgba_pixels( Some(sub_image_data) } +/// Transforms the `hotspot` coordinates based on whether the image is flipped +/// or not. The `rect` is used to determine the image's dimensions. +pub(crate) fn transform_hotspot( + hotspot: (u16, u16), + flip_x: bool, + flip_y: bool, + rect: Rect, +) -> (u16, u16) { + let hotspot_x = hotspot.0 as f32; + let hotspot_y = hotspot.1 as f32; + let (width, height) = (rect.width(), rect.height()); + + let hotspot_x = if flip_x { width - hotspot_x } else { hotspot_x }; + let hotspot_y = if flip_y { + height - hotspot_y + } else { + hotspot_y + }; + + (hotspot_x as u16, hotspot_y as u16) +} + #[cfg(test)] mod tests { use bevy_app::App; @@ -542,4 +574,48 @@ mod tests { 0, 255, 255, 255, // Cyan ] ); + + #[test] + fn test_transform_hotspot_no_flip() { + let hotspot = (10, 20); + let rect = Rect { + min: Vec2::ZERO, + max: Vec2::new(100.0, 200.0), + }; + let transformed = transform_hotspot(hotspot, false, false, rect); + assert_eq!(transformed, (10, 20)); + } + + #[test] + fn test_transform_hotspot_flip_x() { + let hotspot = (10, 20); + let rect = Rect { + min: Vec2::ZERO, + max: Vec2::new(100.0, 200.0), + }; + let transformed = transform_hotspot(hotspot, true, false, rect); + assert_eq!(transformed, (90, 20)); + } + + #[test] + fn test_transform_hotspot_flip_y() { + let hotspot = (10, 20); + let rect = Rect { + min: Vec2::ZERO, + max: Vec2::new(100.0, 200.0), + }; + let transformed = transform_hotspot(hotspot, false, true, rect); + assert_eq!(transformed, (10, 180)); + } + + #[test] + fn test_transform_hotspot_flip_both() { + let hotspot = (10, 20); + let rect = Rect { + min: Vec2::ZERO, + max: Vec2::new(100.0, 200.0), + }; + let transformed = transform_hotspot(hotspot, true, true, rect); + assert_eq!(transformed, (90, 180)); + } }