From 5467859d6d1a61bfaeafad8ce46a7938d503d077 Mon Sep 17 00:00:00 2001 From: copygirl Date: Mon, 5 Jan 2026 07:12:17 +0100 Subject: [PATCH] Use picking system for cursor grabbing --- client/src/input/cursor_grab.rs | 91 ++++++++++++++++----------------- client/src/main.rs | 12 +++-- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/client/src/input/cursor_grab.rs b/client/src/input/cursor_grab.rs index ffe3142..571e777 100644 --- a/client/src/input/cursor_grab.rs +++ b/client/src/input/cursor_grab.rs @@ -1,63 +1,60 @@ use bevy::prelude::*; +use bevy::input::common_conditions::input_just_pressed; use bevy::window::{CursorGrabMode, CursorOptions}; use crate::Screen; pub fn plugin(app: &mut App) { + app.add_systems(OnEnter(Screen::Gameplay), add_window_click_observer); + app.add_systems(PreUpdate, center_cursor.run_if(is_cursor_grabbed)); app.add_systems( - PreUpdate, - update_cursor_grab.run_if(in_state(Screen::Gameplay)), + Update, + ungrab_cursor.run_if( + is_cursor_grabbed.and(input_just_pressed(KeyCode::Escape).or(not(is_window_focused))), + ), ); } -fn update_cursor_grab( - mut mouse_button_input: ResMut>, - key_input: Res>, - window: Single<(&mut Window, &mut CursorOptions)>, -) { - let (mut window, mut cursor) = window.into_inner(); - - let is_grabbed = cursor.grab_mode != CursorGrabMode::None; - let request_grab = mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]); - let request_ungrab = !window.focused || key_input.just_pressed(KeyCode::Escape); - - if !is_grabbed && request_grab && !request_ungrab { - cursor.grab_mode = CursorGrabMode::Locked; - - // To prevent other systems (such as `place_break_blocks`) - // from seeing the mouse button inputs, clear the state here. - mouse_button_input.clear(); - } - - if is_grabbed && request_ungrab && !request_grab { - cursor.grab_mode = CursorGrabMode::None; - } - - if is_grabbed && !request_ungrab { - // Set the cursor position to the center of the window, so that when - // it is ungrabbed, it will reappear there. Because `is_grabbed` is - // not updated on grab, this block is delayed by one frame. - // - // On Wayland, since the cursor is locked into place, this only needs - // to be done once. Unfortunately, for some reason this doesn't work - // in the same frame as setting `grab_mode`, and would log an error. - // - // On X11, the cursor can't be locked into place, only confined to the - // window bounds, so we repeatedly move the cursor back to the center - // while it's grabbed. - // - // On the web, the cursor can be locked, but setting its position is - // not supported at all, so this would instead log a bunch of errors. - let center = window.resolution.size() / 2.; - #[cfg(not(target_family = "wasm"))] // skip on web - window.set_cursor_position(Some(center)); - } - - // Keep cursor visbility in sync with `grab_mode`. - cursor.visible = cursor.grab_mode == CursorGrabMode::None; +fn add_window_click_observer(window: Single>, mut commands: Commands) { + commands.entity(*window).observe( + |mut event: On>, mut cursor: Single<&mut CursorOptions>| { + cursor.grab_mode = CursorGrabMode::Locked; + cursor.visible = false; + event.propagate(false); + }, + ); +} + +fn ungrab_cursor(mut cursor: Single<&mut CursorOptions>) { + cursor.grab_mode = CursorGrabMode::None; + cursor.visible = true; +} + +/// Sets the cursor position to the center of the window, +/// so that once it is ungrabbed, it will reappear there. +fn center_cursor(mut window: Single<&mut Window>) { + let center = window.resolution.size() / 2.; + + // On Wayland, since the cursor is locked into place, this only needs to be + // done once. Unfortunately, for some reason, this doesn't work in the same + // frame as setting `grab_mode`, logging an error. This system is scheduled + // on `PreUpdate`, so it runs with one frame delay. + // + // On X11, the cursor can't be locked into place, only confined to the + // window bounds, so we repeatedly move the cursor back to the center + // while it's grabbed. + // + // On the web, the cursor can be locked, but setting its position isn't + // supported at all, so this would instead log a bunch of errors. + #[cfg(not(target_family = "wasm"))] + window.set_cursor_position(Some(center)); } pub fn is_cursor_grabbed(cursor: Single<&CursorOptions>) -> bool { cursor.grab_mode != CursorGrabMode::None } + +pub fn is_window_focused(window: Single<&Window>) -> bool { + window.focused +} diff --git a/client/src/main.rs b/client/src/main.rs index ba5dd34..4869d43 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -26,7 +26,7 @@ fn main() -> Result { let mut app = App::new(); app.insert_resource(args); - app.add_plugins( + app.add_plugins(( DefaultPlugins .set(WindowPlugin { primary_window: Some(Window { @@ -47,10 +47,16 @@ fn main() -> Result { meta_check: AssetMetaCheck::Never, ..default() }), - ); + bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin, + )); + + // Make entities require the `Pickable` component if + // they should be considered for the picking system. + app.insert_resource(UiPickingSettings { + require_markers: true, + }); app.add_plugins(( - bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin, common::assets::plugin, common::network::plugin, assets::plugin,