-- Party roster and inspector: side by side when wide, sliding pages on phones. -- Each viewport scrolls with fixed readable controls. local PartyMod = require("src.pokemon.Party") local Theme = require("Theme") local Ops = require("Ops") local MonEditor = require("MonEditor") local PAL = Theme.PAL local Party = {} local Motion = require("Motion") -- Roster column width: the design's 460px at the reference size, but it gives -- ground to the inspector on a narrow window so neither column collapses. -- The 300px floor used to be absolute, which on a phone handed the roster -- three quarters of the panel and left the inspector laying itself out at a -- negative width (#497); the floor now yields to a share of what there is. local function rosterWidth(w, s) return math.max(math.min(300 * s, w * 0.42), math.min(460 * s, w * 0.36)) end -- HP colour follows the game's own health bar thresholds. local function hpColor(frac) if frac <= 0.2 then return PAL.red end if frac <= 0.5 then return PAL.yellow end return PAL.green end local function drawRoster(S, Kit, x, y, listW, h) local s = Kit.scale Kit.card(x, y, listW, h) local pad = 18 * s local cx = x + pad local innerW = listW - 2 * pad Kit.caption(cx, y + pad, "PARTY") Kit.textRight( "mono", ("%d/%d"):format(#S.save.party, PartyMod.MAX), cx + innerW, y + pad, PAL.caption ) -- "+ Add mon" / "Remove" pinned to the card bottom; the roster fills above. local actH = Kit.controlH() local actY = y + h - pad - actH local listTop = y + pad + Kit.textHeight("caption") + 12 * s local listH = actY - 12 * s - listTop if #S.save.party == 0 then Kit.emptyBox( cx, listTop, innerW, listH, "Party is empty - Add creates a Lv5 mon owned by the save's player." ) else local rowH = math.max(88 * s, Kit.tapMin() + 38 * s) local rowGap = 8 * s S.selectedParty = Ops.clamp(S.selectedParty or 1, 1, #S.save.party) -- The list used to `break` past the fold, silently hiding party slots on -- a short window; it scrolls instead now (#715), same offset contract as -- every other list in the editor. local drawn, shift = Kit.list(S, "partyOffset", cx, listTop, innerW, listH, #S.save.party, rowH + rowGap) Kit.pushClip(cx, listTop, innerW, listH) for i = 1, drawn do local slot = S.partyOffset + i local mon = S.save.party[slot] if not mon then break end local ry = listTop + (i - 1) * (rowH + rowGap) - shift local selected = (S.editingMon == mon) if Kit.row(cx, ry, innerW, rowH, selected, PAL.green) then Ops.selectParty(S, slot) Motion.change(S, "mobileInspector", true, 1) end local rpad = 12 * s local icon = 44 * s MonEditor.drawSprite(S, Kit, mon.species, cx + rpad, ry + (rowH - icon) / 2, icon, mon) -- right cluster first, so the name knows how much room it has left local rightW = 52 * s local chipH = 20 * s local lvText = ("Lv%d"):format(mon.level) local chipW = Kit.textWidth("tiny", lvText) + 14 * s local chipX = cx + innerW - rpad - chipW Theme.stroke(chipX, ry + 10 * s, chipW, chipH, 6 * s, PAL.cardBorder, 0.3, 1) Kit.textCenter( "tiny", lvText, chipX, ry + 10 * s + (chipH - Kit.textHeight("tiny")) / 2, chipW, PAL.blueInk ) if selected then local ab = Kit.tapMin() local aw = Kit.tapMin() local ax = cx + innerW - rpad - 2 * aw - 4 * s local ay = ry + rowH - 10 * s - ab if Kit.stepper(ax, ay, aw, ab, "up", { font = "tiny" }) then Ops.partyMove(S, -1) end if Kit.stepper(ax + aw + 4 * s, ay, aw, ab, "down", { font = "tiny" }) then Ops.partyMove(S, 1) end rightW = 2 * aw + 4 * s + rpad end local tx = cx + rpad + icon + 12 * s local tw = math.max(40 * s, (cx + innerW - rightW - 10 * s) - tx) local monName = (mon.nickname and mon.nickname ~= "" and mon.nickname) or (type(mon.species) == "string" and mon.species) or (type(mon.name) == "string" and mon.name ~= "" and mon.name) or (S.data and S.data.pokemon and S.data.pokemon[mon.species or mon.speciesId] and S.data.pokemon[mon.species or mon.speciesId].name) or tostring(mon.species or "POKEMON") local name = Kit.ellipsize("monoRow", monName, tw - 34 * s) Kit.text("monoRow", name, tx, ry + 10 * s, PAL.heading) Kit.text( "tiny", ("#%d"):format(slot), tx + Kit.textWidth("monoRow", name) + 8 * s, ry + 12 * s, PAL.caption ) local maxHp = (mon.stats and mon.stats.hp) or 1 local frac = Ops.clamp((mon.hp or 0) / math.max(maxHp, 1), 0, 1) Kit.meter(tx, ry + rowH / 2 + 2 * s, tw, 6 * s, frac * 100, hpColor(frac)) Kit.text( "tiny", ("HP %d/%d"):format(mon.hp or 0, maxHp), tx, ry + rowH - 10 * s - Kit.textHeight("tiny"), PAL.muted ) end Kit.popClip() Kit.listScrollbar(S, "partyOffset", cx, listTop, innerW, listH) end local halfW = (innerW - 10 * s) / 2 if Kit.button( cx, actY, halfW, actH, "Add mon", { font = "small", radius = 9 * s, enabled = #S.save.party < PartyMod.MAX } ) then Ops.partyAdd(S) end if Kit.button( cx + halfW + 10 * s, actY, halfW, actH, Ops.armLabel(S, "party-remove", "Remove"), { kind = "danger", font = "small", radius = 9 * s } ) then Ops.partyRemove(S) end end function Party.draw(S, Kit, x, y, w, h) local s = Kit.scale local gap = 20 * s if w < 800 * s then if S.mobileInspector == nil then S.mobileInspector = S.editingMon ~= nil end Motion.pages(S, Kit, "mobileInspector", x, y, w, h, function(state, kit, dx, dy, dw, dh) if state.editingMon and state.mobileInspector then state.inspectorBack = true MonEditor.draw(state, kit, dx, dy, dw, dh) else drawRoster(state, kit, dx, dy, dw, dh) end end) else S.inspectorBack = false local listW = rosterWidth(w, s) drawRoster(S, Kit, x, y, listW, h) MonEditor.draw(S, Kit, x + listW + gap, y, w - listW - gap, h) end end return Party