diff --git a/main.lua b/main.lua index ef8d0241..a2e7a300 100644 --- a/main.lua +++ b/main.lua @@ -173,6 +173,28 @@ function love.gamepadaxis(joystick, axis, value) Game:gamepadaxis(joystick, axis, value) end +function love.joystickremoved(joystick) + if editorMode then return end + if Importer then return end + Game:joystickremoved(joystick) +end + +-- f is true on focus gained, false on focus lost (e.g. alt-tab). A held +-- direction's key-up can be delivered to the OS instead of the game while +-- unfocused, so reset input on either transition rather than trust it. +function love.focus(f) + if editorMode then return end + if Importer then return end + Game:focus(f) +end + +-- v is true when the window becomes visible again, false on minimize. +function love.visible(v) + if editorMode then return end + if Importer then return end + Game:visible(v) +end + function love.touchpressed(id, x, y, dx, dy, pressure) if editorMode then return end if Importer then return Importer:mousepressed(x, y, 1) end diff --git a/src/core/Game.lua b/src/core/Game.lua index 940dd720..91bd2b98 100644 --- a/src/core/Game.lua +++ b/src/core/Game.lua @@ -349,6 +349,28 @@ function Game:gamepadaxis(joystick, axis, value) Input:gamepadaxis(joystick, axis, value) end +-- Window lost focus or got minimized: any release event due while it was +-- unfocused/hidden can be swallowed by the OS instead of delivered here, +-- which would otherwise leave a held direction stuck on. +function Game:focus(f) + if f then return end + Input:reset() + TouchInput:reset() +end + +function Game:visible(v) + if v then return end + Input:reset() + TouchInput:reset() +end + +-- A disconnected/dropped controller can't send the button-up for whatever +-- it was holding, so drop all input state rather than try to guess which +-- flags it owned. +function Game:joystickremoved(joystick) + Input:reset() +end + function Game:touchpressed(id, x, y) TouchInput:touchpressed(id, x, y) end diff --git a/src/core/Input.lua b/src/core/Input.lua index 5bd19ee4..4088a98a 100644 --- a/src/core/Input.lua +++ b/src/core/Input.lua @@ -32,6 +32,15 @@ local STICK_ON = 0.5 local STICK_OFF = 0.3 function Input:init() + self:reset() +end + +-- Purely event-driven state (press sets true, release sets false) has no +-- fallback if a release event never arrives -- focus loss, a minimized +-- window, or a disconnected gamepad can all swallow the key-up/button-up +-- that would have cleared a held direction. Called from Game on those +-- transitions so a stuck flag can't outlive them. +function Input:reset() self.state = {} self.pressQueue = {} self.pressed = {} diff --git a/src/core/TouchInput.lua b/src/core/TouchInput.lua index 9e4a350f..984c79b6 100644 --- a/src/core/TouchInput.lua +++ b/src/core/TouchInput.lua @@ -75,6 +75,21 @@ function TouchInput:init() self.selectFired = false -- one SELECT per two-finger gesture cluster end +-- LÖVE has no touchcancelled event, so a touch interrupted by the OS (app +-- backgrounded mid-touch, a system gesture stealing the finger) never fires +-- touchreleased and would otherwise strand its direction held forever. +-- Called from Game alongside Input:reset() on focus/visibility loss. +function TouchInput:reset() + for _, touch in pairs(self.touches) do + releaseDir(self, touch) + end + self.touches = {} + self.pendingA = nil + self.armed = {} + self.autoRelease = {} + self.selectFired = false +end + local function pulse(self, key) Input:keypressed(key) self.armed[#self.armed + 1] = key