mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
8bf99c3318
Route gamepad/touch into the editor instead of dropping them in editorMode, and hit-test clicks at event coords so a finger tap is not lost under the Joy-Con virtual cursor. Co-authored-by: Cursor <cursoragent@cursor.com>
216 lines
6.8 KiB
Lua
216 lines
6.8 KiB
Lua
-- Virtual pointer for the save editor on Switch / handhelds / any gamepad.
|
|
-- Mirrors the launcher's RomImporter pad cursor (speeds, deadzone, dual-path
|
|
-- raw gate) without sharing that module -- keeps RomImporter risk-free.
|
|
--
|
|
-- Stick / D-pad move; real mouse motion yields so desktop stays normal.
|
|
-- Callers (App.lua) map A → click, B → close, shoulders → tabs, right stick
|
|
-- → wheel notches.
|
|
|
|
local SafeArea = require("src.core.SafeArea")
|
|
local GamepadMap = require("src.core.GamepadMap")
|
|
|
|
local PAD_DEAD = 0.28
|
|
local PAD_SPEED = 560
|
|
local PAD_DPAD_SPEED = 420
|
|
-- Right stick → Kit wheel notches: ~2 notches/sec at full deflection so lists
|
|
-- scroll at a usable pace without flooding one frame.
|
|
local PAD_WHEEL_RATE = 2.0
|
|
|
|
local PadInput = {}
|
|
|
|
local cursor = { x = 0, y = 0 }
|
|
local active = false
|
|
local inited = false
|
|
local axis = { leftx = 0, lefty = 0, righty = 0 }
|
|
local dir = {}
|
|
local rawHatDirs = {}
|
|
local lastMouseX, lastMouseY
|
|
local wheelAcc = 0
|
|
|
|
local function activate()
|
|
if active then return end
|
|
local ox, oy, w, h = SafeArea.rect()
|
|
if not inited then
|
|
cursor.x = ox + w * 0.5
|
|
cursor.y = oy + h * 0.45
|
|
inited = true
|
|
end
|
|
active = true
|
|
end
|
|
|
|
function PadInput.reset()
|
|
cursor.x, cursor.y = 0, 0
|
|
active = false
|
|
inited = false
|
|
axis.leftx, axis.lefty, axis.righty = 0, 0, 0
|
|
for k in pairs(dir) do dir[k] = nil end
|
|
for k in pairs(rawHatDirs) do rawHatDirs[k] = nil end
|
|
lastMouseX, lastMouseY = nil, nil
|
|
wheelAcc = 0
|
|
end
|
|
|
|
-- Touch / mouse press: drop the virtual cursor for this interaction so a tap
|
|
-- is not swallowed by the Joy-Con pointer sitting elsewhere on screen.
|
|
function PadInput.yieldToPointer()
|
|
active = false
|
|
end
|
|
|
|
-- Returns mx, my, isActive. When inactive the caller should use the system
|
|
-- mouse; when active these coords feed Kit.beginFrame.
|
|
function PadInput.pointer()
|
|
return cursor.x, cursor.y, active
|
|
end
|
|
|
|
function PadInput.isActive()
|
|
return active
|
|
end
|
|
|
|
-- Consume accumulated right-stick scroll as integer wheel notches (same
|
|
-- units App.wheelmoved feeds Kit). Fractional remainder stays for next frame.
|
|
function PadInput.takeWheel()
|
|
local notches = 0
|
|
if wheelAcc >= 1 or wheelAcc <= -1 then
|
|
notches = wheelAcc > 0 and math.floor(wheelAcc) or math.ceil(wheelAcc)
|
|
wheelAcc = wheelAcc - notches
|
|
end
|
|
return notches
|
|
end
|
|
|
|
function PadInput.update(dt)
|
|
if not (love and love.mouse and love.mouse.getPosition) then return end
|
|
local mx, my = love.mouse.getPosition()
|
|
if lastMouseX and active then
|
|
if math.abs(mx - lastMouseX) > 3 or math.abs(my - lastMouseY) > 3 then
|
|
active = false
|
|
end
|
|
end
|
|
lastMouseX, lastMouseY = mx, my
|
|
|
|
local ax = axis.leftx or 0
|
|
local ay = axis.lefty or 0
|
|
local dx, dy = 0, 0
|
|
if math.abs(ax) > PAD_DEAD then dx = dx + ax end
|
|
if math.abs(ay) > PAD_DEAD then dy = dy + ay end
|
|
if dir.dpleft then dx = dx - 1 end
|
|
if dir.dpright then dx = dx + 1 end
|
|
if dir.dpup then dy = dy - 1 end
|
|
if dir.dpdown then dy = dy + 1 end
|
|
|
|
if dx ~= 0 or dy ~= 0 then
|
|
activate()
|
|
local mag = math.sqrt(dx * dx + dy * dy)
|
|
if mag > 1 then dx, dy = dx / mag, dy / mag end
|
|
local speed = (math.abs(ax) > PAD_DEAD or math.abs(ay) > PAD_DEAD)
|
|
and PAD_SPEED or PAD_DPAD_SPEED
|
|
local ox, oy, w, h = SafeArea.rect()
|
|
local nx = cursor.x + dx * speed * dt
|
|
local ny = cursor.y + dy * speed * dt
|
|
cursor.x = math.max(ox, math.min(ox + w, nx))
|
|
cursor.y = math.max(oy, math.min(oy + h, ny))
|
|
end
|
|
|
|
local ry = axis.righty or 0
|
|
if math.abs(ry) > PAD_DEAD then
|
|
activate()
|
|
-- Negative righty (stick up) scrolls lists up = positive wheel notches.
|
|
wheelAcc = wheelAcc + (-ry) * PAD_WHEEL_RATE * dt
|
|
end
|
|
end
|
|
|
|
-- Returns a string action the App layer handles:
|
|
-- "a" | "b" | "tab_prev" | "tab_next" | nil
|
|
function PadInput.gamepadpressed(_, button)
|
|
activate()
|
|
local action = GamepadMap.mapGamepadButton(button)
|
|
if action == "a" or action == "b" then
|
|
return action
|
|
elseif button == "leftshoulder" then
|
|
return "tab_prev"
|
|
elseif button == "rightshoulder" then
|
|
return "tab_next"
|
|
elseif button == "dpup" or button == "dpdown"
|
|
or button == "dpleft" or button == "dpright" then
|
|
dir[button] = true
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function PadInput.gamepadreleased(_, button)
|
|
if button == "dpup" or button == "dpdown"
|
|
or button == "dpleft" or button == "dpright" then
|
|
dir[button] = nil
|
|
end
|
|
end
|
|
|
|
function PadInput.gamepadaxis(_, axisName, value)
|
|
if axisName == "leftx" or axisName == "lefty" or axisName == "righty" then
|
|
axis[axisName] = value
|
|
if math.abs(value) > PAD_DEAD then activate() end
|
|
end
|
|
end
|
|
|
|
function PadInput.joystickpressed(joystick, button)
|
|
if GamepadMap.ignoreRawForJoystick(joystick) then return nil end
|
|
local padButton = GamepadMap.mapRawToGamepadButton(button)
|
|
if padButton then return PadInput.gamepadpressed(joystick, padButton) end
|
|
return nil
|
|
end
|
|
|
|
function PadInput.joystickreleased(joystick, button)
|
|
if GamepadMap.ignoreRawForJoystick(joystick) then return end
|
|
local padButton = GamepadMap.mapRawToGamepadButton(button)
|
|
if padButton then PadInput.gamepadreleased(joystick, padButton) end
|
|
end
|
|
|
|
function PadInput.joystickaxis(joystick, axisIndex, value)
|
|
if GamepadMap.ignoreRawForJoystick(joystick) then return end
|
|
if axisIndex == 1 then
|
|
PadInput.gamepadaxis(joystick, "leftx", value)
|
|
elseif axisIndex == 2 then
|
|
PadInput.gamepadaxis(joystick, "lefty", value)
|
|
end
|
|
end
|
|
|
|
function PadInput.joystickhat(joystick, hat, direction)
|
|
if GamepadMap.ignoreRawForJoystick(joystick) then return end
|
|
for _, d in ipairs(rawHatDirs[hat] or {}) do
|
|
dir[d] = nil
|
|
end
|
|
local dirs = ({
|
|
u = { "dpup" }, d = { "dpdown" }, l = { "dpleft" }, r = { "dpright" },
|
|
lu = { "dpleft", "dpup" }, ru = { "dpright", "dpup" },
|
|
ld = { "dpleft", "dpdown" }, rd = { "dpright", "dpdown" },
|
|
})[direction] or {}
|
|
for _, d in ipairs(dirs) do dir[d] = true end
|
|
rawHatDirs[hat] = dirs
|
|
if #dirs > 0 then activate() end
|
|
end
|
|
|
|
function PadInput.draw()
|
|
if not active then return end
|
|
if not (love and love.graphics) then return end
|
|
local x, y = cursor.x, cursor.y
|
|
love.graphics.push("all")
|
|
if love.graphics.origin then love.graphics.origin() end
|
|
if love.graphics.setLineWidth then love.graphics.setLineWidth(1) end
|
|
love.graphics.setColor(0, 0, 0, 0.45)
|
|
if love.graphics.polygon then
|
|
love.graphics.polygon("fill",
|
|
x + 2, y + 2, x + 2, y + 22, x + 8, y + 16, x + 14, y + 26,
|
|
x + 18, y + 24, x + 11, y + 14, x + 20, y + 14)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.polygon("fill",
|
|
x, y, x, y + 20, x + 6, y + 14, x + 12, y + 24,
|
|
x + 16, y + 22, x + 9, y + 12, x + 18, y + 12)
|
|
love.graphics.setColor(0.05, 0.07, 0.12, 1)
|
|
love.graphics.polygon("line",
|
|
x, y, x, y + 20, x + 6, y + 14, x + 12, y + 24,
|
|
x + 16, y + 22, x + 9, y + 12, x + 18, y + 12)
|
|
else
|
|
love.graphics.rectangle("fill", x, y, 12, 18)
|
|
end
|
|
love.graphics.pop()
|
|
end
|
|
|
|
return PadInput
|