mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 13:36:14 +02:00
Merge pull request #1659 from bryanthaboi/dev
skins again skins again skins again skins again skins again skins again skins again skins again skins again skins again skins again skins again skins again skins again skins again skins again
This commit is contained in:
+13
-6
@@ -294,7 +294,6 @@ function TouchSkin.parse(text)
|
||||
page.viewport = { x = num(vp[1], 0), y = num(vp[2], 0),
|
||||
w = num(vp[3], 1), h = num(vp[4], 1) }
|
||||
page.viewportFill = toBool(kv[p .. "_viewport_fill"])
|
||||
page.viewportExpand = toBool(kv[p .. "_viewport_expand"])
|
||||
end
|
||||
|
||||
page.pixelCoords = not page.normalized
|
||||
@@ -447,7 +446,6 @@ function TouchSkin.parseNative(text)
|
||||
page.viewport = { x = num(raw.viewport.x, 0), y = num(raw.viewport.y, 0),
|
||||
w = num(raw.viewport.w, 1), h = num(raw.viewport.h, 1) }
|
||||
page.viewportFill = raw.viewport.fill == true
|
||||
page.viewportExpand = raw.viewport.expand == true
|
||||
end
|
||||
for _, c in ipairs(raw.controls or {}) do
|
||||
local buttons, hotkeys, keys, decorative = parseBinds(c.bind or "nul")
|
||||
@@ -526,7 +524,6 @@ function TouchSkin.toNative(skin)
|
||||
x = page.viewport.x, y = page.viewport.y,
|
||||
w = page.viewport.w, h = page.viewport.h,
|
||||
fill = page.viewportFill or nil,
|
||||
expand = page.viewportExpand or nil,
|
||||
}
|
||||
end
|
||||
for _, ctl in ipairs(page.controls or {}) do
|
||||
@@ -922,7 +919,6 @@ function TouchSkin.toRetroArchConfig(skin)
|
||||
if page.viewport then
|
||||
out[#out + 1] = p .. "_viewport = " .. fmtRect(page.viewport)
|
||||
if page.viewportFill then out[#out + 1] = p .. "_viewport_fill = true" end
|
||||
if page.viewportExpand then out[#out + 1] = p .. "_viewport_expand = true" end
|
||||
end
|
||||
local controls = {}
|
||||
for _, ctl in ipairs(page.controls or {}) do
|
||||
@@ -1434,6 +1430,16 @@ local function remainderBox(ox, oy, w, h, bx, by, bw, bh)
|
||||
return best[1], best[2], best[3], best[4]
|
||||
end
|
||||
|
||||
-- The deck box pinned to the lower edge (see pageBox) leaves room above it
|
||||
-- that belongs to the game, so a screen rect flush with the top of the deck
|
||||
-- grows into it instead of showing a black band.
|
||||
local function deckHeadroom(y, vh, by, bh, oy, h)
|
||||
if by <= oy + 0.5 then return y, vh end
|
||||
if by + bh < oy + h - 0.5 then return y, vh end
|
||||
if y - by > math.max(2, bh * 0.01) then return y, vh end
|
||||
return oy, vh + (y - oy)
|
||||
end
|
||||
|
||||
function TouchSkin.pageViewport(page, w, h, ox, oy)
|
||||
if not page then return nil end
|
||||
ox, oy = ox or 0, oy or 0
|
||||
@@ -1443,12 +1449,13 @@ function TouchSkin.pageViewport(page, w, h, ox, oy)
|
||||
local x, y = bx + v.x * bw, by + v.y * bh
|
||||
local vw, vh = v.w * bw, v.h * bh
|
||||
if vw <= 0 or vh <= 0 then return nil end
|
||||
return x, y, vw, vh, page.viewportFill == true, page.viewportExpand == true
|
||||
y, vh = deckHeadroom(y, vh, by, bh, oy, h)
|
||||
return x, y, vw, vh, page.viewportFill == true
|
||||
end
|
||||
if page.screenFit == "remainder" then
|
||||
local x, y, vw, vh = remainderBox(ox, oy, w, h, bx, by, bw, bh)
|
||||
if not x then return nil end
|
||||
return x, y, vw, vh, false, false
|
||||
return x, y, vw, vh, false
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -1067,9 +1067,22 @@ local GAME_TABS = {
|
||||
local HEADER_TABS = {
|
||||
{ id = "mods", key = "tab-mods" },
|
||||
{ id = "find", key = "tab-find" },
|
||||
{ id = "skins", key = "tab-skins", glyph = true },
|
||||
{ id = "skins", key = "tab-skins", glyph = true, beta = true },
|
||||
{ id = "bug", key = "tab-bug" },
|
||||
}
|
||||
|
||||
local BETA_TAG_OPTS = { fill = true, bold = true, ink = PAL.inverse }
|
||||
|
||||
local function drawBetaTag(x, y, w, h)
|
||||
Kit.tag(x, y, w, h, "BETA", PAL.yellow, BETA_TAG_OPTS)
|
||||
end
|
||||
|
||||
local function overlayBeta(tx, ty, w, tabH, m)
|
||||
local bh = math.floor(11 * m.s)
|
||||
local bw = math.min(w, Kit.textWidth("micro", "BETA") + math.floor(10 * m.s))
|
||||
drawBetaTag(tx + (w - bw) / 2, ty + tabH - bh - math.floor(2 * m.s), bw, bh)
|
||||
end
|
||||
|
||||
for _, t in ipairs(HEADER_TABS) do
|
||||
t.opts = { face = "tab", font = "tab", color = t.color, letter = t.letter }
|
||||
if t.glyph then
|
||||
@@ -1273,6 +1286,7 @@ local function buildHeader(imp, m)
|
||||
o.image = t.icon
|
||||
o.action = chrome.tab[t.id]
|
||||
btn(imp, tx, ty, w, tabH, t.key, "", o)
|
||||
if t.beta then overlayBeta(tx, ty, w, tabH, m) end
|
||||
tx = tx + w + tabGap
|
||||
end
|
||||
-- The bug-report chip sits LAST, past the sync chip.
|
||||
@@ -1290,10 +1304,7 @@ local function buildHeader(imp, m)
|
||||
local o = chrome.sync
|
||||
o.active = imp._syncModal ~= nil
|
||||
btn(imp, tx, ty, w, tabH, "tab-sync", "", o)
|
||||
local bh = math.floor(11 * m.s)
|
||||
local bw = math.min(w, Kit.textWidth("micro", "BETA") + math.floor(10 * m.s))
|
||||
Kit.tag(tx + (w - bw) / 2, ty + tabH - bh - math.floor(2 * m.s), bw, bh,
|
||||
"BETA", o.active and PAL.inverse or PAL.yellow)
|
||||
overlayBeta(tx, ty, w, tabH, m)
|
||||
local eng = imp._sync
|
||||
if eng and eng.busy and eng:busy() then
|
||||
Kit.spinner(tx + w - math.floor(8 * m.s), ty + math.floor(8 * m.s),
|
||||
@@ -4490,8 +4501,8 @@ local function syncTitle(imp, m, px, py, pw, pad)
|
||||
Kit.text("button", label, px + pad, py, PAL.heading)
|
||||
local bh = math.floor(15 * m.s)
|
||||
local bw = Kit.textWidth("micro", "BETA") + math.floor(14 * m.s)
|
||||
Kit.tag(px + pad + Kit.textWidth("button", label) + math.floor(8 * m.s),
|
||||
py + (Kit.textHeight("button") - bh) / 2, bw, bh, "BETA", PAL.yellow)
|
||||
drawBetaTag(px + pad + Kit.textWidth("button", label) + math.floor(8 * m.s),
|
||||
py + (Kit.textHeight("button") - bh) / 2, bw, bh)
|
||||
return py + Kit.textHeight("button") + math.floor(12 * m.s)
|
||||
end
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ local TouchSkin = require("src.core.TouchSkin")
|
||||
|
||||
local Playfield = {}
|
||||
|
||||
Playfield.WIDTH, Playfield.HEIGHT = 160, 144
|
||||
|
||||
Playfield.entered = false
|
||||
Playfield.box = nil
|
||||
|
||||
@@ -35,14 +33,9 @@ function Playfield.cutout(sw, sh)
|
||||
end
|
||||
|
||||
function Playfield.rect(sw, sh)
|
||||
local x, y, w, h, _, expand = Playfield.cutout(sw, sh)
|
||||
local x, y, w, h = Playfield.cutout(sw, sh)
|
||||
if not x then return 0, 0, sw or 0, sh or 0, false end
|
||||
if expand then return x, y, w, h, true end
|
||||
local s = math.max(1, math.floor(math.min(w / Playfield.WIDTH,
|
||||
h / Playfield.HEIGHT)))
|
||||
local pw = math.min(w, Playfield.WIDTH * s)
|
||||
local ph = math.min(h, Playfield.HEIGHT * s)
|
||||
return x + math.floor((w - pw) / 2), y + math.floor((h - ph) / 2), pw, ph, true
|
||||
return x, y, w, h, true
|
||||
end
|
||||
|
||||
function Playfield.enter(x, y, w, h)
|
||||
|
||||
@@ -87,12 +87,12 @@ local function displayMetrics()
|
||||
if dpiX < 1e-6 then dpiX = 1 end
|
||||
if dpiY < 1e-6 then dpiY = 1 end
|
||||
local vx, vy = 0, 0
|
||||
local cut, grow = false, false
|
||||
local sx, sy, sw, sh, _, expand = Playfield.cutout(pw, ph)
|
||||
local cut = false
|
||||
local sx, sy, sw, sh = Playfield.cutout(pw, ph)
|
||||
if sx then
|
||||
vx, vy, pw, ph, cut, grow = sx, sy, sw, sh, true, expand
|
||||
vx, vy, pw, ph, cut = sx, sy, sw, sh, true
|
||||
end
|
||||
return ww, wh, pw, ph, dpiX, dpiY, vx, vy, cut, grow
|
||||
return ww, wh, pw, ph, dpiX, dpiY, vx, vy, cut
|
||||
end
|
||||
|
||||
local function positionLift(ph, contentPx, dpiY, cut)
|
||||
@@ -275,7 +275,7 @@ end
|
||||
-- corners; flat mode returns exactly today's size (growth factor is 1 when
|
||||
-- tilt is inactive).
|
||||
function Renderer:worldViewSize()
|
||||
local _, _, pw, ph, _, dpiY, _, _, cut, grow = displayMetrics()
|
||||
local _, _, pw, ph, _, dpiY, _, _, cut = displayMetrics()
|
||||
-- FAITHFUL RATIO on mobile. The world pass deliberately expands to cover the
|
||||
-- WHOLE display, so letterbox voids become more map instead of black bars.
|
||||
-- That is why the lock appeared to do nothing in the overworld: it shrank
|
||||
@@ -287,7 +287,6 @@ function Renderer:worldViewSize()
|
||||
-- this is the same sum with the viewport standing in for the window, so
|
||||
-- both platforms show the same map area at the same zoom.
|
||||
local cap = FaithfulRes.scaleCap()
|
||||
if not cap and cut and not grow then cap = self:fitScale() end
|
||||
if cap then
|
||||
local uiw, uih = self:uiSize()
|
||||
pw = cut and math.min(pw, uiw * cap) or uiw * cap
|
||||
|
||||
@@ -456,9 +456,10 @@ function Studio.setCanvas(index, fromSync)
|
||||
page.viewportFill = false
|
||||
markDirty()
|
||||
end
|
||||
-- A cfg-authored aspect_ratio is the overlay's design aspect; keep it so
|
||||
-- the preview letterboxes like RetroArch instead of stretching (#1503).
|
||||
if page and not page.aspectFromCfg then
|
||||
-- A cfg-authored aspect_ratio is the overlay's design aspect, and bezel art
|
||||
-- is its own design canvas (TouchSkin.applyImageAspect); keep either so the
|
||||
-- preview lays the page out exactly the way the game does (#1503).
|
||||
if page and not (page.aspectFromCfg or page.aspectFromImage) then
|
||||
page.aspect = canvas.w / canvas.h
|
||||
end
|
||||
end
|
||||
|
||||
+16
-5
@@ -749,12 +749,23 @@ function Kit.chip(x, y, w, h, label, on, color, id)
|
||||
end
|
||||
|
||||
-- A status label with no interaction: outlined text, the "INSTALLED"/"UPDATE"
|
||||
-- markers on mod rows.
|
||||
function Kit.tag(x, y, w, h, label, color)
|
||||
-- markers on mod rows. Pass opts.fill for a solid chip (BETA badges); ink
|
||||
-- then defaults to PAL.inverse so the label stays readable on the fill.
|
||||
function Kit.tag(x, y, w, h, label, color, opts)
|
||||
if not G then return end
|
||||
Theme.strokeRounded(x, y, w, h, color or PAL.line, 0.7, 1)
|
||||
Kit.textCenter("micro", label, x, y + (h - Kit.textHeight("micro")) / 2, w,
|
||||
color or PAL.muted)
|
||||
opts = opts or {}
|
||||
if opts.fill then
|
||||
Theme.fillRounded(x, y, w, h, color or PAL.yellow, 1, h / 2)
|
||||
else
|
||||
Theme.strokeRounded(x, y, w, h, color or PAL.line, 0.7, 1)
|
||||
end
|
||||
local ink = opts.ink or (opts.fill and PAL.inverse) or color or PAL.muted
|
||||
local ty = y + (h - Kit.textHeight("micro")) / 2
|
||||
if opts.bold then
|
||||
Kit.textCenterBold("micro", label, x, ty, w, ink)
|
||||
else
|
||||
Kit.textCenter("micro", label, x, ty, w, ink)
|
||||
end
|
||||
end
|
||||
|
||||
-- Checkbox row. Returns (newChecked, changed).
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Screen-rect check for a touch skin, both generations.
|
||||
--
|
||||
-- POKEPORT_DRIVER=tests/drivers/skin_screen_shot.lua love .
|
||||
-- POKEPORT_GAME=gold POKEPORT_SKIN=my_skin POKEPORT_DRIVER=... love .
|
||||
--
|
||||
-- Gold's cache lives under the DEFAULT save identity, so a POKEPORT_IDENTITY
|
||||
-- sandbox has to be seeded with it first or the boot hangs in the importer:
|
||||
-- cp -R ~/Library/Application\ Support/LOVE/pokemon-love2d/{gold,skins} \
|
||||
-- ~/Library/Application\ Support/LOVE/<identity>/
|
||||
local U = require("tests.drivers.util")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
|
||||
local function env(name, fallback)
|
||||
local v = os.getenv(name)
|
||||
if v == nil or v == "" then return fallback end
|
||||
return v
|
||||
end
|
||||
|
||||
local function tap(game, button)
|
||||
game.input.pressQueue[#game.input.pressQueue + 1] = button
|
||||
game.input.state[button] = true
|
||||
U.wait(2)
|
||||
game.input.state[button] = false
|
||||
end
|
||||
|
||||
return function(game)
|
||||
local TouchControls = require("src.core.TouchControls")
|
||||
local out = env("POKEPORT_SHOT_DIR", ".")
|
||||
local gen2 = GameVersion.generation() == 2
|
||||
|
||||
love.window.setMode(tonumber(env("POKEPORT_SHOT_W", 460)),
|
||||
tonumber(env("POKEPORT_SHOT_H", 950)))
|
||||
local skin = env("POKEPORT_SKIN", "gb_anim")
|
||||
local ok, err = TouchControls:selectSkin(skin)
|
||||
assert(ok, "skin " .. skin .. ": " .. tostring(err))
|
||||
|
||||
U.wait(60)
|
||||
if gen2 then
|
||||
local A = require("tests.drivers.gold.adapter")
|
||||
assert(game.world and game.world.map, "gold world did not boot")
|
||||
A.teleport(game, env("POKEPORT_SHOT_MAP", "NEW_BARK_TOWN"), 6, 6)
|
||||
U.wait(120)
|
||||
else
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
game.save.party = { Pokemon.new(game.data, "CHARIZARD", 50) }
|
||||
U.teleport(game, env("POKEPORT_SHOT_MAP", "PALLET_TOWN"), 10, 8, "down")
|
||||
U.wait(40)
|
||||
end
|
||||
U.shot(game, out .. "/skin_world.png")
|
||||
|
||||
tap(game, "start")
|
||||
U.wait(60)
|
||||
U.shot(game, out .. "/skin_menu.png")
|
||||
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
@@ -316,6 +316,8 @@ check(view:find('"tab-sync"', 1, true) ~= nil,
|
||||
local header = view:match("local HEADER_TABS = %{(.-)%}\n")
|
||||
check(header and header:find('id = "skins"', 1, true) ~= nil,
|
||||
"and it sits beside the skins tab")
|
||||
check(header and header:find("beta = true", 1, true) ~= nil,
|
||||
"the skins tab carries a BETA badge too")
|
||||
check(view:find('"BETA"', 1, true) ~= nil,
|
||||
"the button and the modal are labelled BETA")
|
||||
check(view:find("buildSyncModal", 1, true) ~= nil,
|
||||
|
||||
@@ -111,6 +111,25 @@ check(Studio.page().viewport ~= nil, "the SGB cutout cannot be toggled off")
|
||||
Studio.setCanvas(#Studio.CANVASES + 1)
|
||||
eq(Studio.canvasIndex, 1, "canvas selection wraps")
|
||||
|
||||
-- the preview must lay the page out the way the game will, so a preset never
|
||||
-- overwrites the aspect the bezel art (or a cfg) already fixed
|
||||
session()
|
||||
local art = Studio.page()
|
||||
art.aspect, art.aspectFromImage = 0.5625, true
|
||||
Studio.setCanvas(1)
|
||||
near(art.aspect, 0.5625, 1e-9, "bezel art keeps its own aspect on a preset switch")
|
||||
local deckW, deckH = 900, 1700
|
||||
local _, dby, _, dbh = TouchSkin.pageBox(art, deckW, deckH)
|
||||
check(dby > 0 and math.abs(dby + dbh - deckH) < 1e-6,
|
||||
"so a preset taller than the art pins the deck low, as gameplay does")
|
||||
|
||||
session()
|
||||
local plain = Studio.page()
|
||||
plain.aspect, plain.aspectFromImage, plain.aspectFromCfg = nil, nil, nil
|
||||
Studio.setCanvas(1)
|
||||
near(plain.aspect, Studio.canvas().w / Studio.canvas().h, 1e-9,
|
||||
"a page with no art of its own still takes the preset's aspect")
|
||||
|
||||
Studio.setCanvas(1)
|
||||
Studio.viewZoom = 1
|
||||
local cx, cy, cw, ch = Studio.canvasRect(0, 0, 800, 600)
|
||||
|
||||
@@ -136,11 +136,16 @@ for off = lo, hi do
|
||||
end
|
||||
Zoom.offset = 0
|
||||
|
||||
local capped = select(1, Renderer:worldViewSize())
|
||||
useSkin("0.25,0.1,0.5,0.6", "overlay0_viewport_expand = true")
|
||||
local expanded = select(1, Renderer:worldViewSize())
|
||||
check(expanded > capped,
|
||||
"viewport_expand lets the survey world fill the cutout instead of the GB box")
|
||||
useSkin("0.25,0.1,0.3,0.8")
|
||||
local tallR = Renderer:frameRects()
|
||||
local tallVw, tallVh = Renderer:worldViewSize()
|
||||
local tallSp = Zoom.scale(tallR.Sp)
|
||||
check(tallVw * tallSp >= tallR.vuw and tallVh * tallSp >= tallR.vuh,
|
||||
"the world pass covers the whole cutout, not just the GB box inside it")
|
||||
check(tallVh * tallSp > tallR.uvph,
|
||||
"so a cutout taller than 160x144 shows map where the UI letterbox ends")
|
||||
check(tallR.uvph < tallR.vuh and tallR.uvpw <= tallR.vuw,
|
||||
"while the UI keeps its whole-pixel letterbox inside that cutout")
|
||||
useSkin("0.25,0.1,0.5,0.6")
|
||||
|
||||
setWindow(480, 800)
|
||||
@@ -159,22 +164,14 @@ setWindow(1280, 720)
|
||||
useSkin("0.25,0.1,0.5,0.6")
|
||||
local px, py, pw, ph, active = Playfield.rect(1280, 720)
|
||||
eq(active, true, "Gold sees the cutout too")
|
||||
eq(pw, 480, "the playfield is a whole multiple of 160")
|
||||
eq(ph, 432, "and of 144")
|
||||
check(inside(px, py, pw, ph, 320, 72, 640, 432),
|
||||
"centred inside the cutout")
|
||||
eq(Chrome.fitScale(1280, 720), 3, "Chrome fits the playfield")
|
||||
eq(px, 320, "the playfield is the cutout, at its origin")
|
||||
eq(py, 72, "on both axes")
|
||||
eq(pw, 640, "with its full width")
|
||||
eq(ph, 432, "and its full height")
|
||||
eq(Chrome.fitScale(1280, 720), 3, "Chrome fits whole GB pixels in it")
|
||||
local cox, coy = Chrome.fitOrigin(1280, 720)
|
||||
eq(cox, px, "and centres the panel on it")
|
||||
eq(coy, py, "on both axes")
|
||||
|
||||
useSkin("0.25,0.1,0.5,0.6", "overlay0_viewport_expand = true")
|
||||
local ex, ey, ew, eh = Playfield.rect(1280, 720)
|
||||
eq(ew, 640, "expand hands the picture the full cutout width")
|
||||
eq(eh, 432, "and its full height")
|
||||
eq(ex, 320, "at the cutout origin")
|
||||
eq(ey, 72, "on both axes")
|
||||
useSkin("0.25,0.1,0.5,0.6")
|
||||
eq(cox, 320 + 80, "and centres the panel on the cutout")
|
||||
eq(coy, 72, "on both axes")
|
||||
|
||||
local ew2, eh2, ex2, ey2, act2 = Playfield.push(1280, 720)
|
||||
eq(act2, true, "push reports the frame is contained")
|
||||
@@ -184,12 +181,29 @@ eq(ew2, pw, "and hands the scene the playfield size")
|
||||
eq(Playfield.cutout(ew2, eh2), nil, "inside the frame there is no cutout left")
|
||||
eq(select(3, Playfield.rect(ew2, eh2)), pw, "so the playfield is the surface")
|
||||
eq(Chrome.fitScale(ew2, eh2), 3, "and Chrome fits it without re-applying")
|
||||
eq(select(1, Chrome.fitOrigin(ew2, eh2)), 0, "at a local origin")
|
||||
eq(select(1, Chrome.fitOrigin(ew2, eh2)), 80, "centred on that surface alone")
|
||||
eq(select(1, Playfield.dimensions()), pw, "screens read the playfield as the display")
|
||||
Playfield.pop()
|
||||
eq(Playfield.entered, false, "pop leaves the frame")
|
||||
eq(select(1, Playfield.cutout(1280, 720)), 320, "and the cutout is visible again")
|
||||
|
||||
setWindow(960, 1901)
|
||||
useSkin("0,0,1,0.5", "overlay0_aspect_ratio = 0.5625")
|
||||
local deck = TouchSkin.page()
|
||||
local _, dy, _, dh = TouchSkin.pageBox(deck, 960, 1901)
|
||||
check(dy > 0 and math.abs(dy + dh - 1901) < 1,
|
||||
"a portrait deck taller than the display pins to the lower edge")
|
||||
local hx, hy, hw, hh = Playfield.cutout(960, 1901)
|
||||
eq(hy, 0, "and the screen rect it left flush takes the room above it")
|
||||
eq(hx, 0, "without moving sideways")
|
||||
eq(hw, 960, "or changing width")
|
||||
eq(hh, math.floor(dy + dh * 0.5), "growing by exactly the headroom")
|
||||
|
||||
useSkin("0,0.1,1,0.5", "overlay0_aspect_ratio = 0.5625")
|
||||
check(select(2, Playfield.cutout(960, 1901)) > dy,
|
||||
"a screen rect the author inset from the top keeps that bezel margin")
|
||||
|
||||
setWindow(1280, 720)
|
||||
useSkin("0.4,0.4,0.1,0.1")
|
||||
local sx, sy, sw, sh = Playfield.rect(1280, 720)
|
||||
check(inside(sx, sy, sw, sh, 512, 288, 128, 72),
|
||||
|
||||
Reference in New Issue
Block a user