mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 10:39:46 -10:00
Lock the pointer again when the window regains focus
Vanilla's grab outlives the window's focus: MouseHandler.grabMouse sets mouseGrabbed and the disabled cursor mode, nothing releases either when the focus goes, and the mode is re-applied when the window comes back, so the pointer is locked again without a click (and put back at the window's centre). Horizon released the pointer on every focus loss and never took it back, and winit only applies a cursor clip while the window is focused and does not re-apply one on WM_SETFOCUS, so the grab was gone: the pointer stayed where it was, often outside the window, and clicks went to whatever was under it. The window now remembers whether it held the pointer when the focus went and takes it again on the focus return, through the same capture(true) the rest of the game uses. A screen that is open keeps the pointer free, and the existing press-to-grab path stays as vanilla's fallback.
This commit is contained in:
1 parent
f4833621f9
commit
e7d70fbe7c
2 files changed
+98
-29
No files matched your search
@@ -2240,6 +2240,11 @@ struct Game<'a> {
|
||||
/// The cursor icon the window shows (`widgets::take_cursor` each frame).
|
||||
cursor_icon: horizon_client::widgets::Cursor,
|
||||
captured: bool,
|
||||
/// The window held the pointer when it lost focus: the system lets a
|
||||
/// grabbed pointer go with the focus, and a window that comes back still
|
||||
/// wanting it takes it again (`MouseHandler.grabMouse`, whose grab
|
||||
/// outlives the window's focus).
|
||||
regrab_on_refocus: bool,
|
||||
/// Only the local player owning keyboard and mouse changes the native pointer.
|
||||
keyboard_mouse: bool,
|
||||
/// This view's corner in its shared window; cursor warps include the offset.
|
||||
@@ -7894,6 +7899,17 @@ impl Game<'_> {
|
||||
self.capture(true);
|
||||
}
|
||||
}
|
||||
/// The window came back into focus after holding the pointer: the system
|
||||
/// dropped the grab with the focus, and vanilla's grab outlives it
|
||||
/// (`MouseHandler.mouseGrabbed` stays true while GLFW keeps the cursor
|
||||
/// mode), so the game takes the pointer again without waiting for a
|
||||
/// click. A screen that is open keeps it free, as releasing the pointer
|
||||
/// for that screen did: it is the screen, not the focus, that shows it.
|
||||
fn regrab_on_refocus(&mut self) {
|
||||
if std::mem::take(&mut self.regrab_on_refocus) && self.screen == Screen::Playing {
|
||||
self.capture(true);
|
||||
}
|
||||
}
|
||||
fn draw_item(&self, batch: &mut HudBatch, stack: &ItemStack, x: f32, y: f32) {
|
||||
// `GuiGraphics.renderItem`: the resolved item model (tints, foil and
|
||||
// special renderers included), then the decorations.
|
||||
@@ -16745,31 +16761,40 @@ impl Game<'_> {
|
||||
WindowEvent::Focused(focused) => {
|
||||
self.focused = focused;
|
||||
self.focus_lost = (!focused).then(Instant::now);
|
||||
if !focused && self.control.is_none() {
|
||||
self.keys.clear();
|
||||
// Latched sneak/sprint live in `toggles`, not in the
|
||||
// physical keys: put them back so a short alt-tab
|
||||
// (no pause screen) does not drop them. Only a mapping
|
||||
// whose toggle option is on is latched; a held one has
|
||||
// no state to keep, and an unfocused window never gets
|
||||
// its key release, so leaving it down would keep the
|
||||
// player sneaking or sprinting until the key is tapped.
|
||||
for (slot, needs_toggle) in [
|
||||
self.settings.toggle_crouch,
|
||||
self.settings.toggle_sprint,
|
||||
self.settings.toggle_attack,
|
||||
self.settings.toggle_use,
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
self.toggles.0[slot].set_down(false, needs_toggle);
|
||||
if focused {
|
||||
self.regrab_on_refocus();
|
||||
} else {
|
||||
// A held pointer goes with the focus, wherever the
|
||||
// system keeps the grab: the window asks for it back
|
||||
// when the focus returns (`regrab_on_refocus`).
|
||||
let held = self.captured;
|
||||
if self.control.is_none() {
|
||||
self.keys.clear();
|
||||
// Latched sneak/sprint live in `toggles`, not in the
|
||||
// physical keys: put them back so a short alt-tab
|
||||
// (no pause screen) does not drop them. Only a mapping
|
||||
// whose toggle option is on is latched; a held one has
|
||||
// no state to keep, and an unfocused window never gets
|
||||
// its key release, so leaving it down would keep the
|
||||
// player sneaking or sprinting until the key is tapped.
|
||||
for (slot, needs_toggle) in [
|
||||
self.settings.toggle_crouch,
|
||||
self.settings.toggle_sprint,
|
||||
self.settings.toggle_attack,
|
||||
self.settings.toggle_use,
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
self.toggles.0[slot].set_down(false, needs_toggle);
|
||||
}
|
||||
self.sync_toggle(0);
|
||||
self.sync_toggle(1);
|
||||
self.left = false;
|
||||
self.right = false;
|
||||
self.capture(false);
|
||||
}
|
||||
self.sync_toggle(0);
|
||||
self.sync_toggle(1);
|
||||
self.left = false;
|
||||
self.right = false;
|
||||
self.capture(false);
|
||||
self.regrab_on_refocus = held;
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
@@ -17945,6 +17970,7 @@ impl<'a> Game<'a> {
|
||||
cursor: (0.0, 0.0),
|
||||
cursor_icon: horizon_client::widgets::Cursor::Arrow,
|
||||
captured: false,
|
||||
regrab_on_refocus: false,
|
||||
keyboard_mouse: true,
|
||||
view_at: [0, 0],
|
||||
split_hud: Default::default(),
|
||||
|
||||
@@ -107,7 +107,8 @@ struct ServerPacks { prompt: Option<()> }
|
||||
struct Game {
|
||||
focused: bool, agent_pointer: bool, window: Option<Window>, mouse: Mouse,
|
||||
keyboard_mouse: bool, view_at: [u32; 2],
|
||||
captured: bool, title: Option<()>, screen: Screen, remote: Option<Remote>,
|
||||
captured: bool, regrab_on_refocus: bool, title: Option<()>, screen: Screen,
|
||||
remote: Option<Remote>,
|
||||
mining: bool, lan_acting: Option<u32>, renderer: Option<Renderer>, cursor: (f32, f32),
|
||||
server_packs: ServerPacks, options: Options,
|
||||
}
|
||||
@@ -115,7 +116,7 @@ impl Default for Game {
|
||||
fn default() -> Self {
|
||||
Self { focused: true, agent_pointer: false, window: Some(Window::default()),
|
||||
keyboard_mouse: true, view_at: [0, 0],
|
||||
mouse: Mouse::default(), captured: false, title: None,
|
||||
mouse: Mouse::default(), captured: false, regrab_on_refocus: false, title: None,
|
||||
screen: Screen::Playing, remote: None, mining: false, lan_acting: None,
|
||||
renderer: None, cursor: (0.0, 0.0), server_packs: ServerPacks::default(),
|
||||
options: Options::default() }
|
||||
@@ -182,11 +183,52 @@ fn main() {
|
||||
assert!(game.agent_pointer);
|
||||
assert_eq!(game.window.as_ref().unwrap().grabs.get(), grabs);
|
||||
game.focused = true;
|
||||
assert!(!game.captured); // Focus alone does not resume gameplay.
|
||||
game.capture(true); // Click or resume action.
|
||||
assert!(!game.captured); // Setting the flag is not a grab: `capture` is.
|
||||
game.capture(true); // `regrab_on_refocus`, or a click on the world.
|
||||
assert!(game.captured);
|
||||
assert_eq!(game.cursor, (213.5, 120.0)); // The grab puts the mouse at the centre.
|
||||
}
|
||||
// A window that held the pointer asks for it back when the focus returns:
|
||||
// the system drops a grabbed pointer with the focus and vanilla's grab
|
||||
// outlives it (`MouseHandler.mouseGrabbed` stays true while GLFW keeps its
|
||||
// cursor mode), so no click is needed to lock it again.
|
||||
{
|
||||
let mut game = Game::default();
|
||||
game.capture(true);
|
||||
assert!(game.captured);
|
||||
let grabs = game.window.as_ref().unwrap().grabs.get();
|
||||
game.focused = false; // Alt+Tab: the release `window_event` makes.
|
||||
game.regrab_on_refocus = game.captured;
|
||||
game.capture(false);
|
||||
assert!(!game.captured);
|
||||
game.focused = true;
|
||||
game.regrab_on_refocus();
|
||||
assert!(game.captured);
|
||||
assert!(game.window.as_ref().unwrap().grabs.get() > grabs);
|
||||
assert!(game.mouse.ignore_first);
|
||||
// Taken once: a focus gain that follows a screen's release takes nothing.
|
||||
game.capture(false);
|
||||
let grabs = game.window.as_ref().unwrap().grabs.get();
|
||||
game.regrab_on_refocus();
|
||||
assert!(!game.captured);
|
||||
assert_eq!(game.window.as_ref().unwrap().grabs.get(), grabs);
|
||||
// A screen open while the window was away keeps the pointer free.
|
||||
game.regrab_on_refocus = true;
|
||||
game.screen = Screen::Inventory;
|
||||
game.regrab_on_refocus();
|
||||
assert!(!game.captured);
|
||||
assert_eq!(game.window.as_ref().unwrap().grabs.get(), grabs);
|
||||
// A window that never held the pointer does not take it on focus.
|
||||
game.screen = Screen::Playing;
|
||||
game.regrab_on_refocus();
|
||||
assert!(!game.captured);
|
||||
assert_eq!(game.window.as_ref().unwrap().grabs.get(), grabs);
|
||||
// A prompt over the world keeps the pointer, as the grab itself checks.
|
||||
game.regrab_on_refocus = true;
|
||||
game.server_packs.prompt = Some(());
|
||||
game.regrab_on_refocus();
|
||||
assert!(!game.captured);
|
||||
}
|
||||
// A controller-only player's screens leave the shared native pointer alone.
|
||||
{
|
||||
let mut game = Game::default();
|
||||
@@ -275,7 +317,8 @@ fn main() {
|
||||
"mouse_position_after_grab_change")
|
||||
fixture = fixture.replace("HELPER", function(mouse, "mouse_position_after_grab_change"))
|
||||
fixture = fixture.replace("METHODS", capture + "\n" +
|
||||
method(source, "capture_loaded_world"))
|
||||
method(source, "capture_loaded_world") + "\n" +
|
||||
method(source, "regrab_on_refocus"))
|
||||
with tempfile.TemporaryDirectory(prefix="horizon-input-") as folder:
|
||||
folder = Path(folder)
|
||||
rust = folder / "capture.rs"
|
||||
|
||||
Reference in new issue
Block a user