mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
554 lines
20 KiB
Lua
554 lines
20 KiB
Lua
-- Gen 2 naming screen: the on-screen keyboard Gold uses for the player, the
|
|
-- rival, mom, a box, and mon nicknames. Ported from
|
|
-- engine/menus/naming_screen.asm.
|
|
--
|
|
-- Layout (tile coords, transcribed from the ASM's hlcoord calls):
|
|
-- whole screen filled with NAMINGSCREEN_BORDER
|
|
-- (1,1) 6x18 cleared -- header: icon, prompt at (5,2), entry field at (5,6)
|
|
-- (1,8) 7x18 cleared -- keyboard: 5 rows at y = 8,10,12,14,16
|
|
-- letters at x = 2,4,...,18 (nine per row)
|
|
-- NAME_BOX gets a sixth row and a shorter header (4x18 / 9x18 at y=6)
|
|
--
|
|
-- Cursor grid is 9 wide by 5 rows (6 for a box). The bottom row is not nine
|
|
-- letters but three fat targets -- case switch, DEL, END -- which is why the
|
|
-- ASM keeps a separate x-offset table for it (.CaseDelEnd: $00,$00,$00,
|
|
-- $30,$30,$30,$60,$60,$60) instead of the even $10 steps letters use.
|
|
--
|
|
-- SELECT toggles case anywhere; START jumps the cursor onto END; B deletes.
|
|
|
|
local Assets = require("src.render.Assets")
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local Font = require("src.render.Font")
|
|
local GbcPalette = require("src.render.GbcPalette")
|
|
local Runtime = require("src.mods.Runtime")
|
|
|
|
local NamingScreen = {}
|
|
NamingScreen.__index = NamingScreen
|
|
NamingScreen.isOpaque = true
|
|
|
|
-- data/text/name_input_chars.asm. Each row is a 17-character string with the
|
|
-- letters on the even columns, so cell N is the character at index N*2 - 1.
|
|
-- Rows whose cells are not single characters (the box screen's dakuten pairs
|
|
-- and multi-byte glyphs) are written out as arrays instead.
|
|
local function rowCells(row)
|
|
local out = {}
|
|
for i = 1, 9 do
|
|
local ch = row:sub(i * 2 - 1, i * 2 - 1)
|
|
out[i] = ch
|
|
end
|
|
return out
|
|
end
|
|
|
|
local NAME_INPUT_UPPER = {
|
|
rowCells("A B C D E F G H I"),
|
|
rowCells("J K L M N O P Q R"),
|
|
rowCells("S T U V W X Y Z "),
|
|
rowCells("- ? ! / . , "),
|
|
}
|
|
local NAME_INPUT_LOWER = {
|
|
rowCells("a b c d e f g h i"),
|
|
rowCells("j k l m n o p q r"),
|
|
rowCells("s t u v w x y z "),
|
|
{ "\xc3\x97", "(", ")", ":", ";", "[", "]", "<PK>", "<MN>" },
|
|
}
|
|
-- BOX_NAME gets one extra symbol row in each case (BoxNameInput*).
|
|
local BOX_INPUT_UPPER = {
|
|
NAME_INPUT_UPPER[1], NAME_INPUT_UPPER[2], NAME_INPUT_UPPER[3],
|
|
{ "\xc3\x97", "(", ")", ":", ";", "[", "]", "<PK>", "<MN>" },
|
|
{ "-", "?", "!", "\xe2\x99\x82", "\xe2\x99\x80", "/", ".", ",", "&" },
|
|
}
|
|
local BOX_INPUT_LOWER = {
|
|
NAME_INPUT_LOWER[1], NAME_INPUT_LOWER[2], NAME_INPUT_LOWER[3],
|
|
{ "\xc3\xa9", "'d", "'l", "'m", "'r", "'s", "'t", "'v", "0" },
|
|
{ "1", "2", "3", "4", "5", "6", "7", "8", "9" },
|
|
}
|
|
|
|
-- The bottom row's three targets and the columns their labels start at. The
|
|
-- case target names the board it SWITCHES TO, not the one it is on: the last
|
|
-- row of NameInputUpper is "lower DEL END" and the last row of
|
|
-- NameInputLower is "UPPER DEL END" (data/text/name_input_chars.asm).
|
|
local BOTTOM_UPPER_LABELS = { "lower", "DEL", "END" }
|
|
local BOTTOM_LOWER_LABELS = { "UPPER", "DEL", "END" }
|
|
-- Cursor tile for each target: NamingScreen_AnimateCursor's .CaseDelEnd adds
|
|
-- pixel $00 / $30 / $60 to the cursor's own XCOORD of 24 (`depixel 10, 3`),
|
|
-- which is OAM x 24 / 72 / 120 and so screen tile 2 / 8 / 14. The bracket is
|
|
-- five tiles wide there, so it wraps the whole label rather than sitting left
|
|
-- of it.
|
|
local BOTTOM_CURSOR_TX = { 2, 8, 14 }
|
|
local BOTTOM_LABEL_TX = { 2, 9, 15 }
|
|
local BOTTOM_CURSOR_TILES = 5
|
|
|
|
-- NAME_* types (constants/menu_constants.asm order) as prompts + field sizes.
|
|
-- Lengths are the ASM's *_NAME_LENGTH - 1, i.e. usable characters.
|
|
NamingScreen.TYPES = {
|
|
player = { prompt = "YOUR NAME?", maxLength = 7, sprite = "SPRITE_CHRIS" },
|
|
rival = { prompt = "RIVAL'S NAME?", maxLength = 7, sprite = "SPRITE_RIVAL" },
|
|
mom = { prompt = "MOTHER'S NAME?", maxLength = 7, sprite = "SPRITE_MOM" },
|
|
box = { prompt = "BOX NAME?", maxLength = 8, isBox = true },
|
|
nickname = { prompt = nil, maxLength = 10 },
|
|
}
|
|
|
|
function NamingScreen:wantsFillScale() return true end
|
|
function NamingScreen:drawsWidescreen() return true end
|
|
|
|
-- opts: type ("player"/"rival"/"mom"/"box"/"nickname"), prompt, maxLength,
|
|
-- initial, monName (nickname header), icon/sprite image path, onDone(name),
|
|
-- onCancel().
|
|
function NamingScreen.new(game, opts)
|
|
opts = opts or {}
|
|
local self = setmetatable({}, NamingScreen)
|
|
self.game = game
|
|
local kind = NamingScreen.TYPES[opts.type or "player"]
|
|
or NamingScreen.TYPES.player
|
|
self.kind = kind
|
|
self.isBox = opts.isBox or kind.isBox or false
|
|
self.maxLength = opts.maxLength or kind.maxLength or 7
|
|
self.prompt = opts.prompt or kind.prompt or "NICKNAME?"
|
|
self.monName = opts.monName
|
|
self.onDone = opts.onDone
|
|
self.onCancel = opts.onCancel
|
|
self.lower = false -- wNamingScreenLetterCase; upper first
|
|
self.text = opts.initial or ""
|
|
self.col = 0
|
|
self.row = 0
|
|
self.iconImage = nil
|
|
if opts.iconPath then
|
|
local ok, img = pcall(Assets.image, opts.iconPath)
|
|
if ok then self.iconImage = img end
|
|
end
|
|
-- The header icon is an OBJ on the cart, so it wears a real palette; without
|
|
-- one it would draw in raw DMG shades next to a colored world.
|
|
self.iconColors = opts.iconColors
|
|
self.gfx = opts.menuGfx
|
|
self.tiles = {}
|
|
if self.gfx then
|
|
for _, key in ipairs({ "border", "middleLine", "underLine", "cursor" }) do
|
|
if self.gfx[key] then
|
|
local ok, img = pcall(Assets.image, self.gfx[key])
|
|
if ok then self.tiles[key] = img end
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
-- ui.naming.grid identity: unhooked, the board is the one the cart ships.
|
|
local function sameGrid(grid) return grid end
|
|
|
|
-- The letter rows of the page that is showing, through the ui.naming.grid hook
|
|
-- -- the same name and the same (grid, ctx) payload the Gen 1 screen uses
|
|
-- (src/ui/NamingScreen.lua), so a mod that splices digits onto a row does it
|
|
-- once for both generations. Guarded with wantsHook because update and draw
|
|
-- both read the board every frame; unhooked this is the module's own table.
|
|
--
|
|
-- Only the LETTER rows travel through the hook. Gold keeps the case / DEL /
|
|
-- END targets off the board entirely, in a fat bottom row of their own
|
|
-- (NamingScreen_AnimateCursor's .CaseDelEnd), so unlike Gen 1 there is no meta
|
|
-- cell a hooked grid could drop -- but bottomRow() counts the rows it answers
|
|
-- with, so a page carrying an extra row still keeps those targets under it.
|
|
function NamingScreen:rows()
|
|
local base
|
|
if self.isBox then
|
|
base = self.lower and BOX_INPUT_LOWER or BOX_INPUT_UPPER
|
|
else
|
|
base = self.lower and NAME_INPUT_LOWER or NAME_INPUT_UPPER
|
|
end
|
|
if not Runtime.wantsHook("ui.naming.grid") then return base end
|
|
local hooked = Runtime.call("ui.naming.grid", sameGrid, base, {
|
|
lower = self.lower and true or false,
|
|
-- `title` is the Gen 1 key for the line above the entry field; here that
|
|
-- is the prompt ("YOUR NAME?"). `box` is Gen 2's own: a BOX NAME page has
|
|
-- a row the name pages do not.
|
|
title = self.prompt,
|
|
maxLen = self.maxLength,
|
|
box = self.isBox,
|
|
game = self.game,
|
|
})
|
|
if type(hooked) ~= "table" or #hooked == 0 then return base end
|
|
return hooked
|
|
end
|
|
|
|
-- The keyboard's first row, and the row index the bottom (case/DEL/END) row
|
|
-- sits at. A box screen shifts everything up two rows to fit its sixth row.
|
|
function NamingScreen:keyboardTop()
|
|
return self.isBox and 6 or 8
|
|
end
|
|
|
|
-- One past the last letter row: four on a name page, five on a box one, and
|
|
-- whatever a hooked grid answers with (see rows()).
|
|
function NamingScreen:bottomRow()
|
|
return #self:rows()
|
|
end
|
|
|
|
function NamingScreen:onBottomRow()
|
|
return self.row == self:bottomRow()
|
|
end
|
|
|
|
-- Which of the three fat targets the cursor is on (1 case, 2 delete, 3 end),
|
|
-- mirroring NamingScreen_GetCursorPosition's `cp $3 / cp $6` split.
|
|
function NamingScreen:bottomTarget()
|
|
if self.col < 3 then return 1 end
|
|
if self.col < 6 then return 2 end
|
|
return 3
|
|
end
|
|
|
|
-- A blank cell is a real SPACE, not a dead key. ApplyTextInputMode writes the
|
|
-- NameInput* rows straight into the tilemap and NamingScreen_GetLastCharacter
|
|
-- reads the tile UNDER the cursor back out of it (hlcoord 0,0 + row*SCREEN_WIDTH
|
|
-- + col), so the trailing blanks in "S T U V W X Y Z " and "- ? ! / . , "
|
|
-- (data/text/name_input_chars.asm) type a space like any other character. Only
|
|
-- a cell that is not on the board at all answers nil. Trimming is not this
|
|
-- screen's job either: it stores what was typed and InitName decides whether
|
|
-- the result counts as blank (home/string.asm:6-30).
|
|
function NamingScreen:characterAt(col, row)
|
|
local grid = self:rows()
|
|
local line = grid[row + 1]
|
|
local ch = line and line[col + 1]
|
|
if not ch or ch == "" then return nil end
|
|
return ch
|
|
end
|
|
|
|
function NamingScreen:addCharacter(ch)
|
|
if not ch then return end
|
|
if #self.text >= self.maxLength then return end
|
|
self.text = self.text .. ch
|
|
end
|
|
|
|
function NamingScreen:deleteCharacter()
|
|
if #self.text == 0 then return end
|
|
self.text = self.text:sub(1, #self.text - 1)
|
|
end
|
|
|
|
function NamingScreen:toggleCase()
|
|
self.lower = not self.lower
|
|
end
|
|
|
|
function NamingScreen:accept()
|
|
local name = self.text
|
|
if self.onDone then self.onDone(name) end
|
|
end
|
|
|
|
-- Cursor movement. On the letter rows the columns step one at a time and wrap
|
|
-- (.right / .wrap_left); on the bottom row left/right hop between the three
|
|
-- targets, which the ASM does by rounding the column to a multiple of three.
|
|
function NamingScreen:moveHorizontal(delta)
|
|
if self:onBottomRow() then
|
|
local target = self:bottomTarget() + delta
|
|
if target < 1 then target = 3 end
|
|
if target > 3 then target = 1 end
|
|
self.col = (target - 1) * 3
|
|
return
|
|
end
|
|
self.col = self.col + delta
|
|
if self.col < 0 then self.col = 8 end
|
|
if self.col > 8 then self.col = 0 end
|
|
end
|
|
|
|
function NamingScreen:moveVertical(delta)
|
|
local last = self:bottomRow()
|
|
self.row = self.row + delta
|
|
if self.row < 0 then self.row = last end
|
|
if self.row > last then self.row = 0 end
|
|
-- Coming onto the bottom row from a letter column lands on a target rather
|
|
-- than between two.
|
|
if self:onBottomRow() then
|
|
self.col = (self:bottomTarget() - 1) * 3
|
|
end
|
|
end
|
|
|
|
function NamingScreen:update(_dt)
|
|
local input = self.game and self.game.input
|
|
if not input then return end
|
|
|
|
if input:wasPressed("left") then
|
|
self:moveHorizontal(-1)
|
|
return
|
|
elseif input:wasPressed("right") then
|
|
self:moveHorizontal(1)
|
|
return
|
|
elseif input:wasPressed("up") then
|
|
self:moveVertical(-1)
|
|
return
|
|
elseif input:wasPressed("down") then
|
|
self:moveVertical(1)
|
|
return
|
|
elseif input:wasPressed("select") then
|
|
self:toggleCase()
|
|
return
|
|
elseif input:wasPressed("start") then
|
|
-- .start parks the cursor on END (var1 = $8, var2 = last row).
|
|
self.row = self:bottomRow()
|
|
self.col = 6
|
|
return
|
|
elseif input:wasPressed("b") then
|
|
-- B is delete, not cancel: the only way out is END (or an empty name,
|
|
-- which callers treat as "keep the default").
|
|
self:deleteCharacter()
|
|
return
|
|
elseif input:wasPressed("a") then
|
|
if self:onBottomRow() then
|
|
local target = self:bottomTarget()
|
|
if target == 1 then
|
|
self:toggleCase()
|
|
elseif target == 2 then
|
|
self:deleteCharacter()
|
|
else
|
|
self:accept()
|
|
end
|
|
return
|
|
end
|
|
self:addCharacter(self:characterAt(self.col, self.row))
|
|
-- Filling the last slot does NOT end entry. `.a` is
|
|
-- `call NamingScreen_TryAddCharacter / ret nc`, and
|
|
-- NamingScreen_AdvanceCursor_CheckEndOfString returns CARRY when the buffer
|
|
-- just filled, so the handler falls through into `.start` and parks the
|
|
-- cursor on END (VAR1 $8, VAR2 the bottom row) with the screen still
|
|
-- running (engine/menus/naming_screen.asm:401-410). Only `.end`, i.e. A
|
|
-- pressed on END, stores the entry and sets JUMPTABLE_EXIT_F. Pressing A
|
|
-- on a letter again is already a no-op here, matching
|
|
-- MailComposition_TryAddCharacter's `cp c / ret nc`.
|
|
if #self.text >= self.maxLength then
|
|
self.row = self:bottomRow()
|
|
self.col = 6
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
-- The backdrop: one patterned tile repeated over the whole screen. Without
|
|
-- menu_gfx.lua (older cache) fall back to a flat mid gray, which keeps the
|
|
-- cleared panels readable.
|
|
function NamingScreen:drawBackdrop()
|
|
local G = love.graphics
|
|
local tile = self.tiles.border
|
|
if not tile then
|
|
G.setColor(0.62, 0.62, 0.62, 1)
|
|
G.rectangle("fill", 0, 0, 160, 144)
|
|
G.setColor(1, 1, 1, 1)
|
|
return
|
|
end
|
|
G.setColor(1, 1, 1, 1)
|
|
for ty = 0, Chrome.SCREEN_H - 1 do
|
|
for tx = 0, Chrome.SCREEN_W - 1 do
|
|
G.draw(tile, tx * 8, ty * 8)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The cursor (data/sprite_anims/oam.asm .OAMData_TextEntryCursor and
|
|
-- .OAMData_TextEntryCursorBig). It is not an arrow beside the character: it is
|
|
-- a box drawn AROUND the cell, stamped out of gfx/naming_screen/cursor.2bpp
|
|
-- tile $00 -- one corner carrying a top edge and a left edge -- four times with
|
|
-- X/Y flips. The wide bottom-row bracket repeats tile $01 (top edge alone)
|
|
-- between the two corner pairs.
|
|
--
|
|
-- The frame offsets sit the top and left edges one pixel outside the cell
|
|
-- (`dbsprite -1, -1, 7, 7` is dy/dx = -1), while the flipped copies put the
|
|
-- bottom and right edges on the cell's last pixel row and column. Sprite
|
|
-- coordinates are OAM's, so XCOORD 24 / YCOORD 80 is screen (16, 64): the
|
|
-- first letter's own tile, not the gutter left of it.
|
|
local CURSOR_TILE_H = 8
|
|
|
|
-- Where the bracket sits, in tiles, plus how many tiles wide it is:
|
|
-- .LetterEntries steps XOFFSET by $10 a column and AnimateCursor swaps the row
|
|
-- into YOFFSET (row * $10), so the letter rows land on the character's own
|
|
-- tile. Split out from the draw so a test can read it without a canvas.
|
|
function NamingScreen:cursorTile()
|
|
if self:onBottomRow() then
|
|
return BOTTOM_CURSOR_TX[self:bottomTarget()],
|
|
self:keyboardTop() + self:bottomRow() * 2, BOTTOM_CURSOR_TILES
|
|
end
|
|
return 2 + self.col * 2, self:keyboardTop() + self.row * 2, 1
|
|
end
|
|
|
|
function NamingScreen:drawCursorTile(quad, x, y, flipX, flipY)
|
|
love.graphics.draw(self.tiles.cursor, quad, x, y, 0,
|
|
flipX and -1 or 1, flipY and -1 or 1,
|
|
flipX and 8 or 0, flipY and CURSOR_TILE_H or 0)
|
|
end
|
|
|
|
function NamingScreen:drawCursorBox(tx, ty, tilesWide)
|
|
local G = love.graphics
|
|
local sheet = self.tiles.cursor
|
|
if not sheet then
|
|
-- No extracted cursor art: the shared ▶ in the gutter left of the cell.
|
|
Chrome.cursor(tx - 1, ty)
|
|
return
|
|
end
|
|
G.setColor(1, 1, 1, 1)
|
|
-- Both quads are cut once and kept: this runs every frame the keyboard is
|
|
-- up, and a fresh Quad per draw churns the GC.
|
|
if not self.cursorQuads then
|
|
local sw, sh = sheet:getDimensions()
|
|
local corner = love.graphics.newQuad(0, 0, 8, CURSOR_TILE_H, sw, sh)
|
|
self.cursorQuads = {
|
|
corner = corner,
|
|
edge = sh >= 2 * CURSOR_TILE_H
|
|
and love.graphics.newQuad(0, CURSOR_TILE_H, 8, CURSOR_TILE_H, sw, sh)
|
|
or corner,
|
|
}
|
|
end
|
|
local corner, edge = self.cursorQuads.corner, self.cursorQuads.edge
|
|
local x0, y0 = tx * 8, ty * 8
|
|
if (tilesWide or 1) <= 1 then
|
|
self:drawCursorTile(corner, x0 - 1, y0 - 1, false, false)
|
|
self:drawCursorTile(corner, x0, y0 - 1, true, false)
|
|
self:drawCursorTile(corner, x0 - 1, y0, false, true)
|
|
self:drawCursorTile(corner, x0, y0, true, true)
|
|
return
|
|
end
|
|
local right = x0 + (tilesWide - 1) * 8
|
|
self:drawCursorTile(corner, x0, y0 - 1, false, false)
|
|
self:drawCursorTile(corner, right, y0 - 1, true, false)
|
|
self:drawCursorTile(corner, x0, y0, false, true)
|
|
self:drawCursorTile(corner, right, y0, true, true)
|
|
for i = 1, tilesWide - 2 do
|
|
self:drawCursorTile(edge, x0 + i * 8, y0 - 1, false, false)
|
|
self:drawCursorTile(edge, x0 + i * 8, y0, false, true)
|
|
end
|
|
end
|
|
|
|
function NamingScreen:clearPanel(tx, ty, tw, th)
|
|
local G = love.graphics
|
|
G.setColor(1, 1, 1, 1)
|
|
G.rectangle("fill", tx * 8, ty * 8, tw * 8, th * 8)
|
|
G.setColor(0, 0, 0, 1)
|
|
end
|
|
|
|
-- The entry field: typed characters, then an underline in the next slot, then
|
|
-- middle lines for the rest (NamingScreen_InitNameEntry).
|
|
function NamingScreen:drawEntry(tx, ty)
|
|
local G = love.graphics
|
|
local pen = tx * 8
|
|
local length = #self.text
|
|
for i = 1, self.maxLength do
|
|
if i <= length then
|
|
G.setColor(0, 0, 0, 1)
|
|
Font.draw(self.text:sub(i, i), pen, ty * 8)
|
|
else
|
|
local isNext = i == length + 1
|
|
local glyph = isNext and self.tiles.underLine or self.tiles.middleLine
|
|
if glyph then
|
|
G.setColor(0, 0, 0, 1)
|
|
G.draw(glyph, pen, ty * 8)
|
|
else
|
|
-- No extracted line tiles: draw them. Underline sits on the cell's
|
|
-- baseline, the middle line halfway up, same as the 1bpp art.
|
|
G.setColor(0, 0, 0, 1)
|
|
G.rectangle("fill", pen + 1, ty * 8 + (isNext and 7 or 4), 6, 1)
|
|
end
|
|
end
|
|
pen = pen + 8
|
|
end
|
|
G.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
function NamingScreen:drawPanel()
|
|
local G = love.graphics
|
|
self:drawBackdrop()
|
|
|
|
local headerH = self.isBox and 4 or 6
|
|
self:clearPanel(1, 1, 18, headerH)
|
|
local keyboardTop = self:keyboardTop()
|
|
local keyboardH = self.isBox and 9 or 7
|
|
self:clearPanel(1, keyboardTop, 18, keyboardH)
|
|
-- NamingScreen_ApplyTextInputMode clears the bottom row separately
|
|
-- (hlcoord 1, 16 / lb bc, 1, 18), leaving one patterned row between the
|
|
-- letters and the case/DEL/END strip.
|
|
self:clearPanel(1, 16, 18, 1)
|
|
|
|
-- Header: the standing-down frame of a 16x96 OW sheet (or the first 16x16 of
|
|
-- a mon icon) on the left, and the prompt at (5,2). Quad it: blitting the
|
|
-- whole sheet paints every walk frame down the screen.
|
|
if self.iconImage then
|
|
G.setColor(1, 1, 1, 1)
|
|
local w, h = self.iconImage:getDimensions()
|
|
local quad = love.graphics.newQuad(0, 0, math.min(16, w), math.min(16, h),
|
|
w, h)
|
|
if self.iconColors and GbcPalette.available() then
|
|
GbcPalette.with(self.iconColors,
|
|
function() G.draw(self.iconImage, quad, 16, 16) end)
|
|
else
|
|
G.draw(self.iconImage, quad, 16, 16)
|
|
end
|
|
end
|
|
if self.monName then
|
|
-- Nickname header is two lines: "<MON>'S" then "NICKNAME?".
|
|
Chrome.print(self.monName .. "'S", 5, 2)
|
|
Chrome.print("NICKNAME?", 5, 4)
|
|
else
|
|
Chrome.print(self.prompt, 5, 2)
|
|
end
|
|
self:drawEntry(5, self.isBox and 4 or 6)
|
|
|
|
-- Keyboard rows.
|
|
local grid = self:rows()
|
|
local bottom = self:bottomRow()
|
|
for row = 0, bottom - 1 do
|
|
local line = grid[row + 1] or {}
|
|
for col = 0, 8 do
|
|
local ch = line[col + 1]
|
|
if ch and ch ~= " " and ch ~= "" then
|
|
Chrome.print(ch, 2 + col * 2, keyboardTop + row * 2)
|
|
end
|
|
end
|
|
end
|
|
local labels = self.lower and BOTTOM_LOWER_LABELS or BOTTOM_UPPER_LABELS
|
|
local bottomY = keyboardTop + bottom * 2
|
|
for i, label in ipairs(labels) do
|
|
Chrome.print(label, BOTTOM_LABEL_TX[i], bottomY)
|
|
end
|
|
|
|
self:drawCursorBox(self:cursorTile())
|
|
G.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
function NamingScreen:draw()
|
|
self:drawPanel()
|
|
end
|
|
|
|
function NamingScreen:drawWidescreen(winW, winH)
|
|
local G = love.graphics
|
|
-- The naming screen's own patterned backdrop is the surround: extend it to
|
|
-- the window edges so a widescreen boot has no black pillarbox.
|
|
self:drawBackdrop()
|
|
local scale = Chrome.fitScale(winW, winH)
|
|
local ox, oy = Chrome.fitOrigin(winW, winH, scale)
|
|
G.setColor(0.62, 0.62, 0.62, 1)
|
|
G.rectangle("fill", 0, 0, winW, winH)
|
|
G.setColor(1, 1, 1, 1)
|
|
if self.tiles.border then
|
|
local tilesX = math.ceil(winW / (8 * scale))
|
|
local tilesY = math.ceil(winH / (8 * scale))
|
|
G.push()
|
|
G.scale(scale, scale)
|
|
for ty = 0, tilesY do
|
|
for tx = 0, tilesX do
|
|
G.draw(self.tiles.border, tx * 8, ty * 8)
|
|
end
|
|
end
|
|
G.pop()
|
|
end
|
|
G.push()
|
|
G.translate(ox, oy)
|
|
G.scale(scale, scale)
|
|
self:drawPanel()
|
|
G.pop()
|
|
end
|
|
|
|
-- Exported for tests: the character the cursor is over right now.
|
|
function NamingScreen:cursorCharacter()
|
|
if self:onBottomRow() then
|
|
local target = self:bottomTarget()
|
|
return ({ "CASE", "DEL", "END" })[target]
|
|
end
|
|
return self:characterAt(self.col, self.row)
|
|
end
|
|
|
|
NamingScreen.NAME_INPUT_UPPER = NAME_INPUT_UPPER
|
|
NamingScreen.NAME_INPUT_LOWER = NAME_INPUT_LOWER
|
|
NamingScreen.BOX_INPUT_UPPER = BOX_INPUT_UPPER
|
|
NamingScreen.BOX_INPUT_LOWER = BOX_INPUT_LOWER
|
|
|
|
return NamingScreen
|