Textbox = class()
function Textbox:init(x,y,w)
self.text = ""
self.x = x
self.y = y
self.width = w
-- in font properties you can set fill,font,fontSize
self.fontProperties = {font="AmericanTypewriter-Bold",fill=color(255,255,255)}
self:setFontSize(30)
self.borderColor = color(255, 255, 255, 255)
-- internal state
self.active = false
self.cursorPos = 0 -- 0 means before the first letter, 1 after the first, so on
self.startPos = 1 -- the first char we show on the box (index into self.text)
end
function Textbox:setFontSize(x)
self.fontProperties.fontSize = x
-- calculate the height based on font properties
pushStyle()
self:applyTextProperties()
local w,h = textSize("dummy")
popStyle()
self.height = h
end
-- call back for when a key is pressed
function Textbox:keyboard(key)
-- if not active, ignore
if not self.active then return nil end
if key == BACKSPACE then
-- if we press backspace. Note if we're already at the start, nothing to do
if self.cursorPos > 0 then
local prefix = self.text:sub(1,self.cursorPos-1)
local posfix = self.text:sub(self.cursorPos+1,self.text:len())
self.text = prefix..posfix
self.cursorPos = self.cursorPos - 1
-- need to improve this behavior for when the cursor is at the
-- start of what's showing. We should go back as much as we can instead of 1
if self.cursorPos < self.startPos then
local width = self:maxTextWidth()
self.startPos = 1
self:moveCursor(self.cursorPos)
end
end
else
local prefix = self.text:sub(1,self.cursorPos)
local posfix = self.text:sub(self.cursorPos+1,self.text:len())
self.text = prefix..key..posfix
self:moveCursor(self.cursorPos + key:len())
end
end
function Textbox:applyTextProperties()
textMode(CORNER)
font(self.fontProperties.font)
fontSize(self.fontProperties.fontSize)
fill(self.fontProperties.fill)
end
-- when the text box is active, the keyboard shows up (and coursor and other elements too)
function Textbox:activate()
self.active = true
-- move the cursor to the end
self:moveCursor(self.text:len())
showKeyboard()
end
function Textbox:inactivate()
self.active = false
self.startPos = 1
hideKeyboard()
end
-- newPos is specified in chars, not pixels
function Textbox:moveCursor(newPos)
self.cursorPos = newPos
local width = self:maxTextWidth()
local cursorText = self.text:sub(self.startPos,self.cursorPos)
pushStyle()
self:applyTextProperties()
local cursorLength = textSize(cursorText)
while cursorLength > width do
self.startPos = self.startPos + 1
cursorText = self.text:sub(self.startPos,self.cursorPos)
cursorLength = textSize(cursorText)
end
popStyle()
end
-- how much space the text can occupy. Can be less than width if we're showing the reset button
function Textbox:maxTextWidth()
local width = self.width
if self.active then
local resetDiam,resetX,resetY = self:resetButtonCoords()
width = resetX - self.x - 5
end
return width
end
-- return coords and dimensions for the reset button
function Textbox:resetButtonCoords()
local resetDiam = self.height-4
local resetX = self.x+self.width-resetDiam-2
local resetY = self.y+2
return resetDiam,resetX,resetY
end
-- the text that we actually display, cropped up if needed
function Textbox:displayText()
local dispText = self.text:sub(self.startPos)
pushStyle()
self:applyTextProperties()
local width = self:maxTextWidth()
while textSize(dispText) > width do
dispText = dispText:sub(1,dispText:len()-1)
end
popStyle()
return dispText
end
function Textbox:draw()
pushStyle()
noSmooth()
-- draw the box
stroke(self.borderColor)
strokeWidth(1)
noFill()
rectMode(CORNER)
rect(self.x,self.y,self.width,self.height)
-- draw the text
self:applyTextProperties()
local dispText = self:displayText()
text(dispText,self.x + 5,self.y)
if not self.active then
popStyle()
return nil
end
-- draw the cursor
if math.floor(ElapsedTime*3)%2 == 0 then
stroke(151, 167, 165, 255)
strokeWidth(1)
local prefix = dispText:sub(1,self.cursorPos - self.startPos + 1)
local len = textSize(prefix) + 5
line(self.x+len,self.y+4,self.x+len,self.y+self.height-4)
end
-- draw the reset button
stroke(self.borderColor)
strokeWidth(1)
noFill()
rectMode(CORNER)
ellipseMode(CORNER)
local resetDiam,resetX,resetY = self:resetButtonCoords()
local sq2 = (math.sqrt(2)-1)/2*1.1
ellipse(resetX,resetY,resetDiam)
line(resetX+resetDiam*sq2,resetY+resetDiam*sq2,
resetX+resetDiam*(1-sq2),resetY+resetDiam*(1-sq2))
line(resetX+resetDiam*sq2,resetY+resetDiam*(1-sq2),
resetX+resetDiam*(1-sq2),resetY+resetDiam*sq2)
popStyle()
end
function Textbox:touched(touch)
-- check if it was the reset button that was pressed
local resetDiam,resetX,resetY = self:resetButtonCoords()
if touch.x>=resetX and touch.x<=self.x+self.width and
touch.y>=self.y and touch.y<=self.y+self.height then
if self.active and touch.state==ENDED then
-- the user touched the reset button
self.text = ""
self.cursorPos = 0
self.startPos = 1
end
end
if touch.x>=self.x and touch.x<=self.x+self.width and
touch.y>=self.y and touch.y<=self.y+self.height then
if not self.active and touch.state == ENDED then
self:activate()
elseif self.active then
-- place the cursor at the touch x coord
local dispText = self:displayText()
self.cursorPos = self.startPos-1
local touchX = touch.x - self.x
pushStyle()
self:applyTextProperties()
for idx = 1,dispText:len() do
local len = textSize(dispText:sub(1,idx))
if len > touchX then break end
self.cursorPos = self.cursorPos + 1
end
popStyle()
end
elseif touch.state == BEGAN and self.active then
-- BEGAN above is important as it makes sure a box is inactivated, and hides
-- the keyboard, before another box is activated at ENDED, which will show
-- the keyboard again. If the other was inverted, then you'd showkeyboard only
-- to hide it right after
self:inactivate()
end
end
Nice update!
Very Sweet, @ruilov. I did something similar when it came to my Window Manager. Keep up the awesome work!