diff --git a/crates/bevy_window/src/cursor.rs b/crates/bevy_window/src/cursor.rs index 3a68b7ee929a0..fab72701feb26 100644 --- a/crates/bevy_window/src/cursor.rs +++ b/crates/bevy_window/src/cursor.rs @@ -75,14 +75,12 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// The icon to display for a [`Window`](crate::window::Window)'s [`Cursor`](crate::window::Cursor). /// -/// Examples of all of these cursors can be found [here](https://www.w3schools.com/cssref/playit.php?filename=playcss_cursor&preval=crosshair). -/// This `enum` is simply a copy of a similar `enum` found in [`winit`](https://docs.rs/winit/latest/winit/window/enum.CursorIcon.html). -/// `winit`, in turn, is based upon the [CSS3 UI spec](https://www.w3.org/TR/css-ui-3/#cursor). +/// Bevy supports two types of cursors, system cursors and custom ones. /// /// See the [`window_settings`] example for usage. /// /// [`window_settings`]: https://github.com/bevyengine/bevy/blob/latest/examples/window/window_settings.rs -#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Reflect)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -90,6 +88,60 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; )] #[reflect(Debug, PartialEq, Default)] pub enum CursorIcon { + /// Represents a system cursor. + System(SystemCursor), + /// Represents a custom cursor image. + Custom(CustomCursor), +} + +impl Default for CursorIcon { + fn default() -> Self { + Self::System(SystemCursor::default()) + } +} + +impl From for CursorIcon { + fn from(system: SystemCursor) -> Self { + Self::System(system) + } +} + +impl From for CursorIcon { + fn from(custom: CustomCursor) -> Self { + Self::Custom(custom) + } +} + +/// Represents a custom cursor image. +/// +/// This `struct` matches the arguments to [`winit`'s `CustomCursor::from_rgba`](https://docs.rs/winit/latest/winit/window/struct.CustomCursor.html#method.from_rgba). +/// +/// See the [`window_settings`] example for usage. +/// +/// [`window_settings`]: https://github.com/bevyengine/bevy/blob/latest/examples/window/window_settings.rs +#[derive(Debug, Hash, PartialEq, Eq, Clone, Reflect)] +pub struct CustomCursor { + /// RGBA values for custom cursor + pub rgba: Vec, + /// Width of the cursor image + pub width: u16, + /// Height of the cursor image + pub height: u16, + /// The hotspot of the cursor is where the user is pointing, relative to the top-left of the cursor + pub hotspot: (u16, u16), +} + +/// Represents a system cursor. +/// +/// Examples of all of these cursors can be found [here](https://www.w3schools.com/cssref/playit.php?filename=playcss_cursor&preval=crosshair). +/// This `enum` is simply a copy of a similar `enum` found in [`winit`](https://docs.rs/winit/latest/winit/window/enum.CursorIcon.html). +/// `winit`, in turn, is based upon the [CSS3 UI spec](https://www.w3.org/TR/css-ui-3/#cursor). +/// +/// See the [`window_settings`] example for usage. +/// +/// [`window_settings`]: https://github.com/bevyengine/bevy/blob/latest/examples/window/window_settings.rs +#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)] +pub enum SystemCursor { /// The platform-dependent default cursor. Often rendered as arrow. #[default] Default, diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 8bc0cf902904e..baf233f8ec899 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -542,7 +542,7 @@ impl WindowResizeConstraints { } /// Cursor data for a [`Window`]. -#[derive(Debug, Copy, Clone, Reflect)] +#[derive(Debug, Clone, Reflect)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -585,7 +585,7 @@ pub struct Cursor { impl Default for Cursor { fn default() -> Self { Cursor { - icon: CursorIcon::Default, + icon: crate::SystemCursor::Default.into(), visible: true, grab_mode: CursorGrabMode::None, hit_test: true, diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index 5cc2c03475528..1cca5b9d00427 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -6,7 +6,7 @@ use bevy_input::{ ButtonState, }; use bevy_math::Vec2; -use bevy_window::{CursorIcon, EnabledButtons, WindowLevel, WindowTheme}; +use bevy_window::{CursorIcon, EnabledButtons, SystemCursor, WindowLevel, WindowTheme}; use winit::keyboard::{Key, NamedKey, NativeKey}; pub fn convert_keyboard_input( @@ -627,43 +627,60 @@ pub fn convert_native_key(native_key: &NativeKey) -> bevy_input::keyboard::Nativ } } -pub fn convert_cursor_icon(cursor_icon: CursorIcon) -> winit::window::CursorIcon { - match cursor_icon { - CursorIcon::Crosshair => winit::window::CursorIcon::Crosshair, - CursorIcon::Pointer => winit::window::CursorIcon::Pointer, - CursorIcon::Move => winit::window::CursorIcon::Move, - CursorIcon::Text => winit::window::CursorIcon::Text, - CursorIcon::Wait => winit::window::CursorIcon::Wait, - CursorIcon::Help => winit::window::CursorIcon::Help, - CursorIcon::Progress => winit::window::CursorIcon::Progress, - CursorIcon::NotAllowed => winit::window::CursorIcon::NotAllowed, - CursorIcon::ContextMenu => winit::window::CursorIcon::ContextMenu, - CursorIcon::Cell => winit::window::CursorIcon::Cell, - CursorIcon::VerticalText => winit::window::CursorIcon::VerticalText, - CursorIcon::Alias => winit::window::CursorIcon::Alias, - CursorIcon::Copy => winit::window::CursorIcon::Copy, - CursorIcon::NoDrop => winit::window::CursorIcon::NoDrop, - CursorIcon::Grab => winit::window::CursorIcon::Grab, - CursorIcon::Grabbing => winit::window::CursorIcon::Grabbing, - CursorIcon::AllScroll => winit::window::CursorIcon::AllScroll, - CursorIcon::ZoomIn => winit::window::CursorIcon::ZoomIn, - CursorIcon::ZoomOut => winit::window::CursorIcon::ZoomOut, - CursorIcon::EResize => winit::window::CursorIcon::EResize, - CursorIcon::NResize => winit::window::CursorIcon::NResize, - CursorIcon::NeResize => winit::window::CursorIcon::NeResize, - CursorIcon::NwResize => winit::window::CursorIcon::NwResize, - CursorIcon::SResize => winit::window::CursorIcon::SResize, - CursorIcon::SeResize => winit::window::CursorIcon::SeResize, - CursorIcon::SwResize => winit::window::CursorIcon::SwResize, - CursorIcon::WResize => winit::window::CursorIcon::WResize, - CursorIcon::EwResize => winit::window::CursorIcon::EwResize, - CursorIcon::NsResize => winit::window::CursorIcon::NsResize, - CursorIcon::NeswResize => winit::window::CursorIcon::NeswResize, - CursorIcon::NwseResize => winit::window::CursorIcon::NwseResize, - CursorIcon::ColResize => winit::window::CursorIcon::ColResize, - CursorIcon::RowResize => winit::window::CursorIcon::RowResize, - _ => winit::window::CursorIcon::Default, - } +pub fn convert_cursor_icon( + cursor_icon: CursorIcon, + event_loop: &winit::event_loop::ActiveEventLoop, +) -> impl Into { + let cursor: winit::window::Cursor = match cursor_icon { + CursorIcon::System(system) => match system { + SystemCursor::Crosshair => winit::window::CursorIcon::Crosshair.into(), + SystemCursor::Pointer => winit::window::CursorIcon::Pointer.into(), + SystemCursor::Move => winit::window::CursorIcon::Move.into(), + SystemCursor::Text => winit::window::CursorIcon::Text.into(), + SystemCursor::Wait => winit::window::CursorIcon::Wait.into(), + SystemCursor::Help => winit::window::CursorIcon::Help.into(), + SystemCursor::Progress => winit::window::CursorIcon::Progress.into(), + SystemCursor::NotAllowed => winit::window::CursorIcon::NotAllowed.into(), + SystemCursor::ContextMenu => winit::window::CursorIcon::ContextMenu.into(), + SystemCursor::Cell => winit::window::CursorIcon::Cell.into(), + SystemCursor::VerticalText => winit::window::CursorIcon::VerticalText.into(), + SystemCursor::Alias => winit::window::CursorIcon::Alias.into(), + SystemCursor::Copy => winit::window::CursorIcon::Copy.into(), + SystemCursor::NoDrop => winit::window::CursorIcon::NoDrop.into(), + SystemCursor::Grab => winit::window::CursorIcon::Grab.into(), + SystemCursor::Grabbing => winit::window::CursorIcon::Grabbing.into(), + SystemCursor::AllScroll => winit::window::CursorIcon::AllScroll.into(), + SystemCursor::ZoomIn => winit::window::CursorIcon::ZoomIn.into(), + SystemCursor::ZoomOut => winit::window::CursorIcon::ZoomOut.into(), + SystemCursor::EResize => winit::window::CursorIcon::EResize.into(), + SystemCursor::NResize => winit::window::CursorIcon::NResize.into(), + SystemCursor::NeResize => winit::window::CursorIcon::NeResize.into(), + SystemCursor::NwResize => winit::window::CursorIcon::NwResize.into(), + SystemCursor::SResize => winit::window::CursorIcon::SResize.into(), + SystemCursor::SeResize => winit::window::CursorIcon::SeResize.into(), + SystemCursor::SwResize => winit::window::CursorIcon::SwResize.into(), + SystemCursor::WResize => winit::window::CursorIcon::WResize.into(), + SystemCursor::EwResize => winit::window::CursorIcon::EwResize.into(), + SystemCursor::NsResize => winit::window::CursorIcon::NsResize.into(), + SystemCursor::NeswResize => winit::window::CursorIcon::NeswResize.into(), + SystemCursor::NwseResize => winit::window::CursorIcon::NwseResize.into(), + SystemCursor::ColResize => winit::window::CursorIcon::ColResize.into(), + SystemCursor::RowResize => winit::window::CursorIcon::RowResize.into(), + _ => winit::window::CursorIcon::Default.into(), + }, + CursorIcon::Custom(custom) => { + let source = winit::window::CustomCursor::from_rgba( + custom.rgba, + custom.width, + custom.height, + custom.hotspot.0, + custom.hotspot.1, + ); + let custom_cursor = event_loop.create_custom_cursor(source.unwrap()); + custom_cursor.into() + } + }; + cursor } pub fn convert_window_level(window_level: WindowLevel) -> winit::window::WindowLevel { diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 7a0dd51a9a861..37c46349ab1e4 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -278,7 +278,10 @@ pub(crate) fn changed_windows( } if window.cursor.icon != cache.window.cursor.icon { - winit_window.set_cursor(converters::convert_cursor_icon(window.cursor.icon)); + winit_window.set_cursor(converters::convert_cursor_icon( + window.cursor.icon.clone(), + event_loop, + )); } if window.cursor.grab_mode != cache.window.cursor.grab_mode {