mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-27 08:51:27 +02:00
feat(ui): add native controller menu navigation with Y hotkey toggle and soft-glow focus
This commit is contained in:
@@ -6011,6 +6011,25 @@ function LauncherView.draw(imp)
|
|||||||
|
|
||||||
Kit.endFrame()
|
Kit.endFrame()
|
||||||
drawPadCursor(imp)
|
drawPadCursor(imp)
|
||||||
|
|
||||||
|
if imp._cursorModeToast and imp._cursorModeToastTime then
|
||||||
|
local elapsed = love.timer.getTime() - imp._cursorModeToastTime
|
||||||
|
if elapsed < 2.2 then
|
||||||
|
local alpha = 1.0
|
||||||
|
if elapsed > 1.7 then alpha = math.max(0, (2.2 - elapsed) / 0.5) end
|
||||||
|
local msg = imp._cursorModeToast
|
||||||
|
local tw = Kit.textWidth("small", msg) + 36 * m.s
|
||||||
|
local th = 34 * m.s
|
||||||
|
local tx = (m.W - tw) / 2
|
||||||
|
local ty = 16 * m.s
|
||||||
|
Theme.fillRounded(tx, ty, tw, th, PAL.surface, 0.95 * alpha, 6)
|
||||||
|
Theme.strokeRounded(tx - 2, ty - 2, tw + 4, th + 4, PAL.railBlue, 0.35 * alpha, 2, 8)
|
||||||
|
Theme.strokeRounded(tx, ty, tw, th, PAL.lineStrong, 0.85 * alpha, 1.5, 6)
|
||||||
|
Kit.textCenterBold("small", msg, tx, ty + 8 * m.s, tw, PAL.heading)
|
||||||
|
else
|
||||||
|
imp._cursorModeToast = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return LauncherView
|
return LauncherView
|
||||||
|
|||||||
+86
-52
@@ -3128,6 +3128,23 @@ function RomImporter:_updatePadCursor(dt)
|
|||||||
|
|
||||||
local ax = self._padAxis.leftx or 0
|
local ax = self._padAxis.leftx or 0
|
||||||
local ay = self._padAxis.lefty or 0
|
local ay = self._padAxis.lefty or 0
|
||||||
|
|
||||||
|
if not self._padCursorActive then
|
||||||
|
-- Stick navigation flick in native controller mode
|
||||||
|
if math.abs(ax) > 0.55 or math.abs(ay) > 0.55 then
|
||||||
|
if not self._stickFlicked then
|
||||||
|
if ay < -0.55 then Kit.navigate("up"); self._stickFlicked = true
|
||||||
|
elseif ay > 0.55 then Kit.navigate("down"); self._stickFlicked = true
|
||||||
|
elseif ax < -0.55 then Kit.navigate("left"); self._stickFlicked = true
|
||||||
|
elseif ax > 0.55 then Kit.navigate("right"); self._stickFlicked = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif math.abs(ax) < 0.3 and math.abs(ay) < 0.3 then
|
||||||
|
self._stickFlicked = false
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local dx, dy = 0, 0
|
local dx, dy = 0, 0
|
||||||
if math.abs(ax) > PAD_DEAD then dx = dx + ax end
|
if math.abs(ax) > PAD_DEAD then dx = dx + ax end
|
||||||
if math.abs(ay) > PAD_DEAD then dy = dy + ay end
|
if math.abs(ay) > PAD_DEAD then dy = dy + ay end
|
||||||
@@ -3147,14 +3164,6 @@ function RomImporter:_updatePadCursor(dt)
|
|||||||
local ny = self._padCursor.y + dy * speed * dt
|
local ny = self._padCursor.y + dy * speed * dt
|
||||||
self._padCursor.x = math.max(ox, math.min(ox + w, nx))
|
self._padCursor.x = math.max(ox, math.min(ox + w, nx))
|
||||||
self._padCursor.y = math.max(oy, math.min(oy + h, ny))
|
self._padCursor.y = math.max(oy, math.min(oy + h, ny))
|
||||||
-- Pushing INTO the top/bottom edge scrolls the page instead of stalling.
|
|
||||||
-- The cursor is clamped to the safe area above, so on a short window the
|
|
||||||
-- rows below the fold are unreachable on a stickless handheld: no mouse
|
|
||||||
-- wheel, no touchscreen, and no right stick to feed the existing wheel
|
|
||||||
-- path. Only the OVERSHOOT scrolls -- parking the cursor at the edge does
|
|
||||||
-- nothing, it has to be actively pushed -- and this block only runs on pad
|
|
||||||
-- input, so a real mouse is unaffected. /48 matches the pixels-per-notch
|
|
||||||
-- LauncherView.draw multiplies back out.
|
|
||||||
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
|
||||||
@@ -3162,17 +3171,12 @@ function RomImporter:_updatePadCursor(dt)
|
|||||||
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
|
||||||
-- Desktop: FlexLove polls the real mouse, so warp it with the pad pointer.
|
|
||||||
-- NX: the getPosition bridge already returns pad coords -- skip setPosition.
|
|
||||||
if not self.isNX and love.mouse.setPosition then
|
if not self.isNX and love.mouse.setPosition then
|
||||||
pcall(love.mouse.setPosition, self._padCursor.x, self._padCursor.y)
|
pcall(love.mouse.setPosition, self._padCursor.x, self._padCursor.y)
|
||||||
self._lastMouseX, self._lastMouseY = self._padCursor.x, self._padCursor.y
|
self._lastMouseX, self._lastMouseY = self._padCursor.x, self._padCursor.y
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Right stick scrolls whatever the pad pointer sits over, through the
|
|
||||||
-- view's wheel path, so the page and the modal scrollers all behave like a
|
|
||||||
-- mouse wheel would.
|
|
||||||
local ry = self._padAxis.righty or 0
|
local ry = self._padAxis.righty or 0
|
||||||
if math.abs(ry) > PAD_DEAD and self._flex then
|
if math.abs(ry) > PAD_DEAD and self._flex then
|
||||||
self:_activatePadCursor()
|
self:_activatePadCursor()
|
||||||
@@ -3191,45 +3195,18 @@ function RomImporter:gamepadpressed(_, button)
|
|||||||
if Kit.FileBrowser.gamepadpressed(action) then return end
|
if Kit.FileBrowser.gamepadpressed(action) then return end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:_activatePadCursor()
|
|
||||||
if action == "a" then
|
-- Y button toggle between Native Controller Navigation and Virtual Pointer Cursor:
|
||||||
-- Instant click at the virtual pointer: dispatched straight into the
|
if action == "y" or button == "y" or button == "x" then
|
||||||
-- view, since the launcher no longer hit-tests presses itself.
|
self._padCursorActive = not self._padCursorActive
|
||||||
if self._flex then
|
if okKit then Kit._ringShown = not self._padCursorActive end
|
||||||
require("src.import.LauncherView").clickAt(self,
|
self._cursorModeToast = self._padCursorActive and "Cursor Navigation [Y]" or "Controller Menu Navigation [Y]"
|
||||||
self._padCursor.x, self._padCursor.y)
|
self._cursorModeToastTime = love.timer.getTime()
|
||||||
end
|
|
||||||
elseif action == "b" then
|
|
||||||
-- Cancel / dismiss active prompts
|
|
||||||
if self._indexPrompt then
|
|
||||||
self._indexPrompt = nil
|
|
||||||
self:_disarmTextInput()
|
|
||||||
return
|
|
||||||
elseif self._rename then
|
|
||||||
self._rename = nil
|
|
||||||
self:_disarmTextInput()
|
|
||||||
return
|
|
||||||
elseif self._profileRenamePrompt then
|
|
||||||
self._profileRenamePrompt = nil
|
|
||||||
self:_disarmTextInput()
|
|
||||||
return
|
|
||||||
elseif self._profileSavePrompt then
|
|
||||||
self._profileSavePrompt = nil
|
|
||||||
self:_disarmTextInput()
|
|
||||||
return
|
|
||||||
elseif self._settingsText then
|
|
||||||
self._settingsText = nil
|
|
||||||
self:_disarmTextInput()
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
elseif button == "leftshoulder" then
|
|
||||||
self:_cycleTab(-1)
|
-- Select button: toggle Virtual Keyboard
|
||||||
elseif button == "rightshoulder" then
|
if button == "back" or button == "select" or action == "select" then
|
||||||
self:_cycleTab(1)
|
|
||||||
elseif button == "dpup" or button == "dpdown"
|
|
||||||
or button == "dpleft" or button == "dpright" then
|
|
||||||
self._padDir[button] = true
|
|
||||||
elseif button == "back" or button == "select" or action == "select" then
|
|
||||||
if okKit and Kit.VirtualKeyboard then
|
if okKit and Kit.VirtualKeyboard then
|
||||||
if Kit.VirtualKeyboard.active then
|
if Kit.VirtualKeyboard.active then
|
||||||
Kit.VirtualKeyboard.close(false)
|
Kit.VirtualKeyboard.close(false)
|
||||||
@@ -3301,13 +3278,70 @@ function RomImporter:gamepadpressed(_, button)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif button == "start" then
|
end
|
||||||
-- Start: Play if ready, else Choose ROM on the active game tab.
|
|
||||||
|
-- Shoulder buttons: cycle tabs
|
||||||
|
if button == "leftshoulder" then
|
||||||
|
self:_cycleTab(-1)
|
||||||
|
return
|
||||||
|
elseif button == "rightshoulder" then
|
||||||
|
self:_cycleTab(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Start button: Play / Choose ROM
|
||||||
|
if button == "start" then
|
||||||
if self.workState == "working" then return end
|
if self.workState == "working" then return end
|
||||||
local version = self.tab
|
local version = self.tab
|
||||||
if GameVersion.VERSIONS[version] then
|
if GameVersion.VERSIONS[version] then
|
||||||
if self.ready[version] then self:play(version) else self:_romAction(version) end
|
if self.ready[version] then self:play(version) else self:_romAction(version) end
|
||||||
end
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self._padCursorActive then
|
||||||
|
if okKit then Kit._ringShown = true end
|
||||||
|
if action == "a" then
|
||||||
|
if okKit then Kit.activateFocused() end
|
||||||
|
return
|
||||||
|
elseif action == "b" then
|
||||||
|
if self._indexPrompt then self._indexPrompt = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._rename then self._rename = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._profileRenamePrompt then self._profileRenamePrompt = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._profileSavePrompt then self._profileSavePrompt = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._settingsText then self._settingsText = nil; self:_disarmTextInput(); return
|
||||||
|
end
|
||||||
|
elseif button == "dpup" or action == "dpup" then
|
||||||
|
if okKit then Kit.navigate("up") end
|
||||||
|
return
|
||||||
|
elseif button == "dpdown" or action == "dpdown" then
|
||||||
|
if okKit then Kit.navigate("down") end
|
||||||
|
return
|
||||||
|
elseif button == "dpleft" or action == "dpleft" then
|
||||||
|
if okKit then Kit.navigate("left") end
|
||||||
|
return
|
||||||
|
elseif button == "dpright" or action == "dpright" then
|
||||||
|
if okKit then Kit.navigate("right") end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:_activatePadCursor()
|
||||||
|
if action == "a" then
|
||||||
|
if self._flex then
|
||||||
|
require("src.import.LauncherView").clickAt(self,
|
||||||
|
self._padCursor.x, self._padCursor.y)
|
||||||
|
end
|
||||||
|
elseif action == "b" then
|
||||||
|
if self._indexPrompt then self._indexPrompt = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._rename then self._rename = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._profileRenamePrompt then self._profileRenamePrompt = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._profileSavePrompt then self._profileSavePrompt = nil; self:_disarmTextInput(); return
|
||||||
|
elseif self._settingsText then self._settingsText = nil; self:_disarmTextInput(); return
|
||||||
|
end
|
||||||
|
elseif button == "dpup" or button == "dpdown"
|
||||||
|
or button == "dpleft" or button == "dpright" then
|
||||||
|
self._padDir[button] = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+12
-3
@@ -568,8 +568,11 @@ function Kit.row(x, y, w, h, selected, id)
|
|||||||
-- The focus ring is a second inset outline, so it reads on both a black
|
-- The focus ring is a second inset outline, so it reads on both a black
|
||||||
-- row and a white selected one.
|
-- row and a white selected one.
|
||||||
if focused then
|
if focused then
|
||||||
Theme.strokeRounded(x + 2, y + 2, w - 4, h - 4,
|
local glowPulse = 0.5 + 0.5 * math.sin(Kit.time * 4)
|
||||||
selected and PAL.inverse or PAL.lineStrong, Theme.A.focus, 1,
|
Theme.strokeRounded(x - 2, y - 2, w + 4, h + 4,
|
||||||
|
PAL.railBlue, 0.35 + 0.25 * glowPulse, 2, Theme.radius() + 2)
|
||||||
|
Theme.strokeRounded(x, y, w, h,
|
||||||
|
selected and PAL.inverse or PAL.lineStrong, 0.90 + 0.10 * glowPulse, 1.5,
|
||||||
Theme.radius())
|
Theme.radius())
|
||||||
end
|
end
|
||||||
local clicked = Kit.press(x, y, w, h)
|
local clicked = Kit.press(x, y, w, h)
|
||||||
@@ -688,9 +691,15 @@ function Kit.button(x, y, w, h, label, opts)
|
|||||||
Theme.strokeRounded(x, y, w, h, stroke, strokeA, 1, radius)
|
Theme.strokeRounded(x, y, w, h, stroke, strokeA, 1, radius)
|
||||||
end
|
end
|
||||||
if doRing then
|
if doRing then
|
||||||
|
local glowPulse = 0.5 + 0.5 * math.sin(Kit.time * 4)
|
||||||
|
-- Outer soft-glow highlight aura
|
||||||
|
Theme.strokeRounded(x - B.ringPad - 2, y - B.ringPad - 2,
|
||||||
|
w + 2 * (B.ringPad + 2), h + 2 * (B.ringPad + 2), PAL.railBlue,
|
||||||
|
0.30 + 0.25 * glowPulse, 2, radius + B.ringPad + 2)
|
||||||
|
-- Primary crisp focus ring
|
||||||
Theme.strokeRounded(x - B.ringPad, y - B.ringPad,
|
Theme.strokeRounded(x - B.ringPad, y - B.ringPad,
|
||||||
w + 2 * B.ringPad, h + 2 * B.ringPad, PAL.lineStrong,
|
w + 2 * B.ringPad, h + 2 * B.ringPad, PAL.lineStrong,
|
||||||
Theme.A.focus, B.ringWidth, radius + B.ringPad)
|
0.90 + 0.10 * glowPulse, B.ringWidth, radius + B.ringPad)
|
||||||
elseif glowA then
|
elseif glowA then
|
||||||
Theme.strokeRounded(x - B.ringPad, y - B.ringPad,
|
Theme.strokeRounded(x - B.ringPad, y - B.ringPad,
|
||||||
w + 2 * B.ringPad, h + 2 * B.ringPad, PAL.lineStrong,
|
w + 2 * B.ringPad, h + 2 * B.ringPad, PAL.lineStrong,
|
||||||
|
|||||||
Reference in New Issue
Block a user