-- keyboard component for the playdate -- from Orllewin -- https://publish.obsidian.md/orllewin/computers/playdate/Text+Input+Screen class('TextInputScreen').extends(playdate.graphics.sprite) local gfx = playdate.graphics local chars = { "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"} local 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"} local xCoords = { 38, 74, 110, 146, 182, 218, 254, 290, 326, 362, 38, 74, 110, 146, 182, 218, 254, 290, 326, 362, 56, 92, 128, 164, 200, 236, 272, 308, 344, 74, 110,146, 182, 218, 254, 290} local yCoords = {100, 125, 150, 175} local row1CharCount = 10 local row2CharCount = 10 local row3CharCount = 9 local row4CharCount = 8 local row5CharCount = 2 local keyRow1Y = 100 local keyRow2Y = 125 local keyRow3Y = 150 local keyRow4Y = 175 local focusWidth = 26 local focusHeight = 30 local margin = 20 function TextInputScreen:init() TextInputScreen.super.init(self) self.lower = true self.showing = false self.input = "" self.defaultFont = gfx.getFont() local pixarlmed = gfx.font.new("Fonts/pixarlmed") gfx.setFont(pixarlmed) self.backgroundImageLower = playdate.graphics.image.new(400, 240) gfx.pushContext(self.backgroundImageLower) playdate.graphics.setColor(playdate.graphics.kColorWhite) gfx.fillRect(0, 0, 400, 240) playdate.graphics.setColor(playdate.graphics.kColorBlack) for n=1,36 do local y = 0 if n <= 10 then y = keyRow1Y elseif n <= 20 then y = keyRow2Y elseif n <= 29 then y = keyRow3Y else y = keyRow4Y end gfx.drawTextAligned(chars[n], xCoords[n], y, kTextAlignment.center) end gfx.drawTextAligned("del", 343, 175, kTextAlignment.center) gfx.drawTextAligned("space", 200, 205, kTextAlignment.center) gfx.drawTextAligned("done", 335, 205, kTextAlignment.center) gfx.popContext() self.backgroundImageUpper = playdate.graphics.image.new(400, 240) gfx.pushContext(self.backgroundImageUpper) playdate.graphics.setColor(playdate.graphics.kColorWhite) gfx.fillRect(0, 0, 400, 240) playdate.graphics.setColor(playdate.graphics.kColorBlack) for n=1,36 do local y = 0 if n <= 10 then y = keyRow1Y elseif n <= 20 then y = keyRow2Y elseif n <= 29 then y = keyRow3Y else y = keyRow4Y end gfx.drawTextAligned(upper[n], xCoords[n], y, kTextAlignment.center) end gfx.drawTextAligned("del", 343, 175, kTextAlignment.center) gfx.drawTextAligned("space", 200, 205, kTextAlignment.center) gfx.drawTextAligned("done", 335, 205, kTextAlignment.center) gfx.popContext() self:setImage(self.backgroundImageLower) self:moveTo(200, 120) self.rowIndex = 3 self.letterIndex = 5 local focusImage = playdate.graphics.image.new(focusWidth, focusHeight) gfx.pushContext(focusImage) playdate.graphics.setColor(playdate.graphics.kColorBlack) gfx.drawRoundRect(0, 0, focusWidth, focusHeight, 7) gfx.popContext() self.focusSprite = gfx.sprite.new(focusImage) local spacebarWidth = 136 local spacebarHeight = focusHeight local spacebarImage = playdate.graphics.image.new(spacebarWidth, 30) gfx.pushContext(spacebarImage) playdate.graphics.setColor(playdate.graphics.kColorBlack) gfx.drawRoundRect(0, 0, spacebarWidth, spacebarHeight, 7) gfx.popContext() self.spacebarSprite = gfx.sprite.new(spacebarImage) self.spacebarSprite:moveTo(200, 216) local deleteWidth = 50 local deleteHeight = focusHeight local deleteImage = playdate.graphics.image.new(deleteWidth, 30) gfx.pushContext(deleteImage) playdate.graphics.setColor(playdate.graphics.kColorBlack) gfx.drawRoundRect(0, 0, deleteWidth, deleteHeight, 7) gfx.popContext() self.deleteSprite = gfx.sprite.new(deleteImage) self.deleteSprite:moveTo(345, keyRow4Y + (focusHeight/2) - 5) local doneWidth = 63 local doneHeight = focusHeight local doneImage = playdate.graphics.image.new(doneWidth, 30) gfx.pushContext(doneImage) playdate.graphics.setColor(playdate.graphics.kColorBlack) gfx.drawRoundRect(0, 0, doneWidth, doneHeight, 7) gfx.popContext() self.doneSprite = gfx.sprite.new(doneImage) self.doneSprite:moveTo(336, 216) local inputImage = playdate.graphics.image.new(1, 1) self.inputSprite = gfx.sprite.new(inputImage) self.inputSprite:moveTo(200, 67) end function TextInputScreen:isShowing() return self.showing end function TextInputScreen:push(message, onDone) self.onDone = onDone self:add() self.focusSprite:add() self.inputSprite:add() self:updateFocusCaret() if message ~= nil then local messageImage = gfx.imageWithText(message, 400, 100) self.messageSprite = gfx.sprite.new(messageImage) self.messageSprite:moveTo(200, 25) self.messageSprite:add() end self.inputHandler = { cranked = function(change, acceleratedChange) end, leftButtonDown = function() self.letterIndex = math.max(1, self.letterIndex - 1) self:updateFocusCaret() end, rightButtonDown = function() self.letterIndex += 1 if self.rowIndex == 1 then if self.letterIndex > row1CharCount then self.letterIndex = row1CharCount end elseif self.rowIndex == 2 then if self.letterIndex > row2CharCount then self.letterIndex = row2CharCount end elseif self.rowIndex == 3 then if self.letterIndex > row3CharCount then self.letterIndex = row3CharCount end elseif self.rowIndex == 4 then if self.letterIndex > row4CharCount then self.letterIndex = row4CharCount end end self:updateFocusCaret() end, upButtonDown = function() self.rowIndex = math.max(1, self.rowIndex - 1) if self.rowIndex == 4 then self.letterIndex = 4 end self:updateFocusCaret() end, downButtonDown = function() self.rowIndex = math.min(5, self.rowIndex + 1) if self.rowIndex == 1 then if self.letterIndex > row1CharCount then self.letterIndex = row1CharCount end elseif self.rowIndex == 2 then if self.letterIndex > row2CharCount then self.letterIndex = row2CharCount end elseif self.rowIndex == 3 then if self.letterIndex > row3CharCount then self.letterIndex = row3CharCount end elseif self.rowIndex == 4 then if self.letterIndex > row4CharCount then self.letterIndex = row4CharCount end elseif self.rowIndex == 5 then --space bar self.letterIndex = 1 end self:updateFocusCaret() end, BButtonDown = function() self.lower = not self.lower if self.lower then self:setImage(self.backgroundImageLower) else self:setImage(self.backgroundImageUpper) end end, AButtonDown = function() self.focusSprite:moveBy(0, 1) self.spacebarSprite:moveBy(0, 1) self.deleteSprite:moveBy(0, 1) if self.rowIndex == 1 then if self.lower then self.input = self.input .. chars[self.letterIndex] else self.input = self.input .. upper[self.letterIndex] end elseif self.rowIndex == 2 then if self.lower then self.input = self.input .. chars[self.letterIndex + 10] else self.input = self.input .. upper[self.letterIndex + 10] end elseif self.rowIndex == 3 then if self.lower then self.input = self.input .. chars[self.letterIndex + 20] else self.input = self.input .. upper[self.letterIndex + 20] end elseif self.rowIndex == 4 then if self.letterIndex == row4CharCount then if string.len(self.input) > 0 then self.input = self.input:sub(1, -2) end else if self.lower then self.input = self.input .. chars[self.letterIndex + 29] else self.input = self.input .. upper[self.letterIndex + 29] end end elseif self.rowIndex == 5 then if self.letterIndex == 1 then self.input = self.input .. " " elseif self.letterIndex == 2 then self:pop() return end end print("Input: " .. self.input) if string.len(self.input) > 0 then local inputImage = gfx.imageWithText(self.input, 400, 100) self.inputSprite:setImage(inputImage) self.inputSprite:add() else self.inputSprite:remove() end end, BButtonUp = function() end, AButtonUp = function() self.focusSprite:moveBy(0, -1) self.spacebarSprite:moveBy(0, -1) self.deleteSprite:moveBy(0, -1) end } playdate.inputHandlers.push(self.inputHandler) self.showing = true end function TextInputScreen:updateFocusCaret() if self.rowIndex == 1 then self.focusSprite:moveTo(xCoords[self.letterIndex], yCoords[self.rowIndex] + (focusHeight/2) - 5) self.focusSprite:add() self.spacebarSprite:remove() self.deleteSprite:remove() elseif self.rowIndex == 2 then self.focusSprite:moveTo(xCoords[self.letterIndex + 10], yCoords[self.rowIndex] + (focusHeight/2) - 5) self.focusSprite:add() self.spacebarSprite:remove() self.deleteSprite:remove() elseif self.rowIndex == 3 then self.focusSprite:moveTo(xCoords[self.letterIndex + 20], yCoords[self.rowIndex] + (focusHeight/2) - 5) self.focusSprite:add() self.spacebarSprite:remove() self.deleteSprite:remove() elseif self.rowIndex == 4 then if self.letterIndex == row4CharCount then --delete self.focusSprite:remove() self.spacebarSprite:remove() self.deleteSprite:add() else self.focusSprite:moveTo(xCoords[self.letterIndex + 29], yCoords[self.rowIndex] + (focusHeight/2) - 5) self.focusSprite:add() self.spacebarSprite:remove() self.deleteSprite:remove() end self.doneSprite:remove() elseif self.rowIndex == 5 then self.focusSprite:remove() self.deleteSprite:remove() if self.letterIndex == 1 then self.spacebarSprite:add() self.doneSprite:remove() elseif self.letterIndex == 2 then self.spacebarSprite:remove() self.doneSprite:add() end else print("Invalid row index: " .. self.rowIndex) end end function TextInputScreen:pop() self.isShowing = false gfx.setFont(self.defaultFont) self.focusSprite:remove() self.spacebarSprite:remove() self.deleteSprite:remove() self.doneSprite:remove() self.inputSprite:remove() self.focusSprite = nil self.spacebarSprite = nil self.deleteSprite = nil self.doneSprite = nil self.inputSprite = nil if self.messageSprite ~= nil then self.messageSprite:remove() self.messageSprite = nil end self:remove() playdate.inputHandlers.pop() if self.onDone ~= nil then self.onDone(self.input) end end