diff --git a/Cargo.toml b/Cargo.toml index 320ab09977..563cc9bb20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,6 +132,7 @@ default = [ "webgl2", "sysinfo_plugin", "android-game-activity", + "custom_cursor", ] # Provides an implementation for picking sprites @@ -417,6 +418,9 @@ track_change_detection = ["bevy_internal/track_change_detection"] # Enable function reflection reflect_functions = ["bevy_internal/reflect_functions"] +# Enable winit custom cursor support +custom_cursor = ["bevy_internal/custom_cursor"] + [dependencies] bevy_internal = { path = "crates/bevy_internal", version = "0.15.0-dev", default-features = false } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 5128a772bb..b8de4caf57 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -223,6 +223,9 @@ reflect_functions = [ "bevy_ecs/reflect_functions", ] +# Enable winit custom cursor support +custom_cursor = ["bevy_winit/custom_cursor"] + [dependencies] # bevy bevy_a11y = { path = "../bevy_a11y", version = "0.15.0-dev" } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index a81ba639f4..46018bde2b 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -66,7 +66,6 @@ bevy_render_macros = { path = "macros", version = "0.15.0-dev" } bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.15.0-dev" } bevy_window = { path = "../bevy_window", version = "0.15.0-dev" } -bevy_winit = { path = "../bevy_winit", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.15.0-dev" } bevy_image = { path = "../bevy_image", version = "0.15.0-dev" } diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 1def6df2ae..5473e9b64c 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -15,12 +15,10 @@ use core::{ num::NonZero, ops::{Deref, DerefMut}, }; -use cursor::CursorPlugin; use wgpu::{ SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor, }; -pub mod cursor; pub mod screenshot; use screenshot::{ScreenshotPlugin, ScreenshotToScreenPipeline}; @@ -29,7 +27,7 @@ pub struct WindowRenderPlugin; impl Plugin for WindowRenderPlugin { fn build(&self, app: &mut App) { - app.add_plugins((ScreenshotPlugin, CursorPlugin)); + app.add_plugins(ScreenshotPlugin); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 65b73eed91..427dc84838 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -18,6 +18,8 @@ serialize = ["serde", "bevy_input/serialize", "bevy_window/serialize"] android-native-activity = ["winit/android-native-activity"] android-game-activity = ["winit/android-game-activity"] +custom_cursor = ["bevy_image", "bevy_asset", "bytemuck", "wgpu-types"] + [dependencies] # bevy @@ -34,6 +36,10 @@ bevy_window = { path = "../bevy_window", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.15.0-dev" } +# bevy optional +bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev", optional = true } +bevy_image = { path = "../bevy_image", version = "0.15.0-dev", optional = true } + # other # feature rwh_06 refers to window_raw_handle@v0.6 winit = { version = "0.30", default-features = false, features = ["rwh_06"] } @@ -44,6 +50,8 @@ approx = { version = "0.5", default-features = false } cfg-if = "1.0" raw-window-handle = "0.6" serde = { version = "1.0", features = ["derive"], optional = true } +bytemuck = { version = "1.5", optional = true } +wgpu-types = { version = "22", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = { version = "0.2" } diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index 8cb07f9600..691113bbbe 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -6,7 +6,9 @@ use bevy_input::{ ButtonState, }; use bevy_math::Vec2; -use bevy_window::{EnabledButtons, SystemCursorIcon, WindowLevel, WindowTheme}; +#[cfg(feature = "custom_cursor")] +use bevy_window::SystemCursorIcon; +use bevy_window::{EnabledButtons, WindowLevel, WindowTheme}; use winit::keyboard::{Key, NamedKey, NativeKey}; pub fn convert_keyboard_input( @@ -628,6 +630,7 @@ pub fn convert_native_key(native_key: &NativeKey) -> bevy_input::keyboard::Nativ } } +#[cfg(feature = "custom_cursor")] /// Converts a [`SystemCursorIcon`] to a [`winit::window::CursorIcon`]. pub fn convert_system_cursor_icon(cursor_icon: SystemCursorIcon) -> winit::window::CursorIcon { match cursor_icon { diff --git a/crates/bevy_render/src/view/window/cursor.rs b/crates/bevy_winit/src/cursor.rs similarity index 86% rename from crates/bevy_render/src/view/window/cursor.rs rename to crates/bevy_winit/src/cursor.rs index 8f40ab5016..297598630b 100644 --- a/crates/bevy_render/src/view/window/cursor.rs +++ b/crates/bevy_winit/src/cursor.rs @@ -1,5 +1,12 @@ +//! Components to customize winit cursor + +use crate::{ + converters::convert_system_cursor_icon, + state::{CursorSource, CustomCursorCache, CustomCursorCacheKey, PendingCursor}, + WinitCustomCursor, +}; use bevy_app::{App, Last, Plugin}; -use bevy_asset::{AssetId, Assets, Handle}; +use bevy_asset::{Assets, Handle}; use bevy_ecs::{ change_detection::DetectChanges, component::Component, @@ -10,18 +17,13 @@ use bevy_ecs::{ system::{Commands, Local, Query, Res}, world::{OnRemove, Ref}, }; +use bevy_image::Image; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_utils::{tracing::warn, HashSet}; use bevy_window::{SystemCursorIcon, Window}; -use bevy_winit::{ - convert_system_cursor_icon, CursorSource, CustomCursorCache, CustomCursorCacheKey, - PendingCursor, -}; -use wgpu::TextureFormat; +use wgpu_types::TextureFormat; -use crate::prelude::Image; - -pub struct CursorPlugin; +pub(crate) struct CursorPlugin; impl Plugin for CursorPlugin { fn build(&self, app: &mut App) { @@ -85,7 +87,7 @@ pub enum CustomCursor { }, } -pub fn update_cursors( +fn update_cursors( mut commands: Commands, windows: Query<(Entity, Ref), With>, cursor_cache: Res, @@ -99,12 +101,7 @@ pub fn update_cursors( let cursor_source = match cursor.as_ref() { CursorIcon::Custom(CustomCursor::Image { handle, hotspot }) => { - let cache_key = match handle.id() { - AssetId::Index { index, .. } => { - CustomCursorCacheKey::AssetIndex(index.to_bits()) - } - AssetId::Uuid { uuid } => CustomCursorCacheKey::AssetUuid(uuid.as_u128()), - }; + let cache_key = CustomCursorCacheKey::Asset(handle.id()); if cursor_cache.0.contains_key(&cache_key) { CursorSource::CustomCached(cache_key) @@ -123,7 +120,7 @@ pub fn update_cursors( let width = image.texture_descriptor.size.width; let height = image.texture_descriptor.size.height; - let source = match bevy_winit::WinitCustomCursor::from_rgba( + let source = match WinitCustomCursor::from_rgba( rgba, width as u16, height as u16, @@ -147,9 +144,8 @@ pub fn update_cursors( if cursor_cache.0.contains_key(&cache_key) { CursorSource::CustomCached(cache_key) } else { - use bevy_winit::CustomCursorExtWebSys; - let source = - bevy_winit::WinitCustomCursor::from_url(url.clone(), hotspot.0, hotspot.1); + use crate::CustomCursorExtWebSys; + let source = WinitCustomCursor::from_url(url.clone(), hotspot.0, hotspot.1); CursorSource::Custom((cache_key, source)) } } @@ -165,7 +161,7 @@ pub fn update_cursors( } /// Resets the cursor to the default icon when `CursorIcon` is removed. -pub fn on_remove_cursor_icon(trigger: Trigger, mut commands: Commands) { +fn on_remove_cursor_icon(trigger: Trigger, mut commands: Commands) { // Use `try_insert` to avoid panic if the window is being destroyed. commands .entity(trigger.entity()) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 75d080a21a..ac56798bc8 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -23,8 +23,6 @@ use bevy_a11y::AccessibilityRequested; use bevy_app::{App, Last, Plugin}; use bevy_ecs::prelude::*; use bevy_window::{exit_on_all_closed, Window, WindowCreated}; -pub use converters::convert_system_cursor_icon; -pub use state::{CursorSource, CustomCursorCache, CustomCursorCacheKey, PendingCursor}; use system::{changed_windows, check_keyboard_focus_lost, despawn_windows}; pub use system::{create_monitors, create_windows}; #[cfg(all(target_family = "wasm", target_os = "unknown"))] @@ -44,6 +42,8 @@ use crate::{ pub mod accessibility; mod converters; +#[cfg(feature = "custom_cursor")] +pub mod cursor; mod state; mod system; mod winit_config; @@ -131,6 +131,8 @@ impl Plugin for WinitPlugin { ); app.add_plugins(AccessKitPlugin); + #[cfg(feature = "custom_cursor")] + app.add_plugins(cursor::CursorPlugin); let event_loop = event_loop_builder .build() diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 56006e56a3..b9c9bdfb40 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -1,5 +1,7 @@ use approx::relative_eq; use bevy_app::{App, AppExit, PluginsState}; +#[cfg(feature = "custom_cursor")] +use bevy_asset::AssetId; use bevy_ecs::{ change_detection::{DetectChanges, NonSendMut, Res}, entity::Entity, @@ -8,6 +10,8 @@ use bevy_ecs::{ system::SystemState, world::FromWorld, }; +#[cfg(feature = "custom_cursor")] +use bevy_image::Image; use bevy_input::{ gestures::*, mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, @@ -16,7 +20,9 @@ use bevy_log::{error, trace, warn}; use bevy_math::{ivec2, DVec2, Vec2}; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::tick_global_task_pools_on_main_thread; -use bevy_utils::{HashMap, Instant}; +#[cfg(feature = "custom_cursor")] +use bevy_utils::HashMap; +use bevy_utils::Instant; use core::marker::PhantomData; use winit::{ application::ApplicationHandler, @@ -88,6 +94,7 @@ struct WinitAppRunnerState { impl WinitAppRunnerState { fn new(mut app: App) -> Self { + #[cfg(feature = "custom_cursor")] app.add_event::().init_resource::(); let event_writer_system_state: SystemState<( @@ -134,23 +141,25 @@ impl WinitAppRunnerState { } } +#[cfg(feature = "custom_cursor")] /// Identifiers for custom cursors used in caching. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum CustomCursorCacheKey { - /// u64 is used instead of `AssetId`, because `bevy_asset` can't be imported here. - AssetIndex(u64), - /// u128 is used instead of `AssetId`, because `bevy_asset` can't be imported here. - AssetUuid(u128), - /// A URL to a cursor. + /// An `AssetId` to a cursor. + Asset(AssetId), + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + /// An URL to a cursor. Url(String), } +#[cfg(feature = "custom_cursor")] /// Caches custom cursors. On many platforms, creating custom cursors is expensive, especially on /// the web. #[derive(Debug, Clone, Default, Resource)] pub struct CustomCursorCache(pub HashMap); -/// A source for a cursor. Is created in `bevy_render` and consumed by the winit event loop. +#[cfg(feature = "custom_cursor")] +/// A source for a cursor. Consumed by the winit event loop. #[derive(Debug)] pub enum CursorSource { /// A custom cursor was identified to be cached, no reason to recreate it. @@ -161,6 +170,7 @@ pub enum CursorSource { System(winit::window::CursorIcon), } +#[cfg(feature = "custom_cursor")] /// Component that indicates what cursor should be used for a window. Inserted /// automatically after changing `CursorIcon` and consumed by the winit event /// loop. @@ -547,6 +557,7 @@ impl ApplicationHandler for WinitAppRunnerState { // This is a temporary solution, full solution is mentioned here: https://github.com/bevyengine/bevy/issues/1343#issuecomment-770091684 if !self.ran_update_since_last_redraw || all_invisible { self.run_app_update(); + #[cfg(feature = "custom_cursor")] self.update_cursors(event_loop); self.ran_update_since_last_redraw = true; } else { @@ -775,6 +786,7 @@ impl WinitAppRunnerState { .send_batch(buffered_events); } + #[cfg(feature = "custom_cursor")] fn update_cursors(&mut self, event_loop: &ActiveEventLoop) { let mut windows_state: SystemState<( NonSendMut, diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 4b5f6f0426..273ee171ff 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -34,6 +34,7 @@ The default feature set enables most of the expected features of a game engine, |bevy_ui|A custom ECS-driven UI framework| |bevy_ui_picking_backend|Provides an implementation for picking ui| |bevy_winit|winit window and input backend| +|custom_cursor|Enable winit custom cursor support| |default_font|Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase| |hdr|HDR image format support| |ktx2|KTX2 compressed texture support| diff --git a/examples/window/screenshot.rs b/examples/window/screenshot.rs index 750ab0ced7..27d4d73527 100644 --- a/examples/window/screenshot.rs +++ b/examples/window/screenshot.rs @@ -2,11 +2,9 @@ use bevy::{ prelude::*, - render::view::{ - cursor::CursorIcon, - screenshot::{save_to_disk, Capturing, Screenshot}, - }, + 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 4492dd7705..2828622fa8 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -5,8 +5,8 @@ use bevy::{ core::FrameCount, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, - render::view::cursor::{CursorIcon, CustomCursor}, window::{CursorGrabMode, PresentMode, SystemCursorIcon, WindowLevel, WindowTheme}, + winit::cursor::{CursorIcon, CustomCursor}, }; fn main() {