Inverse bevy_render bevy_winit dependency and move cursor to bevy_winit (#15649)

# Objective

- `bevy_render` should not depend on `bevy_winit`
- Fixes #15565

## Solution

- `bevy_render` no longer depends on `bevy_winit`
- The following is behind the `custom_cursor` feature
- Move custom cursor code from `bevy_render` to `bevy_winit` behind the
`custom_cursor` feature
- `bevy_winit` now depends on `bevy_render` (for `Image` and
`TextureFormat`)
- `bevy_winit` now depends on `bevy_asset` (for `Assets`, `Handle` and
`AssetId`)
  - `bevy_winit` now depends on `bytemuck` (already in tree)
- Custom cursor code in `bevy_winit` reworked to use `AssetId` (other
than that it is taken over 1:1)
- Rework `bevy_winit` custom cursor interface visibility now that the
logic is all contained in `bevy_winit`

## Testing

- I ran the screenshot and window_settings examples
- Tested on linux wayland so far

---

## Migration Guide

`CursorIcon` and `CustomCursor` previously provided by
`bevy::render::view::cursor` is now available from `bevy::winit`.
A new feature `custom_cursor` enables this functionality (default
feature).
This commit is contained in:
Bude 2024-10-06 20:25:50 +02:00 committed by GitHub
parent 4a23dc4216
commit 6edb78a8c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 64 additions and 40 deletions

View file

@ -132,6 +132,7 @@ default = [
"webgl2", "webgl2",
"sysinfo_plugin", "sysinfo_plugin",
"android-game-activity", "android-game-activity",
"custom_cursor",
] ]
# Provides an implementation for picking sprites # Provides an implementation for picking sprites
@ -417,6 +418,9 @@ track_change_detection = ["bevy_internal/track_change_detection"]
# Enable function reflection # Enable function reflection
reflect_functions = ["bevy_internal/reflect_functions"] reflect_functions = ["bevy_internal/reflect_functions"]
# Enable winit custom cursor support
custom_cursor = ["bevy_internal/custom_cursor"]
[dependencies] [dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.15.0-dev", default-features = false } bevy_internal = { path = "crates/bevy_internal", version = "0.15.0-dev", default-features = false }

View file

@ -223,6 +223,9 @@ reflect_functions = [
"bevy_ecs/reflect_functions", "bevy_ecs/reflect_functions",
] ]
# Enable winit custom cursor support
custom_cursor = ["bevy_winit/custom_cursor"]
[dependencies] [dependencies]
# bevy # bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.15.0-dev" } bevy_a11y = { path = "../bevy_a11y", version = "0.15.0-dev" }

View file

