CLOSES #1367, CLOSES #1585, CLOSES #1611, CLOSES #1612, CLOSES #1636, CLOSES #1638, CLOSES #1641

This commit is contained in:
bryanthaboi
2026-08-21 07:14:24 -04:00
parent c11c762f15
commit 8c0d0ace4d
15 changed files with 174 additions and 52 deletions
+2
View File
@@ -877,6 +877,8 @@ love.handlers = love.handlers or {}
function love.handlers.audiosuspend() function love.handlers.audiosuspend()
local ChipAudio = package.loaded["src.core.ChipAudio"] local ChipAudio = package.loaded["src.core.ChipAudio"]
if ChipAudio then pcall(ChipAudio.setSuspended, true) end if ChipAudio then pcall(ChipAudio.setSuspended, true) end
local Sound = package.loaded["src.core.Sound"]
if Sound then pcall(Sound.onDeviceReset) end
end end
function love.handlers.audioreset() function love.handlers.audioreset()
@@ -54,7 +54,7 @@
<activity <activity
android:name="org.love2d.android.GameActivity" android:name="org.love2d.android.GameActivity"
android:exported="true" android:exported="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation|uiMode|density|fontScale|locale|layoutDirection|colorMode"
android:label="${NAME}" android:label="${NAME}"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="${ORIENTATION}" android:screenOrientation="${ORIENTATION}"
@@ -71,7 +71,7 @@
</activity> </activity>
<activity <activity
android:name="org.love2d.android.GameActivity$SecondaryActivity" android:name="org.love2d.android.GameActivity$SecondaryActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation|uiMode|density|fontScale|locale|layoutDirection|colorMode"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="false" android:exported="false"
android:launchMode="singleTask" android:launchMode="singleTask"
+1 -10
View File
@@ -919,16 +919,7 @@ function Game:gamepadaxis(joystick, axis, value)
Input:gamepadaxis(joystick, axis, value) Input:gamepadaxis(joystick, axis, value)
end end
-- conf.lua turns the mobile accelerometer-joystick off (#468), but guard the local isAccelerometer = GamepadMap.isAccelerometer
-- generic joystick path anyway: any sensor-style device that still reaches us
-- has gravity pinning an axis past the deadzone, which would hide the touch
-- overlay every instant and steer the player by tilt through the axis-1/2
-- mapping (#459). Real controllers arrive as SDL gamepads or named sticks,
-- never as "* Accelerometer".
local function isAccelerometer(joystick)
local name = joystick and joystick.getName and joystick:getName()
return name ~= nil and name:lower():find("accelerometer", 1, true) ~= nil
end
-- BindingsMenu's raw-stick capture rides the same top-state routing as the -- BindingsMenu's raw-stick capture rides the same top-state routing as the
-- keyboard and gamepad paths (#632). Only a stick SDL does not recognize -- keyboard and gamepad paths (#632). Only a stick SDL does not recognize
+11
View File
@@ -106,6 +106,17 @@ function GamepadMap.ignoreRawForJoystick(joystick)
return ok and isPad == true return ok and isPad == true
end end
-- conf.lua turns the mobile accelerometer-joystick off (#468), but guard the
-- generic joystick path anyway: any sensor-style device that still reaches us
-- has gravity pinning an axis past the deadzone, which would hide the touch
-- overlay every instant and steer the player by tilt through the axis-1/2
-- mapping (#459). Real controllers arrive as SDL gamepads or named sticks,
-- never as "* Accelerometer".
function GamepadMap.isAccelerometer(joystick)
local name = joystick and joystick.getName and joystick:getName()
return name ~= nil and name:lower():find("accelerometer", 1, true) ~= nil
end
function GamepadMap.mapRawButton(index) function GamepadMap.mapRawButton(index)
if nxActive() then if nxActive() then
local nx = GamepadMap.NX_RAW_BUTTON_BINDINGS[index] local nx = GamepadMap.NX_RAW_BUTTON_BINDINGS[index]
+6 -1
View File
@@ -281,6 +281,7 @@ end
function Input:joystickpressed(joystick, button) function Input:joystickpressed(joystick, button)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
noteCapture(self, "joy", "pressed", button) noteCapture(self, "joy", "pressed", button)
local btn = self.joyBindings[button] local btn = self.joyBindings[button]
if btn then press(self, btn, "joy:" .. button) end if btn then press(self, btn, "joy:" .. button) end
@@ -288,6 +289,7 @@ end
function Input:joystickreleased(joystick, button) function Input:joystickreleased(joystick, button)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
noteCapture(self, "joy", "released", button) noteCapture(self, "joy", "released", button)
local btn = self.joyBindings[button] local btn = self.joyBindings[button]
if btn then release(self, btn, "joy:" .. button) end if btn then release(self, btn, "joy:" .. button) end
@@ -330,6 +332,7 @@ end
function Input:joystickaxis(joystick, axis, value) function Input:joystickaxis(joystick, axis, value)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
if axis == 1 then if axis == 1 then
self:gamepadaxis(joystick, "leftx", value) self:gamepadaxis(joystick, "leftx", value)
elseif axis == 2 then elseif axis == 2 then
@@ -343,6 +346,7 @@ end
-- directions on top of a direction rebind. -- directions on top of a direction rebind.
function Input:joystickhat(joystick, hat, direction) function Input:joystickhat(joystick, hat, direction)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
local source = "hat:" .. hat local source = "hat:" .. hat
for _, btn in ipairs(self.hatDirs[hat] or {}) do for _, btn in ipairs(self.hatDirs[hat] or {}) do
release(self, btn, source) release(self, btn, source)
@@ -380,7 +384,8 @@ function Input:reconcile()
local ok, joysticks = pcall(js.getJoysticks) local ok, joysticks = pcall(js.getJoysticks)
if not ok or type(joysticks) ~= "table" then return end if not ok or type(joysticks) ~= "table" then return end
for _, j in ipairs(joysticks) do for _, j in ipairs(joysticks) do
if GamepadMap.ignoreRawForJoystick(j) then if GamepadMap.isAccelerometer(j) then
elseif GamepadMap.ignoreRawForJoystick(j) then
-- SDL-recognized pad: buttons + left stick, the gamepad surfaces -- SDL-recognized pad: buttons + left stick, the gamepad surfaces
if j.isGamepadDown then if j.isGamepadDown then
for button, btn in pairs(self.padBindings) do for button, btn in pairs(self.padBindings) do
+79 -23
View File
@@ -1,4 +1,4 @@
-- Screen orientation lock, Android only (#592, #716). -- Screen orientation lock, Android and iOS (#592, #716, #1638).
-- --
-- Persisted as options.orientation: "auto" | "portrait" | "landscape" | -- Persisted as options.orientation: "auto" | "portrait" | "landscape" |
-- "reverseLandscape". The lock travels through SDL_HINT_ORIENTATIONS: -- "reverseLandscape". The lock travels through SDL_HINT_ORIENTATIONS:
@@ -10,16 +10,12 @@
-- rotation lock"; LANDSCAPE allows both landscapes (SENSOR_LANDSCAPE -> -- rotation lock"; LANDSCAPE allows both landscapes (SENSOR_LANDSCAPE ->
-- USER_LANDSCAPE); REVERSE LANDSCAPE is SDL's LandscapeRight alone. -- USER_LANDSCAPE); REVERSE LANDSCAPE is SDL's LandscapeRight alone.
-- --
-- SDL only re-reads the hint when the window is created or its resizable -- Android only re-reads the hint at window creation or on a resizable-flag
-- flag changes (SDL_androidwindow.c: Android_CreateWindow / -- change (SDL_androidwindow.c), and SDL_SetWindowResizable early-returns on
-- Android_SetWindowResizable both call Android_JNI_SetOrientation). LOVE -- a fullscreen window (SDL_video.c:2237) -- which LOVE's Android window
-- 11.5 exposes neither hints nor a resizable setter, so apply() goes through -- always is -- so the hint never reached a running activity (#1638).
-- the FFI to SDL's C API: set the hint, then pulse the window's resizable -- apply() sets the hint for a later window, then goes over JNI for the live
-- flag off and back on -- each edge makes the Android backend recompute the -- one. iOS needs only the hint. Desktop and headless stubs no-op.
-- requested orientation, so a change from the launcher or the OPTION menu
-- takes hold immediately, and the flag ends where it started (conf.lua sets
-- resizable on mobile). Everything is pcall-guarded: desktop, iOS (the
-- Info.plist governs there) and headless stubs make this a no-op.
local Orientation = {} local Orientation = {}
@@ -58,6 +54,11 @@ function Orientation.isAndroid()
return love.system.getOS() == "Android" return love.system.getOS() == "Android"
end end
function Orientation.isIOS()
if not love or not love.system or not love.system.getOS then return false end
return love.system.getOS() == "iOS"
end
function Orientation.cycle(mode, dir) function Orientation.cycle(mode, dir)
local cur, idx = Orientation.normalize(mode), 1 local cur, idx = Orientation.normalize(mode), 1
for i, m in ipairs(Orientation.MODES) do for i, m in ipairs(Orientation.MODES) do
@@ -67,6 +68,15 @@ function Orientation.cycle(mode, dir)
return Orientation.MODES[(idx - 1 + (dir or 1)) % n + 1] return Orientation.MODES[(idx - 1 + (dir or 1)) % n + 1]
end end
-- ActivityInfo constants, what setOrientationBis lands on per hint after
-- GameActivity's *_SENSOR -> *_USER remap (#716).
local REQUESTED = {
auto = 13,
portrait = 1,
landscape = 11,
reverseLandscape = 8,
}
-- The SDL2 C API this module needs. cdef errors on redefinition, so run it -- The SDL2 C API this module needs. cdef errors on redefinition, so run it
-- once and remember whether it took; ffi itself may be absent (plain Lua -- once and remember whether it took; ffi itself may be absent (plain Lua
-- test interpreters), hence the pcall'd require. -- test interpreters), hence the pcall'd require.
@@ -77,33 +87,79 @@ local function sdlFfi()
if cdefOk == nil then if cdefOk == nil then
cdefOk = pcall(ffi.cdef, [[ cdefOk = pcall(ffi.cdef, [[
typedef struct SDL_Window SDL_Window; typedef struct SDL_Window SDL_Window;
typedef union { int32_t i; int64_t pad; } love_jvalue;
int SDL_SetHint(const char *name, const char *value); int SDL_SetHint(const char *name, const char *value);
SDL_Window *SDL_GL_GetCurrentWindow(void); SDL_Window *SDL_GL_GetCurrentWindow(void);
void SDL_SetWindowResizable(SDL_Window *window, int resizable); void SDL_SetWindowResizable(SDL_Window *window, int resizable);
void *SDL_AndroidGetJNIEnv(void);
void *SDL_AndroidGetActivity(void);
]]) ]])
end end
if not cdefOk then return nil end if not cdefOk then return nil end
return ffi return ffi
end end
-- Push the mode into the live activity. Returns true when the hint reached -- Slot numbers in JNINativeInterface (jni.h).
-- SDL (the symbols resolved), false on any non-Android / stubbed platform. local JNI_EXCEPTION_CLEAR = 17
local JNI_DELETE_LOCAL_REF = 23
local JNI_GET_OBJECT_CLASS = 31
local JNI_GET_METHOD_ID = 33
local JNI_CALL_VOID_METHOD_A = 63
-- What Android_JNI_SetOrientation reaches, called directly: the hint path
-- cannot re-run on a live fullscreen window (SDL_video.c:2237).
local function setRequestedOrientation(ffi, requested)
local env = ffi.C.SDL_AndroidGetJNIEnv()
if env == nil then return false end
local activity = ffi.C.SDL_AndroidGetActivity()
if activity == nil then return false end
local fns = ffi.cast("void***", env)[0]
local getObjectClass = ffi.cast("void *(*)(void *, void *)", fns[JNI_GET_OBJECT_CLASS])
local getMethodID = ffi.cast(
"void *(*)(void *, void *, const char *, const char *)", fns[JNI_GET_METHOD_ID])
local callVoidMethodA = ffi.cast(
"void (*)(void *, void *, void *, love_jvalue *)", fns[JNI_CALL_VOID_METHOD_A])
local deleteLocalRef = ffi.cast("void (*)(void *, void *)", fns[JNI_DELETE_LOCAL_REF])
local exceptionClear = ffi.cast("void (*)(void *)", fns[JNI_EXCEPTION_CLEAR])
local ok = false
local cls = getObjectClass(env, activity)
if cls ~= nil then
local mid = getMethodID(env, cls, "setRequestedOrientation", "(I)V")
if mid ~= nil then
local args = ffi.new("love_jvalue[1]")
args[0].pad = 0
args[0].i = requested
callVoidMethodA(env, activity, mid, args)
ok = true
end
exceptionClear(env)
deleteLocalRef(env, cls)
end
deleteLocalRef(env, activity)
return ok
end
-- Returns true only when the request actually landed, never unconditionally
-- as it once did (#1638).
function Orientation.apply(mode) function Orientation.apply(mode)
if not Orientation.isAndroid() then return false end local android = Orientation.isAndroid()
if not (android or Orientation.isIOS()) then return false end
local ffi = sdlFfi() local ffi = sdlFfi()
if not ffi then return false end if not ffi then return false end
mode = Orientation.normalize(mode) mode = Orientation.normalize(mode)
local ok = pcall(function() local ok, reached = pcall(function()
-- "SDL_IOS_ORIENTATIONS" is SDL_HINT_ORIENTATIONS's name (SDL_hints.h); -- SDL_HINT_ORIENTATIONS is "SDL_IOS_ORIENTATIONS" in the SDL2 Android
-- despite the IOS in the string, the Android backend reads it too. -- ships and "SDL_ORIENTATIONS" in the SDL3 the iOS app links; each
-- engine ignores the other's key.
ffi.C.SDL_SetHint("SDL_IOS_ORIENTATIONS", HINTS[mode]) ffi.C.SDL_SetHint("SDL_IOS_ORIENTATIONS", HINTS[mode])
local win = ffi.C.SDL_GL_GetCurrentWindow() ffi.C.SDL_SetHint("SDL_ORIENTATIONS", HINTS[mode])
if win ~= nil then -- On iOS the hint is the lock: UIKit re-asks on every rotation
ffi.C.SDL_SetWindowResizable(win, 0) -- (SDL_uikitviewcontroller.m supportedInterfaceOrientations).
ffi.C.SDL_SetWindowResizable(win, 1) if not android then return true end
end return setRequestedOrientation(ffi, REQUESTED[mode])
end) end)
return ok return ok and reached == true
end end
function Orientation.applyOptions(opts) function Orientation.applyOptions(opts)
+8
View File
@@ -154,8 +154,14 @@ local function newSfxSource(data, key, def, pitch, tempo, plain)
return newFileSource(def) return newFileSource(def)
end end
local function deviceSuspended()
local ChipAudio = package.loaded["src.core.ChipAudio"]
return ChipAudio ~= nil and ChipAudio.isSuspended()
end
local function playPath(data, key, def, pitch, tempo, plain) local function playPath(data, key, def, pitch, tempo, plain)
if not love.audio or not def then return nil end if not love.audio or not def then return nil end
if deviceSuspended() then return nil end
local src = cache[key] local src = cache[key]
if src == false then return nil end -- known bad, already logged if src == false then return nil end -- known bad, already logged
if not src then if not src then
@@ -602,6 +608,7 @@ end
-- cache carries no clips (Red/Blue) or headless. -- cache carries no clips (Red/Blue) or headless.
function Sound.playPikaCry(data, n) function Sound.playPikaCry(data, n)
if not love.audio then return nil end if not love.audio then return nil end
if deviceSuspended() then return nil end
local count = data.audio and data.audio.pikaCries local count = data.audio and data.audio.pikaCries
if not count then return nil end if not count then return nil end
n = math.max(1, math.min(count, n or 1)) n = math.max(1, math.min(count, n or 1))
@@ -633,6 +640,7 @@ end
-- like the original's PlayCry -> WaitForSoundToFinish can poll it -- like the original's PlayCry -> WaitForSoundToFinish can poll it
function Sound.playCry(data, species, pikaClip) function Sound.playCry(data, species, pikaClip)
if not love.audio then return nil end if not love.audio then return nil end
if deviceSuspended() then return nil end
-- Yellow voices every Pikachu cry with the PCM clips (the chip cry is -- Yellow voices every Pikachu cry with the PCM clips (the chip cry is
-- never used for the species there). Which clip is a property of the -- never used for the species there). Which clip is a property of the
-- call site in the original -- every caller of PlayPikachuSoundClip sets -- call site in the original -- every caller of PlayPikachuSoundClip sets
+4 -7
View File
@@ -303,15 +303,12 @@ local function coreRows(opts, hooks)
end) end)
end end
-- ORIENTATION (#592): Android only -- the lock rides SDL's orientation -- ORIENTATION (#592, #1638): mobile only. Unlike the other launcher rows
-- hint, which iOS reads only at startup (the Info.plist governs there) and -- this one live-applies: the window exists here too, and rotating under
-- desktop ignores. Unlike the other launcher rows this one live-applies: -- the player's finger is the only feedback that reads.
-- the window exists here too, and rotating under the player's finger is
-- the only feedback that reads.
do do
local osName = love.system and love.system.getOS and love.system.getOS()
local okOr, Orientation = pcall(require, "src.core.Orientation") local okOr, Orientation = pcall(require, "src.core.Orientation")
if okOr and osName == "Android" then if okOr and (Orientation.isAndroid() or Orientation.isIOS()) then
add(Strings("ORIENTATION"), add(Strings("ORIENTATION"),
function() return Strings(Orientation.modeLabel(opts.orientation)) end, function() return Strings(Orientation.modeLabel(opts.orientation)) end,
function(dir) function(dir)
+7 -2
View File
@@ -3017,9 +3017,13 @@ local function buildTextModal(imp, m, key, title, body, closeFn)
local h = math.floor(math.min(m.H - 2 * m.pad, 460 * m.s)) local h = math.floor(math.min(m.H - 2 * m.pad, 460 * m.s))
local px, py, pw, ph = modalPanel(m, w, h) local px, py, pw, ph = modalPanel(m, w, h)
local cy = py + pad local cy = py + pad
Kit.text("button", Kit.ellipsize("button", title, pw - 2 * pad), local xW = math.max(Kit.tapMin(), math.floor(30 * m.s))
Kit.text("button", Kit.ellipsize("button", title,
pw - 2 * pad - xW - math.floor(8 * m.s)),
px + pad, cy, PAL.heading) px + pad, cy, PAL.heading)
cy = cy + Kit.textHeight("button") + math.floor(10 * m.s) btn(imp, px + pw - pad - xW, cy, xW, xW, key .. "-x", "X",
{ font = "small", action = closeFn })
cy = cy + math.max(Kit.textHeight("button"), xW) + math.floor(10 * m.s)
local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s))
local bodyH = (py + ph - pad) - cy - m.btnH - math.floor(10 * m.s) local bodyH = (py + ph - pad) - cy - m.btnH - math.floor(10 * m.s)
@@ -5071,6 +5075,7 @@ function LauncherView.draw(imp)
-- whole stage draws shielded (no clicks, no hover, no focus ring) while -- whole stage draws shielded (no clicks, no hover, no focus ring) while
-- one is up; buildModals lowers the shield for the modal's own controls. -- one is up; buildModals lowers the shield for the modal's own controls.
imp._modalUpNow = modalUp(imp) imp._modalUpNow = modalUp(imp)
if imp._modalUpNow then imp:_blurPanelFields() end
Kit.blockClicks = imp._modalUpNow Kit.blockClicks = imp._modalUpNow
local step = Kit.scrollStep(m.s) local step = Kit.scrollStep(m.s)
+22 -1
View File
@@ -2854,6 +2854,7 @@ function RomImporter:_updatePadCursor(dt)
local overY = 0 local overY = 0
if ny > oy + h then overY = ny - (oy + h) if ny > oy + h then overY = ny - (oy + h)
elseif ny < oy then overY = ny - oy end elseif ny < oy then overY = ny - oy end
if math.abs(ay) > PAD_DEAD and not self._padStickCentered then overY = 0 end
if overY ~= 0 and self._flex then if overY ~= 0 and self._flex then
require("src.import.LauncherView").wheelmoved(self, 0, -overY / 48) require("src.import.LauncherView").wheelmoved(self, 0, -overY / 48)
end end
@@ -2913,7 +2914,11 @@ end
function RomImporter:gamepadaxis(_, axis, value) function RomImporter:gamepadaxis(_, axis, value)
if axis == "leftx" or axis == "lefty" or axis == "righty" then if axis == "leftx" or axis == "lefty" or axis == "righty" then
self._padAxis[axis] = value self._padAxis[axis] = value
if math.abs(value) > PAD_DEAD then self:_activatePadCursor() end if math.abs(value) > PAD_DEAD then
self:_activatePadCursor()
elseif axis == "lefty" then
self._padStickCentered = true
end
end end
end end
@@ -2922,18 +2927,21 @@ end
-- fire the virtual cursor's click twice off one A press (#620). -- fire the virtual cursor's click twice off one A press (#620).
function RomImporter:joystickpressed(joystick, button) function RomImporter:joystickpressed(joystick, button)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
local padButton = GamepadMap.mapRawToGamepadButton(button) local padButton = GamepadMap.mapRawToGamepadButton(button)
if padButton then self:gamepadpressed(joystick, padButton) end if padButton then self:gamepadpressed(joystick, padButton) end
end end
function RomImporter:joystickreleased(joystick, button) function RomImporter:joystickreleased(joystick, button)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
local padButton = GamepadMap.mapRawToGamepadButton(button) local padButton = GamepadMap.mapRawToGamepadButton(button)
if padButton then self:gamepadreleased(joystick, padButton) end if padButton then self:gamepadreleased(joystick, padButton) end
end end
function RomImporter:joystickaxis(joystick, axis, value) function RomImporter:joystickaxis(joystick, axis, value)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
if axis == 1 then if axis == 1 then
self:gamepadaxis(joystick, "leftx", value) self:gamepadaxis(joystick, "leftx", value)
elseif axis == 2 then elseif axis == 2 then
@@ -2943,6 +2951,7 @@ end
function RomImporter:joystickhat(joystick, hat, direction) function RomImporter:joystickhat(joystick, hat, direction)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
for _, dir in ipairs(self._rawHatDirs[hat] or {}) do for _, dir in ipairs(self._rawHatDirs[hat] or {}) do
self._padDir[dir] = nil self._padDir[dir] = nil
end end
@@ -4003,6 +4012,18 @@ function RomImporter:_disarmTextInput()
end end
end end
function RomImporter:_blurPanelFields()
if not (self._findSearchFocus or self._skinUrlFocus) then return end
if self._indexPrompt or self._rename or self._settingsText
or self._profileSavePrompt or self._profileRenamePrompt
or (self._syncModal and self._syncFocus) then
return
end
self._findSearchFocus = false
self._skinUrlFocus = false
self:_disarmTextInput()
end
function RomImporter:_beginRename(version, id) function RomImporter:_beginRename(version, id)
local label local label
for _, slot in ipairs(self.slots[version] or {}) do for _, slot in ipairs(self.slots[version] or {}) do
+2 -2
View File
@@ -580,8 +580,8 @@ local function buildRows(game)
end end
rows = filtered rows = filtered
end end
-- ORIENTATION only on Android, the one platform Orientation.apply reaches. -- ORIENTATION only on the platforms Orientation.apply reaches (#1638).
if not Orientation.isAndroid() then if not (Orientation.isAndroid() or Orientation.isIOS()) then
local filtered = {} local filtered = {}
for _, row in ipairs(rows) do for _, row in ipairs(rows) do
if row.id ~= "orientation" then filtered[#filtered + 1] = row end if row.id ~= "orientation" then filtered[#filtered + 1] = row end
+4
View File
@@ -151,6 +151,7 @@ end
function PadCursor.joystickpressed(joystick, button) function PadCursor.joystickpressed(joystick, button)
if GamepadMap.ignoreRawForJoystick(joystick) then return nil end if GamepadMap.ignoreRawForJoystick(joystick) then return nil end
if GamepadMap.isAccelerometer(joystick) then return nil end
local padButton = GamepadMap.mapRawToGamepadButton(button) local padButton = GamepadMap.mapRawToGamepadButton(button)
if padButton then return PadCursor.gamepadpressed(joystick, padButton) end if padButton then return PadCursor.gamepadpressed(joystick, padButton) end
return nil return nil
@@ -158,12 +159,14 @@ end
function PadCursor.joystickreleased(joystick, button) function PadCursor.joystickreleased(joystick, button)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
local padButton = GamepadMap.mapRawToGamepadButton(button) local padButton = GamepadMap.mapRawToGamepadButton(button)
if padButton then PadCursor.gamepadreleased(joystick, padButton) end if padButton then PadCursor.gamepadreleased(joystick, padButton) end
end end
function PadCursor.joystickaxis(joystick, axisIndex, value) function PadCursor.joystickaxis(joystick, axisIndex, value)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
if axisIndex == 1 then if axisIndex == 1 then
PadCursor.gamepadaxis(joystick, "leftx", value) PadCursor.gamepadaxis(joystick, "leftx", value)
elseif axisIndex == 2 then elseif axisIndex == 2 then
@@ -173,6 +176,7 @@ end
function PadCursor.joystickhat(joystick, hat, direction) function PadCursor.joystickhat(joystick, hat, direction)
if GamepadMap.ignoreRawForJoystick(joystick) then return end if GamepadMap.ignoreRawForJoystick(joystick) then return end
if GamepadMap.isAccelerometer(joystick) then return end
for _, d in ipairs(rawHatDirs[hat] or {}) do for _, d in ipairs(rawHatDirs[hat] or {}) do
dir[d] = nil dir[d] = nil
end end
+14 -2
View File
@@ -252,6 +252,8 @@ function EvolutionAnim:setPhase(phase)
full = self.full, full = self.full,
}) })
end end
local stack = self.game and self.game.stack
if stack and stack.top and stack:top() == self then stack:pop() end
return return
end end
end end
@@ -321,8 +323,18 @@ end
function EvolutionAnim:update(_dt) function EvolutionAnim:update(_dt)
local input = self.game and self.game.input local input = self.game and self.game.input
local phase = self.phase local phase = self.phase
-- onDone has already fired; the caller pops this state on its own beat. -- onDone has already fired.
if phase == "done" then return end if phase == "done" then
local stack = self.game and self.game.stack
if stack and stack.top and stack:top() == self then stack:pop() end
return
end
if phase == "waitingLearn" then
local stack = self.game and self.game.stack
if stack and stack.top and stack:top() == self then self:nextLearn() end
return
end
if phase == "flash" then if phase == "flash" then
return self:updateFlash(input) return self:updateFlash(input)
+7 -1
View File
@@ -291,9 +291,15 @@ eq(edgeImp._pageScrollMax, 0, "with no page scroll left to catch the notch")
check(ereg.y + ereg.h < 720, "and a region that ends above the safe area") check(ereg.y + ereg.h < 720, "and a region that ends above the safe area")
edgeImp._padCursorActive = true edgeImp._padCursorActive = true
edgeImp._padCursor = { x = ereg.x + 20, y = 719 } edgeImp._padCursor = { x = ereg.x + 20, y = 719 }
edgeImp._padAxis = { lefty = 1 }
edgeImp._padDir = {} edgeImp._padDir = {}
pointer(ereg.x + 20, 719) pointer(ereg.x + 20, 719)
edgeImp:gamepadaxis(nil, "lefty", 1)
edgeImp:_updatePadCursor(0.5)
eq(edgeImp._wheelY or 0, 0,
"an axis that has never read under the deadzone cannot edge-scroll")
edgeImp._padCursor = { x = ereg.x + 20, y = 719 }
edgeImp:gamepadaxis(nil, "lefty", 0)
edgeImp:gamepadaxis(nil, "lefty", 1)
edgeImp:_updatePadCursor(0.5) edgeImp:_updatePadCursor(0.5)
check((edgeImp._wheelY or 0) < 0, "the edge push synthesizes a notch") check((edgeImp._wheelY or 0) < 0, "the edge push synthesizes a notch")
LauncherView.draw(edgeImp) LauncherView.draw(edgeImp)
+5 -1
View File
@@ -55,8 +55,12 @@ end
love.system.getOS = function() return "OS X" end love.system.getOS = function() return "OS X" end
T.eq(Orientation.apply("portrait"), false, "desktop apply is a refused no-op") T.eq(Orientation.apply("portrait"), false, "desktop apply is a refused no-op")
-- iOS posed but no SDL loaded: the FFI path must fail closed, not claim the
-- lock took.
love.system.getOS = function() return "iOS" end love.system.getOS = function() return "iOS" end
T.eq(Orientation.apply("portrait"), false, "iOS defers to the Info.plist") T.eq(Orientation.apply("portrait"), false, "iOS apply fails closed with no SDL")
local okIOS = pcall(Orientation.applyOptions, { orientation = "portrait" })
T.eq(okIOS, true, "posed-iOS apply never raises")
-- Android posed but no SDL loaded in this process: the FFI path must fail -- Android posed but no SDL loaded in this process: the FFI path must fail
-- closed inside its pcall, never throw. -- closed inside its pcall, never throw.
love.system.getOS = function() return "Android" end love.system.getOS = function() return "Android" end