mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-28 09:54:43 +02:00
283 lines
10 KiB
Lua
Executable File
283 lines
10 KiB
Lua
Executable File
-- Controller-driven on-screen keyboard (OSK) for handhelds and gamepads.
|
|
-- Allows typing URLs (mod index, repositories), mod search queries, and custom names
|
|
-- without needing a physical keyboard.
|
|
|
|
local Theme = require("src.ui.kit.Theme")
|
|
local PAL = Theme.PAL
|
|
local Kit = nil
|
|
local function getKit()
|
|
if not Kit then Kit = require("src.ui.kit.Kit") end
|
|
return Kit
|
|
end
|
|
|
|
local VirtualKeyboard = {
|
|
active = false,
|
|
text = "",
|
|
title = "Enter Text",
|
|
targetId = nil,
|
|
onDone = nil,
|
|
row = 1,
|
|
col = 1,
|
|
mode = 1, -- 1: lower, 2: upper, 3: symbols
|
|
}
|
|
|
|
local ROWS_LOWER = {
|
|
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=" },
|
|
{ "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "/", ":" },
|
|
{ "a", "s", "d", "f", "g", "h", "j", "k", "l", ".", "_", "@" },
|
|
{ "z", "x", "c", "v", "b", "n", "m", "?", "!", "&", "%", "#" },
|
|
{ { id = "shift", label = "Shift", w = 2 }, { id = "sym", label = "Sym", w = 2 }, { id = "space", label = "Space", w = 4 }, { id = "back", label = "Bksp", w = 2 }, { id = "done", label = "Done", w = 2 } },
|
|
{ { id = "url_https", label = "https://", w = 3 }, { id = "url_http", label = "http://", w = 2 }, { id = "url_json", label = ".json", w = 2 }, { id = "url_zip", label = ".zip", w = 2 }, { id = "clear", label = "Clear", w = 3 } },
|
|
}
|
|
|
|
local ROWS_UPPER = {
|
|
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=" },
|
|
{ "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "/", ":" },
|
|
{ "A", "S", "D", "F", "G", "H", "J", "K", "L", ".", "_", "@" },
|
|
{ "Z", "X", "C", "V", "B", "N", "M", "?", "!", "&", "%", "#" },
|
|
{ { id = "shift", label = "shift", w = 2 }, { id = "sym", label = "Sym", w = 2 }, { id = "space", label = "Space", w = 4 }, { id = "back", label = "Bksp", w = 2 }, { id = "done", label = "Done", w = 2 } },
|
|
{ { id = "url_https", label = "https://", w = 3 }, { id = "url_http", label = "http://", w = 2 }, { id = "url_json", label = ".json", w = 2 }, { id = "url_zip", label = ".zip", w = 2 }, { id = "clear", label = "Clear", w = 3 } },
|
|
}
|
|
|
|
local ROWS_SYM = {
|
|
{ "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+" },
|
|
{ "~", "`", "{", "}", "[", "]", "|", "\\", ";", ":", "'", "\"" },
|
|
{ "<", ">", ",", ".", "/", "?", "=", "-", "*", "/", "\\", "~" },
|
|
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", ".", "=" },
|
|
{ { id = "shift", label = "ABC", w = 2 }, { id = "sym", label = "123", w = 2 }, { id = "space", label = "Space", w = 4 }, { id = "back", label = "Bksp", w = 2 }, { id = "done", label = "Done", w = 2 } },
|
|
{ { id = "url_https", label = "https://", w = 3 }, { id = "url_http", label = "http://", w = 2 }, { id = "url_json", label = ".json", w = 2 }, { id = "url_zip", label = ".zip", w = 2 }, { id = "clear", label = "Clear", w = 3 } },
|
|
}
|
|
|
|
local function isLinuxHandheld()
|
|
return os.getenv("HANDHELD") == "1"
|
|
or os.getenv("PORTMASTER") == "1"
|
|
or os.getenv("POKEPORT_HANDHELD") == "1"
|
|
or os.getenv("TRIMUI") == "1"
|
|
or os.getenv("MUOS") == "1"
|
|
or os.getenv("KNULLI") == "1"
|
|
or os.getenv("POKEPORT_SBC") == "1"
|
|
or os.getenv("ANBERNIC") == "1"
|
|
end
|
|
|
|
function VirtualKeyboard.isSupported()
|
|
return isLinuxHandheld()
|
|
end
|
|
|
|
function VirtualKeyboard.open(opts)
|
|
if not isLinuxHandheld() then return false end
|
|
opts = opts or {}
|
|
VirtualKeyboard.active = true
|
|
VirtualKeyboard.text = tostring(opts.text or "")
|
|
VirtualKeyboard.title = opts.title or "Enter Text"
|
|
VirtualKeyboard.targetId = opts.targetId
|
|
VirtualKeyboard.onDone = opts.onDone
|
|
VirtualKeyboard.row = 1
|
|
VirtualKeyboard.col = 1
|
|
VirtualKeyboard.mode = 1
|
|
return true
|
|
end
|
|
|
|
function VirtualKeyboard.close(confirmed)
|
|
if not VirtualKeyboard.active then return end
|
|
local txt = VirtualKeyboard.text
|
|
local cb = VirtualKeyboard.onDone
|
|
VirtualKeyboard.active = false
|
|
VirtualKeyboard.targetId = nil
|
|
VirtualKeyboard.onDone = nil
|
|
if cb then
|
|
cb(txt, confirmed == true)
|
|
end
|
|
end
|
|
|
|
local function currentRows()
|
|
if VirtualKeyboard.mode == 2 then return ROWS_UPPER end
|
|
if VirtualKeyboard.mode == 3 then return ROWS_SYM end
|
|
return ROWS_LOWER
|
|
end
|
|
|
|
local function triggerKey(key)
|
|
if type(key) == "string" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. key
|
|
elseif type(key) == "table" then
|
|
if key.id == "shift" then
|
|
VirtualKeyboard.mode = (VirtualKeyboard.mode == 2) and 1 or 2
|
|
elseif key.id == "sym" then
|
|
VirtualKeyboard.mode = (VirtualKeyboard.mode == 3) and 1 or 3
|
|
elseif key.id == "space" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. " "
|
|
elseif key.id == "back" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text:sub(1, -2)
|
|
elseif key.id == "clear" then
|
|
VirtualKeyboard.text = ""
|
|
elseif key.id == "done" then
|
|
VirtualKeyboard.close(true)
|
|
elseif key.id == "url_https" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. "https://"
|
|
elseif key.id == "url_http" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. "http://"
|
|
elseif key.id == "url_json" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. ".json"
|
|
elseif key.id == "url_zip" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. ".zip"
|
|
end
|
|
end
|
|
end
|
|
|
|
function VirtualKeyboard.gamepadpressed(button)
|
|
if not VirtualKeyboard.active then return false end
|
|
|
|
local rows = currentRows()
|
|
local r = VirtualKeyboard.row
|
|
local c = VirtualKeyboard.col
|
|
|
|
if button == "dpup" then
|
|
VirtualKeyboard.row = math.max(1, r - 1)
|
|
local maxC = #rows[VirtualKeyboard.row]
|
|
if VirtualKeyboard.col > maxC then VirtualKeyboard.col = maxC end
|
|
return true
|
|
elseif button == "dpdown" then
|
|
VirtualKeyboard.row = math.min(#rows, r + 1)
|
|
local maxC = #rows[VirtualKeyboard.row]
|
|
if VirtualKeyboard.col > maxC then VirtualKeyboard.col = maxC end
|
|
return true
|
|
elseif button == "dpleft" then
|
|
VirtualKeyboard.col = math.max(1, c - 1)
|
|
return true
|
|
elseif button == "dpright" then
|
|
VirtualKeyboard.col = math.min(#rows[r], c + 1)
|
|
return true
|
|
elseif button == "a" then
|
|
local key = rows[r] and rows[r][c]
|
|
if key then triggerKey(key) end
|
|
return true
|
|
elseif button == "b" then
|
|
VirtualKeyboard.close(false)
|
|
return true
|
|
elseif button == "x" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text:sub(1, -2)
|
|
return true
|
|
elseif button == "y" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. " "
|
|
return true
|
|
elseif button == "leftshoulder" or button == "rightshoulder" then
|
|
VirtualKeyboard.mode = (VirtualKeyboard.mode % 3) + 1
|
|
local maxC = #currentRows()[VirtualKeyboard.row]
|
|
if VirtualKeyboard.col > maxC then VirtualKeyboard.col = maxC end
|
|
return true
|
|
elseif button == "start" then
|
|
VirtualKeyboard.close(true)
|
|
return true
|
|
end
|
|
|
|
return true -- consume all input while keyboard modal is up
|
|
end
|
|
|
|
function VirtualKeyboard.keypressed(key)
|
|
if not VirtualKeyboard.active then return false end
|
|
if key == "escape" then
|
|
VirtualKeyboard.close(false)
|
|
return true
|
|
elseif key == "return" then
|
|
VirtualKeyboard.close(true)
|
|
return true
|
|
elseif key == "backspace" then
|
|
VirtualKeyboard.text = VirtualKeyboard.text:sub(1, -2)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function VirtualKeyboard.textinput(text)
|
|
if not VirtualKeyboard.active then return false end
|
|
VirtualKeyboard.text = VirtualKeyboard.text .. text
|
|
return true
|
|
end
|
|
|
|
function VirtualKeyboard.draw(m)
|
|
if not VirtualKeyboard.active then return end
|
|
|
|
local Kit = getKit()
|
|
local s = m and m.s or Kit.scale or 1
|
|
local W = (m and m.W) or (love.graphics and love.graphics.getWidth()) or 640
|
|
local H = (m and m.H) or (love.graphics and love.graphics.getHeight()) or 480
|
|
|
|
-- Scrim background
|
|
Theme.fill(0, 0, W, H, PAL.bg, 0.92)
|
|
Kit.blockClicks = true
|
|
|
|
local pad = math.floor(12 * s)
|
|
local modalW = math.floor(math.min(540 * s, W - 20 * s))
|
|
local modalH = math.floor(math.min(360 * s, H - 20 * s))
|
|
local px = math.floor((W - modalW) / 2)
|
|
local py = math.floor((H - modalH) / 2)
|
|
|
|
Kit.card(px, py, modalW, modalH, true)
|
|
|
|
local cy = py + pad
|
|
-- Title & hint
|
|
Kit.text("button", VirtualKeyboard.title, px + pad, cy, PAL.heading)
|
|
Kit.textRight("micro", "L1/R1: Mode X: Bksp Y: Space Start: Done B: Cancel",
|
|
px + modalW - pad, cy + 2 * s, PAL.muted)
|
|
|
|
cy = cy + Kit.textHeight("button") + math.floor(8 * s)
|
|
|
|
-- Preview textfield box
|
|
local fieldH = math.floor(32 * s)
|
|
Theme.fillRounded(px + pad, cy, modalW - 2 * pad, fieldH, PAL.bg, 1)
|
|
Theme.strokeRounded(px + pad, cy, modalW - 2 * pad, fieldH, PAL.lineStrong, 1, 2)
|
|
|
|
local shown = Kit.ellipsizeLeft("mono", VirtualKeyboard.text .. "_", modalW - 4 * pad)
|
|
Kit.text("mono", shown, px + pad + math.floor(8 * s), cy + math.floor(7 * s), PAL.heading)
|
|
|
|
cy = cy + fieldH + math.floor(10 * s)
|
|
|
|
-- Keyboard Rows
|
|
local rows = currentRows()
|
|
local rowH = math.floor(30 * s)
|
|
local gap = math.floor(4 * s)
|
|
local availableW = modalW - 2 * pad
|
|
|
|
for rIdx, rData in ipairs(rows) do
|
|
local totalUnits = 0
|
|
for _, key in ipairs(rData) do
|
|
totalUnits = totalUnits + (type(key) == "table" and (key.w or 1) or 1)
|
|
end
|
|
|
|
local unitW = (availableW - (gap * (#rData - 1))) / totalUnits
|
|
local kx = px + pad
|
|
|
|
for cIdx, key in ipairs(rData) do
|
|
local units = type(key) == "table" and (key.w or 1) or 1
|
|
local kw = math.floor(unitW * units)
|
|
local isSelected = (VirtualKeyboard.row == rIdx and VirtualKeyboard.col == cIdx)
|
|
|
|
local label = type(key) == "table" and key.label or key
|
|
local isSpecial = type(key) == "table"
|
|
|
|
-- Click / touch detection
|
|
if Kit.press(kx, cy, kw, rowH) then
|
|
VirtualKeyboard.row = rIdx
|
|
VirtualKeyboard.col = cIdx
|
|
triggerKey(key)
|
|
end
|
|
|
|
if isSelected then
|
|
Theme.fillRounded(kx, cy, kw, rowH, PAL.ink, 1)
|
|
Kit.textCenterBold("small", label, kx, cy + math.floor(6 * s), kw, PAL.inverse)
|
|
else
|
|
Theme.fillRounded(kx, cy, kw, rowH, isSpecial and PAL.raised or PAL.surface, 1)
|
|
Theme.strokeRounded(kx, cy, kw, rowH, PAL.line, Theme.A.hairline, 1)
|
|
Kit.textCenter("small", label, kx, cy + math.floor(6 * s), kw, isSpecial and PAL.heading or PAL.text)
|
|
end
|
|
|
|
kx = kx + kw + gap
|
|
end
|
|
|
|
cy = cy + rowH + gap
|
|
end
|
|
|
|
Kit.blockClicks = false
|
|
end
|
|
|
|
return VirtualKeyboard
|