skins fixerino

This commit is contained in:
bryanthaboi
2026-08-21 14:03:25 -04:00
parent 5c1837b1eb
commit b0ff1552f2
23 changed files with 1065 additions and 127 deletions
@@ -45,6 +45,15 @@ for _, version in ipairs({ "gold", "silver" }) do
check(has(model, "VIBRATION"), version .. " and VIBRATION")
check(has(model, "TOUCH CONTROLS"), version .. " and the layout editor")
check(has(model, "VOID FILL"), version .. " and VOID FILL")
check(has(model, "ZOOM"), version .. " and ZOOM")
local zoom = findRow(model, "ZOOM")
eq(zoom.value(), "FIT", version .. " ZOOM defaults to FIT")
zoom.step(-1)
eq(model.opts.gold.zoom, -1, version .. " left stores OUT1 in the gold block")
eq(zoom.value(), "OUT1", version .. " and the row reads OUT1")
zoom.step(1)
eq(model.opts.gold.zoom, 0, version .. " right restores FIT")
local voidFill = findRow(model, "VOID FILL")
eq(voidFill.value(), "FADE ", version .. " VOID FILL defaults to FADE")
+23 -2
View File
@@ -86,6 +86,16 @@ local function skinLauncher(count)
end
window(360, 780)
local compact = skinLauncher(12)
LauncherView.draw(compact)
LauncherView.draw(compact)
eq(compact._tabScrollMax.skins or 0, 0,
"the compact Skins landing panel never scrolls through installed skins")
-- Historical assertions for the old installed-skins list. Skin browsing now
-- lives exclusively in My Skins, so this launcher panel intentionally has no
-- list/pager region to exercise.
if false then
local imp = skinLauncher(12)
LauncherView.draw(imp)
LauncherView.draw(imp)
@@ -139,6 +149,7 @@ LauncherView.draw(imp)
LauncherView.draw(imp)
eq(imp._tabScrollMax.skins, 0, "a panel that now fits has no travel")
eq(imp._tabScroll.skins, 0, "and its offset comes back with it")
end
local mods = {}
for i = 1, 60 do
@@ -207,6 +218,7 @@ gameImp._wheelY = -1
LauncherView.draw(gameImp)
check((gameImp._tabScroll.red or 0) > 0, "and a notch over it moves it")
if false then
window(360, 780)
local touchImp = skinLauncher(12)
LauncherView.draw(touchImp)
@@ -223,6 +235,7 @@ LauncherView.touchmoved(touchImp, 1, treg.x + 20, treg.y + 40 - 200 - tmax * 2)
eq(touchImp._tabScroll.skins, tmax, "a longer drag reaches the panel's bottom")
check((touchImp._pageScroll or 0) > 0, "and spills into the page from there")
LauncherView.touchreleased(touchImp, 1, treg.x + 20, treg.y - 400)
end
window(360, 780)
local dragMods = RomImporter.new(function() end, { launcher = true })
@@ -244,6 +257,7 @@ eq(dragMods._tabScroll.mods, dRegionMax, "carrying on saturates the region")
check((dragMods._pageScroll or 0) > 0, "and only then reaches the page")
LauncherView.touchreleased(dragMods, 7, dreg.x + 20, dreg.y - 900)
if false then
dragMods._skins = { { id = "s1", source = "user", controls = 8, pages = 1 } }
for i = 2, 12 do
dragMods._skins[i] = { id = "s" .. i, source = "user", controls = 8, pages = 1 }
@@ -266,7 +280,9 @@ eq(dragMods._pages.mods or 1, heldModsPage,
eq(dragMods._tabScroll.mods, heldModsAt,
"with its region offset held for the return trip")
LauncherView.touchreleased(dragMods, 9, sreg.x + 20, sreg.y - 400)
end
if false then
love.graphics.polygon = love.graphics.polygon or function() end
window(360, 780)
local padImp = skinLauncher(12)
@@ -305,6 +321,7 @@ check((edgeImp._wheelY or 0) < 0, "the edge push synthesizes a notch")
LauncherView.draw(edgeImp)
check((edgeImp._tabScroll.skins or 0) > 0,
"which reaches the tab region even though the cursor is below it")
end
Kit.blockClicks = false
Kit._clipRect = nil
@@ -330,6 +347,7 @@ eq(Kit.dragAccum, 200, "and its travel stays queued")
Kit.dragEnd()
eq(Kit.dragAccum, 0, "releasing the button retires the gesture")
if false then
window(360, 780)
local mouseImp = skinLauncher(12)
LauncherView.draw(mouseImp)
@@ -371,20 +389,23 @@ LauncherView.update(mouseImp, 0.016)
check(mouseImp._clickPt ~= nil,
"press and release without travel is still a tap, on release")
mouseImp._clickPt = nil
end
LauncherView.draw(gameImp)
LauncherView.draw(gameImp)
check((gameImp._noDragN or 0) > 0, "the game tab publishes its cartridge rect")
local cart = gameImp._noDragRects[1]
local gameMouseDown = false
love.mouse.isDown = function() return gameMouseDown end
pointer(cart.x + cart.w / 2, cart.y + cart.h / 2)
mdown = true
gameMouseDown = true
LauncherView.update(gameImp, 0.016)
check(gameImp._clickPt ~= nil,
"a press on the cartridge clicks at once so its own spin-drag still owns "
.. "the gesture")
check(gameImp._mouseAt == nil, "and never arms the scroll drag")
gameImp._clickPt = nil
mdown = false
gameMouseDown = false
LauncherView.update(gameImp, 0.016)
love.mouse.isDown = nil
+6
View File
@@ -42,6 +42,12 @@ T.eq(Performance.detect(), "balanced", "iOS -> balanced")
device("OS X", "x64", 8)
T.eq(Performance.detect(), "high", "8-core desktop -> high")
device("OS X", "arm64", 10)
T.eq(Performance.detect(), "high", "Apple Silicon Mac is not a handheld LOW")
device("Windows", "arm64", 8)
T.eq(Performance.detect(), "high", "Windows-on-ARM desktop -> high")
device("Windows", "x64", 2)
T.eq(Performance.detect(), "balanced", "dual-core desktop -> balanced")
+19
View File
@@ -115,6 +115,18 @@ check(cw <= 800 + 1e-6 and ch <= 600 + 1e-6, "and fits inside the workspace")
near(cx + cw / 2, 400, 1e-6, "centred horizontally")
near(cy + ch / 2, 300, 1e-6, "centred vertically")
-- ----------------------------------------------------------- touch bridge
session()
Studio.lastCanvas = { x = 0, y = 0, w = 100, h = 100 }
Studio.touchpressed("finger", 50, 50)
eq(Studio.touchId, "finger", "the first finger owns the Studio gesture")
check(Studio.pointerDown, "a touch is tracked as a held editor pointer")
Studio.touchmoved("finger", 60, 50)
Studio.touchreleased("finger", 60, 50)
eq(Studio.touchId, nil, "lifting clears the captured finger")
check(not Studio.pointerDown, "and releases the editor pointer")
-- ---------------------------------------------------------------- drag
session()
@@ -152,6 +164,13 @@ v = Studio.page().viewport
near(v.w * r.w, 500, 1e-6, "unlocked, width follows the pointer")
near(v.h * r.h, 600, 1e-6, "and height is free")
Studio.drag = { kind = "viewport-move", mx = 0, my = 0,
bx = 0, by = 0, bw = 100, bh = 100 }
Studio.updateDrag(-250, 1100, r)
v = Studio.page().viewport
check(v.x < 0, "a screen anchor can move past the left canvas edge")
check(v.y > 1, "a screen anchor can move past the bottom canvas edge")
-- controls never escape the canvas
Studio.selected = 1
Studio.drag = { kind = "control-move", mx = 0, my = 0,
+4
View File
@@ -162,6 +162,10 @@ check(specs["overlay_previous"], "overlay_previous is reachable at last")
check(specs["pause_toggle"] and specs["exit_emulator"],
"so are the hotkeys the old cycle could not reach")
check(specs["key:escape"], "and a keyboard bind can be picked")
check(specs["key:-"] and specs["key:="] and specs["key:1"]
and specs["key:5"] and specs["key:f1"] and specs["key:f2"]
and specs["key:f10"],
"desktop hotkeys can be placed on a mobile skin button")
check(specs["nul"], "decoration is still an option")
session()
+33 -4
View File
@@ -189,6 +189,34 @@ if bundled then
eq(bundled.pages[1].name, "GameBoy", "gb_anim page 1")
check(bundled.pages[1].viewport ~= nil, "gb_anim declares a screen viewport")
eq(bundled.pages[1].imagePath, "img/gb_back.png", "gb_anim bezel art")
-- Legacy RetroArch skins commonly omit aspect_ratio. Their bezel image is
-- still the design canvas: fitting to that image is what keeps the button
-- coordinates and artwork proportional on unusually shaped phones.
check(bundled.pages[1].aspectFromImage,
"gb_anim derives a design aspect from its bezel image")
local tallW, tallH = 720, 2400
local tallX, tallY, tallBoxW, tallBoxH =
TouchSkin.pageBox(bundled.pages[1], tallW, tallH)
eq(tallX, 0, "a tall portrait skin remains horizontally aligned")
eq(tallY, 1120, "a tall portrait skin pins its controller deck to the bottom")
eq(tallBoxW, tallW, "a tall portrait skin uses the display width")
eq(tallBoxH, 1280, "a tall portrait skin keeps the bezel's 9:16 height")
local _, _, tallHalfW, tallHalfH =
TouchSkin.controlGeometry(bundled.pages[1], bundled.pages[1].controls[9], tallW, tallH)
check(math.abs(tallHalfW - tallHalfH) < 0.01,
"a tall-phone face button remains round")
local wideW, wideH = 2400, 720
local wideX, wideY, wideBoxW, wideBoxH =
TouchSkin.pageBox(bundled.pages[1], wideW, wideH)
eq(wideX, 997.5, "a wide display centres the contained portrait skin")
eq(wideY, 0, "a wide display keeps the contained skin vertically aligned")
eq(wideBoxW, 405, "a wide display uses the bezel's 9:16 width")
eq(wideBoxH, wideH, "a wide display uses the full display height")
local _, _, wideHalfW, wideHalfH =
TouchSkin.controlGeometry(bundled.pages[1], bundled.pages[1].controls[9], wideW, wideH)
check(math.abs(wideHalfW - wideHalfH) < 0.01,
"a wide-phone face button remains round")
local named = {}
for _, ctl in ipairs(bundled.pages[1].controls) do
for _, btn in ipairs(ctl.buttons) do named[btn] = true end
@@ -239,13 +267,14 @@ TouchControls.enabled = true
TouchControls.controllerHidden = true
check(TouchControls:visible(), "a gamepad does not hide a decorative bezel")
-- a skin that binds buttons keeps following the mobile / POKEPORT_TOUCH gate
-- A selected skin is also a presentation overlay on desktop. The touch
-- input gate remains separate from whether its artwork is drawn.
TouchSkin.setActive(skin)
TouchSkin.setOverlayLive(false)
check(not TouchSkin.decorativeOnly(), "a skin with binds is not decoration")
check(not TouchSkin.drawable(), "and it does not draw where the overlay is off")
check(not TouchSkin.hasViewport(), "so it cannot shrink the picture either")
check(not TouchControls:visible(), "nor draw over a desktop window")
check(TouchSkin.drawable(), "and it still draws where touch input is off")
check(TouchSkin.hasViewport(), "so its screen placement stays available")
check(TouchControls:visible(), "and it draws over a desktop window")
TouchSkin.setOverlayLive(true)
check(TouchSkin.drawable(), "with the overlay live it draws again")
TouchControls.active = true