-- DESCRIPTION: Two-button recursive keyboard experiment local ROWS = { { "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" } } -- Spatial Rank - Halving must split the keyboard left/right on screen, so keys sort by their -- centered column position, with the row index only breaking ties. local KEYS = {} local keyRank = {} for rowIndex, row in ipairs(ROWS) do for columnIndex, key in ipairs(row) do KEYS[#KEYS + 1] = key keyRank[key] = (columnIndex - (#row + 1) / 2) * 10 + rowIndex end end table.sort(KEYS, function(a, b) return keyRank[a] < keyRank[b] end) local MAX_TEXT_BYTES = 80 local text = "" local candidates = {} local history = {} local uppercase = false local needsDraw = true local firstDraw = true local function copyKeys(source, first, last) local result = {} for index = first, last do result[#result + 1] = source[index] end return result end local function resetCandidates() candidates = copyKeys(KEYS, 1, #KEYS) history = {} end local function halves() local midpoint = math.ceil(#candidates / 2) return copyKeys(candidates, 1, midpoint), copyKeys(candidates, midpoint + 1, #candidates) end local function append(value) if #text + #value <= MAX_TEXT_BYTES then text = text .. value end end local function chooseHalf(left) local leftHalf, rightHalf = halves() history[#history + 1] = candidates candidates = left and leftHalf or rightHalf if #candidates == 1 then local letter = candidates[1] append(uppercase and letter or letter:lower()) resetCandidates() end needsDraw = true end local function undo() if #history == 0 then sys.exit() return end candidates = history[#history] history[#history] = nil needsDraw = true end -- Side Lookup - Rendering needs per-key shading, but candidates are stored in layout order. local function sides() local leftHalf, rightHalf = halves() local result = {} for _, key in ipairs(leftHalf) do result[key] = "left" end for _, key in ipairs(rightHalf) do result[key] = "right" end return result end local function drawKeyboard(width, top, bottom) local gap = 6 local margin = 16 local keyWidth = math.floor((width - margin * 2 - gap * 9) / 10) local keyHeight = math.min(56, math.floor((bottom - top - gap * 2) / 3)) local side = sides() for rowIndex, row in ipairs(ROWS) do local rowWidth = #row * keyWidth + (#row - 1) * gap local startX = math.floor((width - rowWidth) / 2) local y = top + (rowIndex - 1) * (keyHeight + gap) for columnIndex, key in ipairs(row) do local keySide = side[key] if keySide then local x = startX + (columnIndex - 1) * (keyWidth + gap) local shaded = keySide == "right" gui.fillRoundedRect(x, y, keyWidth, keyHeight, 4, shaded and COLOR_DARK_GRAY or COLOR_WHITE) gui.drawRoundedRect(x, y, keyWidth, keyHeight, 1, 4, COLOR_BLACK) local label = uppercase and key or key:lower() local textX = x + math.floor((keyWidth - gui.getTextWidth(FONT_NOTOSANS_16, label, STYLE_BOLD)) / 2) gui.drawText(FONT_NOTOSANS_16, textX, y + math.floor(keyHeight / 2) - 10, label, shaded and COLOR_WHITE or COLOR_BLACK, STYLE_BOLD) end end end end local function render() local width = gui.width() local height = gui.height() local margin = 20 gui.clear() gui.drawCenteredText(FONT_UI_12, 24, "Binary Keyboard", COLOR_BLACK, STYLE_BOLD) gui.drawRoundedRect(margin, 65, width - margin * 2, 82, 2, 8, COLOR_BLACK) local visibleText = text == "" and "Type with the side buttons" or text:sub(-38) gui.drawText(FONT_UI_12, margin + 12, 96, visibleText, COLOR_BLACK, STYLE_NORMAL) gui.drawCenteredText(FONT_UI_10, 170, "UP = plain left DOWN = shaded right", COLOR_BLACK, STYLE_NORMAL) drawKeyboard(width, 195, height - 90) gui.drawButtonHints(#history == 0 and "Exit" or "Undo", "Space", "Delete", uppercase and "lower" or "UPPER") gui.refresh(firstDraw and REFRESH_HALF or REFRESH_FAST) firstDraw = false needsDraw = false end function init() text = "" uppercase = false firstDraw = true needsDraw = true resetCandidates() end function draw() if input.wasPressed("up") then chooseHalf(true) elseif input.wasPressed("down") then chooseHalf(false) elseif input.wasPressed("back") then undo() elseif input.wasPressed("confirm") then append(" ") resetCandidates() needsDraw = true elseif input.wasPressed("left") then if #text > 0 then text = text:sub(1, -2) end resetCandidates() needsDraw = true elseif input.wasPressed("right") then uppercase = not uppercase needsDraw = true end if needsDraw then render() end end