189 lines
6.0 KiB
Lua
189 lines
6.0 KiB
Lua
local ui = require("ui")
|
|
|
|
local M = {}
|
|
|
|
local KEY_W, KEY_H, KEY_GAP, ROW_GAP = 26, 30, 4, 5
|
|
local SIDE_W, MODE_W, SPACE_W, OK_W = 40, 54, 174, 58
|
|
local PAGES = {
|
|
lower = {"qwertyuiop", "asdfghjkl", "zxcvbnm"},
|
|
upper = {"QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"},
|
|
numbers = {"1234567890", "-/:;()$&@", ".,?!'"},
|
|
symbols = {"[]{}#%^*+=", "_\\|~<>$&@", ".,?!'"},
|
|
}
|
|
|
|
local function chars(page, row)
|
|
return PAGES[page][row]
|
|
end
|
|
|
|
local function rowWidth(count)
|
|
return count * KEY_W + (count - 1) * KEY_GAP
|
|
end
|
|
|
|
local function centeredX(node, width)
|
|
return node.rect.x + (node.rect.w - width) // 2
|
|
end
|
|
|
|
local function thirdRow(node)
|
|
local value = chars(node.page, 3)
|
|
local width = SIDE_W * 2 + KEY_GAP * 2 + rowWidth(#value)
|
|
return value, centeredX(node, width)
|
|
end
|
|
|
|
local function keyAt(node, x, y)
|
|
local localY = y - node.rect.y
|
|
if localY < 0 then return end
|
|
local row = localY // (KEY_H + ROW_GAP) + 1
|
|
if row > 4 or localY % (KEY_H + ROW_GAP) >= KEY_H then return end
|
|
|
|
if row <= 2 then
|
|
local value = chars(node.page, row)
|
|
local localX = x - centeredX(node, rowWidth(#value))
|
|
if localX < 0 then return end
|
|
local column = localX // (KEY_W + KEY_GAP) + 1
|
|
if column <= #value and localX % (KEY_W + KEY_GAP) < KEY_W then
|
|
return (row - 1) * 10 + column, "char", value:sub(column, column)
|
|
end
|
|
return
|
|
end
|
|
|
|
if row == 3 then
|
|
local value, start = thirdRow(node)
|
|
local localX = x - start
|
|
if localX < 0 then return end
|
|
if localX < SIDE_W then
|
|
return 90, (node.page == "lower" or node.page == "upper") and "shift" or "symbols"
|
|
end
|
|
localX = localX - SIDE_W - KEY_GAP
|
|
local width = rowWidth(#value)
|
|
if localX >= 0 and localX < width then
|
|
local column = localX // (KEY_W + KEY_GAP) + 1
|
|
if localX % (KEY_W + KEY_GAP) < KEY_W then
|
|
return 20 + column, "char", value:sub(column, column)
|
|
end
|
|
return
|
|
end
|
|
localX = localX - width - KEY_GAP
|
|
if localX >= 0 and localX < SIDE_W then return 91, "backspace" end
|
|
return
|
|
end
|
|
|
|
local width = MODE_W + SPACE_W + OK_W + KEY_GAP * 2
|
|
local localX = x - centeredX(node, width)
|
|
if localX < 0 then return end
|
|
if localX < MODE_W then return 100, "mode" end
|
|
localX = localX - MODE_W - KEY_GAP
|
|
if localX >= 0 and localX < SPACE_W then return 101, "space" end
|
|
localX = localX - SPACE_W - KEY_GAP
|
|
if localX >= 0 and localX < OK_W then return 102, "submit" end
|
|
end
|
|
|
|
local function drawArrow(x, y, width, color, down)
|
|
local middle = x + width // 2
|
|
if down then
|
|
gui.drawLine(middle, y + 8, middle, y + 20, color)
|
|
gui.drawLine(middle - 5, y + 15, middle, y + 20, color)
|
|
gui.drawLine(middle, y + 20, middle + 5, y + 15, color)
|
|
else
|
|
gui.drawLine(middle, y + 9, middle, y + 21, color)
|
|
gui.drawLine(middle - 5, y + 14, middle, y + 9, color)
|
|
gui.drawLine(middle, y + 9, middle + 5, y + 14, color)
|
|
end
|
|
end
|
|
|
|
local function drawKey(node, id, label, x, y, width)
|
|
local pressed = node.pressed and node.activeKey == id
|
|
local gradient = pressed and node.press_gradient or node.gradient
|
|
local color = pressed and (node.press_color or node.bg) or node.color
|
|
gui.roundRect(x, y, width, KEY_H, node.radius, node.bg,
|
|
gradient[1], gradient[2], color)
|
|
if label == "shift" then
|
|
drawArrow(x, y, width, color, node.page == "upper")
|
|
else
|
|
gui.drawText(label, x + (width - gui.getTextWidth(label)) // 2,
|
|
y + (KEY_H - gui.getFontHeight()) // 2, color, gradient[2])
|
|
end
|
|
end
|
|
|
|
local function paint(node)
|
|
local y = node.rect.y
|
|
for row = 1, 2 do
|
|
local value = chars(node.page, row)
|
|
local x = centeredX(node, rowWidth(#value))
|
|
for column = 1, #value do
|
|
drawKey(node, (row - 1) * 10 + column, value:sub(column, column), x, y, KEY_W)
|
|
x = x + KEY_W + KEY_GAP
|
|
end
|
|
y = y + KEY_H + ROW_GAP
|
|
end
|
|
|
|
local value, x = thirdRow(node)
|
|
drawKey(node, 90, (node.page == "lower" or node.page == "upper") and "shift" or
|
|
(node.page == "numbers" and "#+=" or "123"), x, y, SIDE_W)
|
|
x = x + SIDE_W + KEY_GAP
|
|
for column = 1, #value do
|
|
drawKey(node, 20 + column, value:sub(column, column), x, y, KEY_W)
|
|
x = x + KEY_W + KEY_GAP
|
|
end
|
|
drawKey(node, 91, "<-", x, y, SIDE_W)
|
|
|
|
y = y + KEY_H + ROW_GAP
|
|
local width = MODE_W + SPACE_W + OK_W + KEY_GAP * 2
|
|
x = centeredX(node, width)
|
|
drawKey(node, 100, (node.page == "lower" or node.page == "upper") and "123" or "ABC", x, y, MODE_W)
|
|
x = x + MODE_W + KEY_GAP
|
|
drawKey(node, 101, "space", x, y, SPACE_W)
|
|
x = x + SPACE_W + KEY_GAP
|
|
drawKey(node, 102, "OK", x, y, OK_W)
|
|
end
|
|
|
|
local function down(node, x, y)
|
|
node.activeKey = keyAt(node, x, y)
|
|
end
|
|
|
|
local function changed(node)
|
|
if node.on_change then node.on_change(node.value) end
|
|
end
|
|
|
|
local function press(node, x, y)
|
|
local id, action, char = keyAt(node, x, y)
|
|
if not id or id ~= node.activeKey then return end
|
|
|
|
if action == "char" then
|
|
if #node.value < node.max_length then node.value = node.value .. char; changed(node) end
|
|
elseif action == "shift" then
|
|
node.page = node.page == "lower" and "upper" or "lower"
|
|
node:invalidate()
|
|
elseif action == "symbols" then
|
|
node.page = node.page == "numbers" and "symbols" or "numbers"
|
|
node:invalidate()
|
|
elseif action == "mode" then
|
|
node.page = (node.page == "lower" or node.page == "upper") and "numbers" or "lower"
|
|
node:invalidate()
|
|
elseif action == "backspace" then
|
|
node.value = node.value:sub(1, -2)
|
|
changed(node)
|
|
elseif action == "space" then
|
|
if #node.value < node.max_length then node.value = node.value .. " "; changed(node) end
|
|
elseif action == "submit" and node.on_submit then
|
|
node.on_submit(node.value)
|
|
end
|
|
end
|
|
|
|
-- One custom-painted node keeps a full keyboard from retaining dozens of component tables.
|
|
function M.new(spec)
|
|
spec = spec or {}
|
|
return ui.box{
|
|
h = 4 * KEY_H + 3 * ROW_GAP,
|
|
value = spec.value or "",
|
|
max_length = spec.max_length or 64,
|
|
page = "lower",
|
|
on_change = spec.on_change,
|
|
on_submit = spec.on_submit,
|
|
paint = paint,
|
|
on_down = down,
|
|
on_press = press,
|
|
}
|
|
end
|
|
|
|
return M
|