@ -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_time = { path = "../bevy_time", version = "0.15.0-dev" }
bevy_transform = { path = "../bevy_transform", 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_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_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
bevy_tasks = { path = "../bevy_tasks", 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" } bevy_image = { path = "../bevy_image", version = "0.15.0-dev" }

View file

@ -15,12 +15,10 @@ use core::{
num::NonZero, num::NonZero,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use cursor::CursorPlugin;
use wgpu::{ use wgpu::{
SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor, SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor,
}; };
pub mod cursor;
pub mod screenshot; pub mod screenshot;
use screenshot::{ScreenshotPlugin, ScreenshotToScreenPipeline}; use screenshot::{ScreenshotPlugin, ScreenshotToScreenPipeline};
@ -29,7 +27,7 @@ pub struct WindowRenderPlugin;
impl Plugin for WindowRenderPlugin { impl Plugin for WindowRenderPlugin {
fn build(&self, app: &mut App) { 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) { if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app render_app

View file

@ -18,6 +18,8 @@ serialize = ["serde", "bevy_input/serialize", "bevy_window/serialize"]
android-native-activity = ["winit/android-native-activity"] android-native-activity = ["winit/android-native-activity"]
android-game-activity = ["winit/android-game-activity"] android-game-activity = ["winit/android-game-activity"]
custom_cursor = ["bevy_image", "bevy_asset", "bytemuck", "wgpu-types"]
[dependencies] [dependencies]
# bevy # 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_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
bevy_tasks = { path = "../bevy_tasks", 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 # other
# feature rwh_06 refers to window_raw_handle@v0.6 # feature rwh_06 refers to window_raw_handle@v0.6
winit = { version = "0.30", default-features = false, features = ["rwh_06"] } 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" cfg-if = "1.0"
raw-window-handle = "0.6" raw-window-handle = "0.6"
serde = { version = "1.0", features = ["derive"], optional = true } 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] [target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" } wasm-bindgen = { version = "0.2" }

View file

@ -6,7 +6,9 @@ use bevy_input::{
ButtonState, ButtonState,
}; };
use bevy_math::Vec2; 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}; use winit::keyboard::{Key, NamedKey, NativeKey};
pub fn convert_keyboard_input( 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`]. /// Converts a [`SystemCursorIcon`] to a [`winit::window::CursorIcon`].
pub fn convert_system_cursor_icon(cursor_icon: SystemCursorIcon) -> winit::window::CursorIcon { pub fn convert_system_cursor_icon(cursor_icon: SystemCursorIcon) -> winit::window::CursorIcon {
match cursor_icon { match cursor_icon {

View file

@ -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_app::{App, Last, Plugin};
use bevy_asset::{AssetId, Assets, Handle}; use bevy_asset::{Assets, Handle};
use bevy_ecs::{ use bevy_ecs::{
change_detection::DetectChanges, change_detection::DetectChanges,
component::Component, component::Component,
@ -10,18 +17,13 @@ use bevy_ecs::{
system::{Commands, Local, Query, Res}, system::{Commands, Local, Query, Res},
world::{OnRemove, Ref}, world::{OnRemove, Ref},
}; };
use bevy_image::Image;
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_utils::{tracing::warn, HashSet}; use bevy_utils::{tracing::warn, HashSet};
use bevy_window::{SystemCursorIcon, Window}; use bevy_window::{SystemCursorIcon, Window};
use bevy_winit::{ use wgpu_types::TextureFormat;
convert_system_cursor_icon, CursorSource, CustomCursorCache, CustomCursorCacheKey,
PendingCursor,
};
use wgpu::TextureFormat;
use crate::prelude::Image; pub(crate) struct CursorPlugin;
pub struct CursorPlugin;
impl Plugin for CursorPlugin { impl Plugin for CursorPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@ -85,7 +87,7 @@ pub enum CustomCursor {
}, },
} }
pub fn update_cursors( fn update_cursors(
mut commands: Commands, mut commands: Commands,
windows: Query<(Entity, Ref<CursorIcon>), With<Window>>, windows: Query<(Entity, Ref<CursorIcon>), With<Window>>,
cursor_cache: Res<CustomCursorCache>, cursor_cache: Res<CustomCursorCache>,
@ -99,12 +101,7 @@ pub fn update_cursors(
let cursor_source = match cursor.as_ref() { let cursor_source = match cursor.as_ref() {
CursorIcon::Custom(CustomCursor::Image { handle, hotspot }) => { CursorIcon::Custom(CustomCursor::Image { handle, hotspot }) => {
let cache_key = match handle.id() { let cache_key = CustomCursorCacheKey::Asset(handle.id());
AssetId::Index { index, .. } => {
CustomCursorCacheKey::AssetIndex(index.to_bits())
}
AssetId::Uuid { uuid } => CustomCursorCacheKey::AssetUuid(uuid.as_u128()),
};
if cursor_cache.0.contains_key(&cache_key) { if cursor_cache.0.contains_key(&cache_key) {
CursorSource::CustomCached(cache_key) CursorSource::CustomCached(cache_key)
@ -123,7 +120,7 @@ pub fn update_cursors(
let width = image.texture_descriptor.size.width; let width = image.texture_descriptor.size.width;
let height = image.texture_descriptor.size.height; let height = image.texture_descriptor.size.height;
let source = match bevy_winit::WinitCustomCursor::from_rgba( let source = match WinitCustomCursor::from_rgba(
rgba, rgba,
width as u16, width as u16,
height as u16, height as u16,
@ -147,9 +144,8 @@ pub fn update_cursors(
if cursor_cache.0.contains_key(&cache_key) { if cursor_cache.0.contains_key(&cache_key) {
CursorSource::CustomCached(cache_key) CursorSource::CustomCached(cache_key)
} else { } else {
use bevy_winit::CustomCursorExtWebSys; use crate::CustomCursorExtWebSys;
let source = let source = WinitCustomCursor::from_url(url.clone(), hotspot.0, hotspot.1);
bevy_winit::WinitCustomCursor::from_url(url.clone(), hotspot.0, hotspot.1);
CursorSource::Custom((cache_key, source)) CursorSource::Custom((cache_key, source))
} }
} }
@ -165,7 +161,7 @@ pub fn update_cursors(
} }
/// Resets the cursor to the default icon when `CursorIcon` is removed. /// Resets the cursor to the default icon when `CursorIcon` is removed.
pub fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: Commands) { fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: Commands) {
// Use `try_insert` to avoid panic if the window is being destroyed. // Use `try_insert` to avoid panic if the window is being destroyed.
commands commands
.entity(trigger.entity()) .entity(trigger.entity())

View file

@ -23,8 +23,6 @@ use bevy_a11y::AccessibilityRequested;
use bevy_app::{App, Last, Plugin}; use bevy_app::{App, Last, Plugin};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_window::{exit_on_all_closed, Window, WindowCreated}; 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}; use system::{changed_windows, check_keyboard_focus_lost, despawn_windows};
pub use system::{create_monitors, create_windows}; pub use system::{create_monitors, create_windows};
#[cfg(all(target_family = "wasm", target_os = "unknown"))] #[cfg(all(target_family = "wasm", target_os = "unknown"))]
@ -44,6 +42,8 @@ use crate::{
pub mod accessibility; pub mod accessibility;
mod converters; mod converters;
#[cfg(feature = "custom_cursor")]
pub mod cursor;
mod state; mod state;
mod system; mod system;
mod winit_config; mod winit_config;
@ -131,6 +131,8 @@ impl<T: Event> Plugin for WinitPlugin<T> {
); );
app.add_plugins(AccessKitPlugin); app.add_plugins(AccessKitPlugin);
#[cfg(feature = "custom_cursor")]
app.add_plugins(cursor::CursorPlugin);
let event_loop = event_loop_builder let event_loop = event_loop_builder
.build() .build()

View file

@ -1,5 +1,7 @@
use approx::relative_eq; use approx::relative_eq;
use bevy_app::{App, AppExit, PluginsState}; use bevy_app::{App, AppExit, PluginsState};
#[cfg(feature = "custom_cursor")]
use bevy_asset::AssetId;
use bevy_ecs::{ use bevy_ecs::{
change_detection::{DetectChanges, NonSendMut, Res}, change_detection::{DetectChanges, NonSendMut, Res},
entity::Entity, entity::Entity,
@ -8,6 +10,8 @@ use bevy_ecs::{
system::SystemState, system::SystemState,
world::FromWorld, world::FromWorld,
}; };
#[cfg(feature = "custom_cursor")]
use bevy_image::Image;
use bevy_input::{ use bevy_input::{
gestures::*, gestures::*,
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
@ -16,7 +20,9 @@ use bevy_log::{error, trace, warn};
use bevy_math::{ivec2, DVec2, Vec2}; use bevy_math::{ivec2, DVec2, Vec2};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::tick_global_task_pools_on_main_thread; 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 core::marker::PhantomData;
use winit::{ use winit::{
application::ApplicationHandler, application::ApplicationHandler,
@ -88,6 +94,7 @@ struct WinitAppRunnerState<T: Event> {
impl<T: Event> WinitAppRunnerState<T> { impl<T: Event> WinitAppRunnerState<T> {
fn new(mut app: App) -> Self { fn new(mut app: App) -> Self {
#[cfg(feature = "custom_cursor")]
app.add_event::<T>().init_resource::<CustomCursorCache>(); app.add_event::<T>().init_resource::<CustomCursorCache>();
let event_writer_system_state: SystemState<( let event_writer_system_state: SystemState<(
@ -134,23 +141,25 @@ impl<T: Event> WinitAppRunnerState<T> {
} }
} }
#[cfg(feature = "custom_cursor")]
/// Identifiers for custom cursors used in caching. /// Identifiers for custom cursors used in caching.
#[derive(Debug, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum CustomCursorCacheKey { pub enum CustomCursorCacheKey {
/// u64 is used instead of `AssetId`, because `bevy_asset` can't be imported here. /// An `AssetId` to a cursor.
AssetIndex(u64), Asset(AssetId<Image>),
/// u128 is used instead of `AssetId`, because `bevy_asset` can't be imported here. #[cfg(all(target_family = "wasm", target_os = "unknown"))]
AssetUuid(u128), /// An URL to a cursor.
/// A URL to a cursor.
Url(String), Url(String),
} }
#[cfg(feature = "custom_cursor")]
/// Caches custom cursors. On many platforms, creating custom cursors is expensive, especially on /// Caches custom cursors. On many platforms, creating custom cursors is expensive, especially on
/// the web. /// the web.
#[derive(Debug, Clone, Default, Resource)] #[derive(Debug, Clone, Default, Resource)]
pub struct CustomCursorCache(pub HashMap<CustomCursorCacheKey, winit::window::CustomCursor>); pub struct CustomCursorCache(pub HashMap<CustomCursorCacheKey, winit::window::CustomCursor>);
/// 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)] #[derive(Debug)]
pub enum CursorSource { pub enum CursorSource {
/// A custom cursor was identified to be cached, no reason to recreate it. /// A custom cursor was identified to be cached, no reason to recreate it.
@ -161,6 +170,7 @@ pub enum CursorSource {
System(winit::window::CursorIcon), System(winit::window::CursorIcon),
} }
#[cfg(feature = "custom_cursor")]
/// Component that indicates what cursor should be used for a window. Inserted /// Component that indicates what cursor should be used for a window. Inserted
/// automatically after changing `CursorIcon` and consumed by the winit event /// automatically after changing `CursorIcon` and consumed by the winit event
/// loop. /// loop.
@ -547,6 +557,7 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
// This is a temporary solution, full solution is mentioned here: https://github.com/bevyengine/bevy/issues/1343#issuecomment-770091684 // 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 { if !self.ran_update_since_last_redraw || all_invisible {
self.run_app_update(); self.run_app_update();
#[cfg(feature = "custom_cursor")]
self.update_cursors(event_loop); self.update_cursors(event_loop);
self.ran_update_since_last_redraw = true; self.ran_update_since_last_redraw = true;
} else { } else {
@ -775,6 +786,7 @@ impl<T: Event> WinitAppRunnerState<T> {
.send_batch(buffered_events); .send_batch(buffered_events);
} }
#[cfg(feature = "custom_cursor")]
fn update_cursors(&mut self, event_loop: &ActiveEventLoop) { fn update_cursors(&mut self, event_loop: &ActiveEventLoop) {
let mut windows_state: SystemState<( let mut windows_state: SystemState<(
NonSendMut<WinitWindows>, NonSendMut<WinitWindows>,

View file

@ -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|A custom ECS-driven UI framework|
|bevy_ui_picking_backend|Provides an implementation for picking ui| |bevy_ui_picking_backend|Provides an implementation for picking ui|
|bevy_winit|winit window and input backend| |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| |default_font|Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase|
|hdr|HDR image format support| |hdr|HDR image format support|
|ktx2|KTX2 compressed texture support| |ktx2|KTX2 compressed texture support|

View file

@ -2,11 +2,9 @@
use bevy::{ use bevy::{
prelude::*, prelude::*,
render::view::{ render::view::screenshot::{save_to_disk, Capturing, Screenshot},
cursor::CursorIcon,
screenshot::{save_to_disk, Capturing, Screenshot},
},
window::SystemCursorIcon, window::SystemCursorIcon,
winit::cursor::CursorIcon,
}; };
fn main() { fn main() {

View file

@ -5,8 +5,8 @@ use bevy::{
core::FrameCount, core::FrameCount,
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*, prelude::*,
render::view::cursor::{CursorIcon, CustomCursor},
window::{CursorGrabMode, PresentMode, SystemCursorIcon, WindowLevel, WindowTheme}, window::{CursorGrabMode, PresentMode, SystemCursorIcon, WindowLevel, WindowTheme},
winit::cursor::{CursorIcon, CustomCursor},
}; };
fn main() { fn main() {