diff --git a/Cargo.toml b/Cargo.toml index 4b4f00b090393..b928b0841ec16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,6 +132,7 @@ default = [ "bevy_asset", "bevy_audio", "bevy_color", + "bevy_cursor_icon", "bevy_core_pipeline", "bevy_core_widgets", "bevy_anti_aliasing", @@ -212,6 +213,9 @@ bevy_audio = ["bevy_internal/bevy_audio"] # Provides shared color types and operations bevy_color = ["bevy_internal/bevy_color"] +# Provides cursor +bevy_cursor_icon = ["bevy_internal/bevy_cursor_icon"] + # Provides cameras and other basic render pipeline features bevy_core_pipeline = [ "bevy_internal/bevy_core_pipeline", diff --git a/crates/bevy_cursor_icon/Cargo.toml b/crates/bevy_cursor_icon/Cargo.toml new file mode 100644 index 0000000000000..40f2052e19717 --- /dev/null +++ b/crates/bevy_cursor_icon/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bevy_cursor_icon" +version = "0.17.0-dev" +edition = "2024" +description = "Provides custom cursor icon support" +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[features] +default = [] + +# Functionality + +## Custom cursor image +custom_cursor = ["bevy_image", "bevy_asset"] + +# Platform Compatibility + +[dependencies] +bevy_app = { path = "../bevy_app", version = "0.17.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } + +# bevy optional +bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev", optional = true } +bevy_image = { path = "../bevy_image", version = "0.17.0-dev", optional = true } diff --git a/crates/bevy_cursor_icon/LICENSE-APACHE b/crates/bevy_cursor_icon/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_cursor_icon/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_cursor_icon/LICENSE-MIT b/crates/bevy_cursor_icon/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_cursor_icon/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_cursor_icon/README.md b/crates/bevy_cursor_icon/README.md new file mode 100644 index 0000000000000..64acda5527fb5 --- /dev/null +++ b/crates/bevy_cursor_icon/README.md @@ -0,0 +1,7 @@ +# Bevy Color + +[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) +[![Crates.io](https://img.shields.io/crates/v/bevy_cursor_icon.svg)](https://crates.io/crates/bevy_cursor_icon) +[![Downloads](https://img.shields.io/crates/d/bevy_cursor_icon.svg)](https://crates.io/crates/bevy_cursor_icon) +[![Docs](https://docs.rs/bevy_cursor_icon/badge.svg)](https://docs.rs/bevy_cursor_icon/latest/bevy_cursor_icon/) +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&cursor=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) diff --git a/crates/bevy_cursor_icon/src/cursor.rs b/crates/bevy_cursor_icon/src/cursor.rs new file mode 100644 index 0000000000000..01967cb41ff13 --- /dev/null +++ b/crates/bevy_cursor_icon/src/cursor.rs @@ -0,0 +1,45 @@ +use bevy_ecs::component::Component; +use bevy_ecs::reflect::ReflectComponent; +use bevy_reflect::prelude::ReflectDefault; +use bevy_reflect::Reflect; + +#[cfg(feature = "custom_cursor")] +use crate::custom_cursor::CustomCursor; + +use crate::SystemCursorIcon; + +/// Insert into a window entity to set the cursor for that window. +#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)] +#[reflect(Component, Debug, Default, PartialEq, Clone)] +pub enum CursorIcon { + #[cfg(feature = "custom_cursor")] + /// Custom cursor image. + Custom(CustomCursor), + /// System provided cursor icon. + System(SystemCursorIcon), +} + +impl Default for CursorIcon { + fn default() -> Self { + CursorIcon::System(Default::default()) + } +} + +impl From for CursorIcon { + fn from(icon: SystemCursorIcon) -> Self { + CursorIcon::System(icon) + } +} + +#[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, Clone)] +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. + pub url: String, + /// X and Y coordinates of the hotspot in pixels. The hotspot must be within + /// the image bounds. + pub hotspot: (u16, u16), +} diff --git a/crates/bevy_cursor_icon/src/custom_cursor.rs b/crates/bevy_cursor_icon/src/custom_cursor.rs new file mode 100644 index 0000000000000..cbe51ab969acb --- /dev/null +++ b/crates/bevy_cursor_icon/src/custom_cursor.rs @@ -0,0 +1,59 @@ +use crate::CursorIcon; +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +use crate::CustomCursorUrl; +use bevy_asset::Handle; +use bevy_image::{Image, TextureAtlas}; +use bevy_math::URect; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; + +/// Custom cursor image data. +#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)] +#[reflect(Clone, PartialEq, 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 for CursorIcon { + fn from(cursor: CustomCursor) -> Self { + CursorIcon::Custom(cursor) + } +} + +/// A custom cursor created from an image. +#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)] +#[reflect(Debug, Default, Hash, PartialEq, Clone)] +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, + /// 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 + /// to using a [`TextureAtlas`]. + /// + /// When used with a [`TextureAtlas`], the rect is offset by the atlas's + /// minimal (top-left) corner position. + 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), +} diff --git a/crates/bevy_cursor_icon/src/lib.rs b/crates/bevy_cursor_icon/src/lib.rs new file mode 100644 index 0000000000000..912f9ce3a6081 --- /dev/null +++ b/crates/bevy_cursor_icon/src/lib.rs @@ -0,0 +1,28 @@ +mod cursor; +mod system_cursor; + +#[cfg(feature = "custom_cursor")] +mod custom_cursor; + +use bevy_app::{App, Plugin}; +pub use cursor::*; +pub use system_cursor::SystemCursorIcon; + +#[cfg(feature = "custom_cursor")] +pub use custom_cursor::*; + +pub mod prelude { + pub use crate::cursor::*; + #[cfg(feature = "custom_cursor")] + pub use crate::custom_cursor::*; + pub use crate::system_cursor::*; +} + +#[derive(Default)] +pub struct CursorIconPlugin; + +impl Plugin for CursorIconPlugin { + fn build(&self, app: &mut App) { + app.register_type::(); + } +} diff --git a/crates/bevy_window/src/system_cursor.rs b/crates/bevy_cursor_icon/src/system_cursor.rs similarity index 94% rename from crates/bevy_window/src/system_cursor.rs rename to crates/bevy_cursor_icon/src/system_cursor.rs index 8390e561b091d..269e5cdfabd5e 100644 --- a/crates/bevy_window/src/system_cursor.rs +++ b/crates/bevy_cursor_icon/src/system_cursor.rs @@ -68,12 +68,8 @@ // --------- END OF W3C SHORT NOTICE // -------------------------------------------------------------- -#[cfg(feature = "bevy_reflect")] use bevy_reflect::{prelude::ReflectDefault, Reflect}; -#[cfg(feature = "serialize")] -use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; - /// The icon to display for a window. /// /// Examples of all of these cursors can be found [here](https://www.w3schools.com/cssref/playit.php?filename=playcss_cursor&preval=crosshair). @@ -83,17 +79,8 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// 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)] -#[cfg_attr( - feature = "bevy_reflect", - derive(Reflect), - reflect(Debug, PartialEq, Hash, Default, Clone) -)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr( - all(feature = "serialize", feature = "bevy_reflect"), - reflect(Serialize, Deserialize) -)] +#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)] +#[reflect(Debug, PartialEq, Hash, Default, Clone)] pub enum SystemCursorIcon { /// The platform-dependent default cursor. Often rendered as arrow. #[default] diff --git a/crates/bevy_feathers/Cargo.toml b/crates/bevy_feathers/Cargo.toml index 746db79b85d6d..9b23cc9f04dde 100644 --- a/crates/bevy_feathers/Cargo.toml +++ b/crates/bevy_feathers/Cargo.toml @@ -29,14 +29,14 @@ bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [ ] } bevy_ui_render = { path = "../bevy_ui_render", version = "0.17.0-dev" } bevy_window = { path = "../bevy_window", version = "0.17.0-dev" } -bevy_winit = { path = "../bevy_winit", version = "0.17.0-dev" } +bevy_cursor_icon = { path = "../bevy_cursor_icon", version = "0.17.0-dev" } # other accesskit = "0.21" [features] default = [] -custom_cursor = ["bevy_winit/custom_cursor"] +custom_cursor = ["bevy_cursor_icon/custom_cursor"] [lints] workspace = true diff --git a/crates/bevy_feathers/src/controls/button.rs b/crates/bevy_feathers/src/controls/button.rs index 3566639993764..74c64eee36135 100644 --- a/crates/bevy_feathers/src/controls/button.rs +++ b/crates/bevy_feathers/src/controls/button.rs @@ -73,7 +73,7 @@ pub fn button + Send + Sync + 'static, B: Bundle>( }, props.variant, Hovered::default(), - EntityCursor::System(bevy_window::SystemCursorIcon::Pointer), + EntityCursor::System(bevy_cursor_icon::SystemCursorIcon::Pointer), TabIndex(0), props.corners.to_border_radius(4.0), ThemeBackgroundColor(tokens::BUTTON_BG), diff --git a/crates/bevy_feathers/src/controls/checkbox.rs b/crates/bevy_feathers/src/controls/checkbox.rs index 549a3737e5d5f..c78f7062826f7 100644 --- a/crates/bevy_feathers/src/controls/checkbox.rs +++ b/crates/bevy_feathers/src/controls/checkbox.rs @@ -74,7 +74,7 @@ pub fn checkbox + Send + Sync + 'static, B: Bundle>( }, CheckboxFrame, Hovered::default(), - EntityCursor::System(bevy_window::SystemCursorIcon::Pointer), + EntityCursor::System(bevy_cursor_icon::SystemCursorIcon::Pointer), TabIndex(0), ThemeFontColor(tokens::CHECKBOX_TEXT), InheritableFont { diff --git a/crates/bevy_feathers/src/controls/radio.rs b/crates/bevy_feathers/src/controls/radio.rs index aa5afa5efb426..d4c6303a10adc 100644 --- a/crates/bevy_feathers/src/controls/radio.rs +++ b/crates/bevy_feathers/src/controls/radio.rs @@ -58,7 +58,7 @@ pub fn radio + Send + Sync + 'static, B: Bundle>( }, CoreRadio, Hovered::default(), - EntityCursor::System(bevy_window::SystemCursorIcon::Pointer), + EntityCursor::System(bevy_cursor_icon::SystemCursorIcon::Pointer), TabIndex(0), ThemeFontColor(tokens::RADIO_TEXT), InheritableFont { diff --git a/crates/bevy_feathers/src/controls/slider.rs b/crates/bevy_feathers/src/controls/slider.rs index 49a7a4c0cc633..81809261d976e 100644 --- a/crates/bevy_feathers/src/controls/slider.rs +++ b/crates/bevy_feathers/src/controls/slider.rs @@ -87,7 +87,7 @@ pub fn slider(props: SliderProps, overrides: B) -> impl Bundle { SliderStyle, SliderValue(props.value), SliderRange::new(props.min, props.max), - EntityCursor::System(bevy_window::SystemCursorIcon::EwResize), + EntityCursor::System(bevy_cursor_icon::SystemCursorIcon::EwResize), TabIndex(0), RoundedCorners::All.to_border_radius(6.0), // Use a gradient to draw the moving bar diff --git a/crates/bevy_feathers/src/controls/toggle_switch.rs b/crates/bevy_feathers/src/controls/toggle_switch.rs index 58b6b474244db..84b4c48cf07ca 100644 --- a/crates/bevy_feathers/src/controls/toggle_switch.rs +++ b/crates/bevy_feathers/src/controls/toggle_switch.rs @@ -63,7 +63,7 @@ pub fn toggle_switch(props: ToggleSwitchProps, overrides: B) -> impl ThemeBorderColor(tokens::SWITCH_BORDER), AccessibilityNode(accesskit::Node::new(Role::Switch)), Hovered::default(), - EntityCursor::System(bevy_window::SystemCursorIcon::Pointer), + EntityCursor::System(bevy_cursor_icon::SystemCursorIcon::Pointer), TabIndex(0), overrides, children![( diff --git a/crates/bevy_feathers/src/cursor.rs b/crates/bevy_feathers/src/cursor.rs index 837b4c114b46f..b48de5b75a84c 100644 --- a/crates/bevy_feathers/src/cursor.rs +++ b/crates/bevy_feathers/src/cursor.rs @@ -1,5 +1,8 @@ //! Provides a way to automatically set the mouse cursor based on hovered entity. use bevy_app::{App, Plugin, PreUpdate}; +#[cfg(feature = "custom_cursor")] +use bevy_cursor_icon::CustomCursor; +use bevy_cursor_icon::{CursorIcon, SystemCursorIcon}; use bevy_ecs::{ component::Component, entity::Entity, @@ -12,10 +15,7 @@ use bevy_ecs::{ }; use bevy_picking::{hover::HoverMap, pointer::PointerId, PickingSystems}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_window::{SystemCursorIcon, Window}; -use bevy_winit::cursor::CursorIcon; -#[cfg(feature = "custom_cursor")] -use bevy_winit::cursor::CustomCursor; +use bevy_window::Window; /// A resource that specifies the cursor icon to be used when the mouse is not hovering over /// any other entity. This is used to set the default cursor icon for the window. diff --git a/crates/bevy_feathers/src/lib.rs b/crates/bevy_feathers/src/lib.rs index d6ec11e7b6707..0742c3308e28a 100644 --- a/crates/bevy_feathers/src/lib.rs +++ b/crates/bevy_feathers/src/lib.rs @@ -69,7 +69,7 @@ impl Plugin for FeathersPlugin { )); app.insert_resource(DefaultCursor(EntityCursor::System( - bevy_window::SystemCursorIcon::Default, + bevy_cursor_icon::SystemCursorIcon::Default, ))); app.add_systems(PostUpdate, theme::update_theme) diff --git a/crates/bevy_image/Cargo.toml b/crates/bevy_image/Cargo.toml index 4069136e2b907..13ea6b32177dc 100644 --- a/crates/bevy_image/Cargo.toml +++ b/crates/bevy_image/Cargo.toml @@ -11,6 +11,8 @@ keywords = ["bevy"] [features] default = ["bevy_reflect"] +custom_cursor = [] + # bevy_reflect can't optional as it's needed for TypePath # this feature only control reflection in bevy_image bevy_reflect = ["bevy_math/bevy_reflect"] diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 6caaa09198f53..43b6b7e71409d 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -205,6 +205,7 @@ bevy_ui = ["dep:bevy_ui", "bevy_image"] bevy_ui_render = ["dep:bevy_ui_render"] bevy_shader = ["dep:bevy_shader"] bevy_image = ["dep:bevy_image"] +bevy_cursor_icon = ["dep:bevy_cursor_icon"] bevy_mesh = ["dep:bevy_mesh", "bevy_image"] bevy_camera = ["dep:bevy_camera", "bevy_mesh"] @@ -465,6 +466,7 @@ bevy_window = { path = "../bevy_window", optional = true, version = "0.17.0-dev" "bevy_reflect", ] } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.17.0-dev", default-features = false } +bevy_cursor_icon = { path = "../bevy_cursor_icon", optional = true, version = "0.17.0-dev" } [lints] workspace = true diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index cdb59921dcc74..f02e5eb50075d 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -3,6 +3,7 @@ use bevy_app::{plugin_group, Plugin}; plugin_group! { /// This plugin group will add all the default plugins for a *Bevy* application: pub struct DefaultPlugins { + bevy_app:::PanicHandlerPlugin, #[cfg(feature = "bevy_log")] bevy_log:::LogPlugin, @@ -18,6 +19,8 @@ plugin_group! { bevy_window:::WindowPlugin, #[cfg(feature = "bevy_window")] bevy_a11y:::AccessibilityPlugin, + #[cfg(feature = "bevy_cursor_icon")] + bevy_cursor_icon:::CursorIconPlugin, #[cfg(feature = "std")] #[custom(cfg(any(all(unix, not(target_os = "horizon")), windows)))] bevy_app:::TerminalCtrlCHandlerPlugin, diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 61879abdc9086..14cb5c5f112e1 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -33,6 +33,8 @@ pub use bevy_color as color; pub use bevy_core_pipeline as core_pipeline; #[cfg(feature = "bevy_core_widgets")] pub use bevy_core_widgets as core_widgets; +#[cfg(feature = "bevy_cursor_icon")] +pub use bevy_cursor_icon as cursor_icon; #[cfg(feature = "bevy_dev_tools")] pub use bevy_dev_tools as dev_tools; pub use bevy_diagnostic as diagnostic; diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index c8ba27ea82c1e..3b07418823c5d 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -86,3 +86,7 @@ pub use crate::gltf::prelude::*; #[doc(hidden)] #[cfg(feature = "bevy_picking")] pub use crate::picking::prelude::*; + +#[doc(hidden)] +#[cfg(feature = "bevy_cursor_icon")] +pub use crate::cursor_icon::prelude::*; diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 22e657cf038c0..4e6bda8ca5c47 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -25,7 +25,6 @@ mod event; mod monitor; mod raw_handle; mod system; -mod system_cursor; mod window; pub use crate::raw_handle::*; @@ -36,7 +35,6 @@ pub use android_activity; pub use event::*; pub use monitor::*; pub use system::*; -pub use system_cursor::*; pub use window::*; /// The windowing prelude. diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 69e9ffb3c7cc7..82e2a5347cfcd 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -23,12 +23,19 @@ serialize = [ android-native-activity = ["winit/android-native-activity"] android-game-activity = ["winit/android-game-activity"] -custom_cursor = ["bevy_image", "bevy_asset", "bytemuck", "wgpu-types"] +custom_cursor = [ + "bevy_image", + "bevy_asset", + "bytemuck", + "wgpu-types", + "bevy_cursor_icon/custom_cursor", +] [dependencies] # bevy bevy_a11y = { path = "../bevy_a11y", version = "0.17.0-dev" } bevy_app = { path = "../bevy_app", version = "0.17.0-dev" } +bevy_cursor_icon = { path = "../bevy_cursor_icon", version = "0.17.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_input = { path = "../bevy_input", version = "0.17.0-dev" } diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index 3de27162a4520..6e22891b41528 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -1,3 +1,4 @@ +use bevy_cursor_icon::SystemCursorIcon; use bevy_ecs::entity::Entity; use bevy_input::{ keyboard::{KeyCode, KeyboardInput, NativeKeyCode}, @@ -6,7 +7,6 @@ use bevy_input::{ ButtonState, }; use bevy_math::{CompassOctant, Vec2}; -use bevy_window::SystemCursorIcon; use bevy_window::{EnabledButtons, WindowLevel, WindowTheme}; use winit::keyboard::{Key, NamedKey, NativeKey}; diff --git a/crates/bevy_winit/src/cursor.rs b/crates/bevy_winit/src/cursor.rs index c5c5e489a665a..60881157f0278 100644 --- a/crates/bevy_winit/src/cursor.rs +++ b/crates/bevy_winit/src/cursor.rs @@ -16,36 +16,34 @@ use crate::{ use bevy_app::{App, Last, Plugin}; #[cfg(feature = "custom_cursor")] use bevy_asset::Assets; +use bevy_cursor_icon::{CursorIcon, SystemCursorIcon}; #[cfg(feature = "custom_cursor")] use bevy_ecs::system::Res; use bevy_ecs::{ change_detection::DetectChanges, - component::Component, entity::Entity, lifecycle::Remove, observer::On, query::With, - reflect::ReflectComponent, system::{Commands, Local, Query}, world::Ref, }; #[cfg(feature = "custom_cursor")] use bevy_image::{Image, TextureAtlasLayout}; use bevy_platform::collections::HashSet; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_window::{SystemCursorIcon, Window}; +use bevy_window::Window; #[cfg(feature = "custom_cursor")] use tracing::warn; #[cfg(feature = "custom_cursor")] -pub use crate::custom_cursor::{CustomCursor, CustomCursorImage}; +pub use bevy_cursor_icon::{CustomCursor, CustomCursorImage}; #[cfg(all( feature = "custom_cursor", target_family = "wasm", target_os = "unknown" ))] -pub use crate::custom_cursor::CustomCursorUrl; +pub use bevy_cursor_icon::CustomCursorUrl; pub(crate) struct CursorPlugin; @@ -54,36 +52,12 @@ impl Plugin for CursorPlugin { #[cfg(feature = "custom_cursor")] app.add_plugins(CustomCursorPlugin); - app.register_type::() - .add_systems(Last, update_cursors); + app.add_systems(Last, update_cursors); app.add_observer(on_remove_cursor_icon); } } -/// Insert into a window entity to set the cursor for that window. -#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)] -#[reflect(Component, Debug, Default, PartialEq, Clone)] -pub enum CursorIcon { - #[cfg(feature = "custom_cursor")] - /// Custom cursor image. - Custom(CustomCursor), - /// System provided cursor icon. - System(SystemCursorIcon), -} - -impl Default for CursorIcon { - fn default() -> Self { - CursorIcon::System(Default::default()) - } -} - -impl From for CursorIcon { - fn from(icon: SystemCursorIcon) -> Self { - CursorIcon::System(icon) - } -} - fn update_cursors( mut commands: Commands, windows: Query<(Entity, Ref), With>, diff --git a/crates/bevy_winit/src/custom_cursor.rs b/crates/bevy_winit/src/custom_cursor.rs index dd8236e30e43d..cb56591922fdb 100644 --- a/crates/bevy_winit/src/custom_cursor.rs +++ b/crates/bevy_winit/src/custom_cursor.rs @@ -1,76 +1,10 @@ use bevy_app::{App, Plugin}; -use bevy_asset::{Assets, Handle}; +use bevy_asset::Assets; use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasPlugin}; use bevy_math::{ops, Rect, URect, UVec2, Vec2}; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use wgpu_types::TextureFormat; -use crate::{cursor::CursorIcon, state::CustomCursorCache}; - -/// A custom cursor created from an image. -#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)] -#[reflect(Debug, Default, Hash, PartialEq, Clone)] -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, - /// 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 - /// to using a [`TextureAtlas`]. - /// - /// When used with a [`TextureAtlas`], the rect is offset by the atlas's - /// minimal (top-left) corner position. - 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), -} - -#[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, Clone)] -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. - 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)] -#[reflect(Clone, PartialEq, 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 for CursorIcon { - fn from(cursor: CustomCursor) -> Self { - CursorIcon::Custom(cursor) - } -} +use crate::state::CustomCursorCache; /// Adds support for custom cursors. pub(crate) struct CustomCursorPlugin; diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 6e409cf998bb7..fd9de221241c6 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -22,6 +22,7 @@ The default feature set enables most of the expected features of a game engine, |bevy_color|Provides shared color types and operations| |bevy_core_pipeline|Provides cameras and other basic render pipeline features| |bevy_core_widgets|Headless widget collection for Bevy UI.| +|bevy_cursor_icon|Provides cursor| |bevy_gilrs|Adds gamepad support| |bevy_gizmos|Adds support for rendering gizmos| |bevy_gltf|[glTF](https://www.khronos.org/gltf/) support| diff --git a/examples/3d/clustered_decals.rs b/examples/3d/clustered_decals.rs index a0593af0e08cd..7e554bedebfd3 100644 --- a/examples/3d/clustered_decals.rs +++ b/examples/3d/clustered_decals.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Formatter}; use bevy::{ color::palettes::css::{LIME, ORANGE_RED, SILVER}, + cursor_icon::{CursorIcon, SystemCursorIcon}, input::mouse::AccumulatedMouseMotion, pbr::{ decal::{self, clustered::ClusteredDecal}, @@ -15,8 +16,6 @@ use bevy::{ render_resource::{AsBindGroup, ShaderRef}, renderer::{RenderAdapter, RenderDevice}, }, - window::SystemCursorIcon, - winit::cursor::CursorIcon, }; use ops::{acos, cos, sin}; use widgets::{ diff --git a/examples/3d/light_textures.rs b/examples/3d/light_textures.rs index 743f3b152e69a..a2edc0558fb37 100644 --- a/examples/3d/light_textures.rs +++ b/examples/3d/light_textures.rs @@ -5,12 +5,11 @@ use std::fmt::{self, Formatter}; use bevy::{ color::palettes::css::{SILVER, YELLOW}, + cursor_icon::{CursorIcon, SystemCursorIcon}, input::mouse::AccumulatedMouseMotion, pbr::{decal, DirectionalLightTexture, NotShadowCaster, PointLightTexture, SpotLightTexture}, prelude::*, render::renderer::{RenderAdapter, RenderDevice}, - window::SystemCursorIcon, - winit::cursor::CursorIcon, }; use light_consts::lux::{AMBIENT_DAYLIGHT, CLEAR_SUNRISE}; use ops::{acos, cos, sin}; diff --git a/examples/window/custom_cursor_image.rs b/examples/window/custom_cursor_image.rs index ea715a54d6d73..009805313682a 100644 --- a/examples/window/custom_cursor_image.rs +++ b/examples/window/custom_cursor_image.rs @@ -4,8 +4,8 @@ use std::time::Duration; use bevy::{ + cursor_icon::{CursorIcon, CustomCursor, CustomCursorImage}, prelude::*, - winit::cursor::{CursorIcon, CustomCursor, CustomCursorImage}, }; fn main() { diff --git a/examples/window/screenshot.rs b/examples/window/screenshot.rs index 84c15a6837349..29fd462f19889 100644 --- a/examples/window/screenshot.rs +++ b/examples/window/screenshot.rs @@ -1,10 +1,9 @@ //! An example showing how to save screenshots to disk use bevy::{ + cursor_icon::CursorIcon, prelude::*, render::view::screenshot::{save_to_disk, Capturing, Screenshot}, - window::SystemCursorIcon, - winit::cursor::CursorIcon, }; fn main() { diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index 7be899f6e315b..6dcf9003b06e1 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -1,15 +1,14 @@ //! Illustrates how to change window settings and shows how to affect //! the mouse pointer in various ways. +use bevy::cursor_icon::SystemCursorIcon; #[cfg(feature = "custom_cursor")] use bevy::winit::cursor::{CustomCursor, CustomCursorImage}; use bevy::{ + cursor_icon::CursorIcon, diagnostic::{FrameCount, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, - window::{ - CursorGrabMode, CursorOptions, PresentMode, SystemCursorIcon, WindowLevel, WindowTheme, - }, - winit::cursor::CursorIcon, + window::{CursorGrabMode, CursorOptions, PresentMode, WindowLevel, WindowTheme}, }; fn main() { diff --git a/release-content/migration-guides/cursoricon_moved_to_bevy_cursor_icon.md b/release-content/migration-guides/cursoricon_moved_to_bevy_cursor_icon.md new file mode 100644 index 0000000000000..aab83474a8de0 --- /dev/null +++ b/release-content/migration-guides/cursoricon_moved_to_bevy_cursor_icon.md @@ -0,0 +1,8 @@ +--- +title: The `CursorIcon` API has been extracted from `bevy_winit and moved into a new crate `bevy_cursor_icon` +pull_requests: [20381] +--- + +The `CursorIcon`, `SystemCursorIcon`, `CustomCursor`, and `CustomCursorImage` types have all been moved into a new crate `bevy_cursor_icon`. + +The is to make cursor customization independent of `bevy_winit`, so that it can be used with any windowing system. diff --git a/release-content/release-notes/bevy_cursor_icon_crate.md b/release-content/release-notes/bevy_cursor_icon_crate.md new file mode 100644 index 0000000000000..6edd253519c76 --- /dev/null +++ b/release-content/release-notes/bevy_cursor_icon_crate.md @@ -0,0 +1,11 @@ +--- +title: `bevy_cursor_icon` crate +authors: ["@Ickshonpe"] +pull_requests: [20381] +--- + +In past releases the `CursorIcon` API was located in the `bevy_winit` crate. This meant that mouse cursor icon customization was tied to the `bevy_winit` backend. + +In order that cursor icon customization is independent of any particular windowing system a new crate has been added, `bevy_cursor_icon`. + +The `CursorIcon`, `SystemCursorIcon`, `CustomCursor`, and `CustomCursorImage` types have all moved into the `bevy_cursor_icon` crate. The `CursorIcon` API is otherwise changed.