LoveFrames/objects/textinput.lua
Kenny Shields 072918fbea textinput custom cursor fixes
Updated the textinput custom cursor code to use the newest 0.9.0 cursor
functions
Fixed textinput custom cursors not being reset in certain situations
loveframes.hoverobject is now used to reference the currently hovered
object
loveframes.downobject has been added to assume the previous role of
loveframes.hoverobject
2013-10-30 19:05:10 -04:00

1953 lines
48 KiB
Lua

--[[------------------------------------------------
-- Love Frames - A GUI library for LOVE --
-- Copyright (c) 2013 Kenny Shields --
--]]------------------------------------------------
-- textinput class
local newobject = loveframes.NewObject("textinput", "loveframes_object_textinput", true)
--[[---------------------------------------------------------
- func: initialize()
- desc: initializes the object
--]]---------------------------------------------------------
function newobject:initialize()
self.type = "textinput"
self.keydown = "none"
self.tabreplacement = " "
self.maskchar = "*"
self.font = loveframes.basicfont
self.width = 200
self.height = 25
self.delay = 0
self.repeatdelay = 0.80
self.repeatrate = 0.02
self.offsetx = 0
self.offsety = 0
self.indincatortime = 0
self.indicatornum = 0
self.indicatorx = 0
self.indicatory = 0
self.textx = 0
self.texty = 0
self.textoffsetx = 5
self.textoffsety = 5
self.unicode = 0
self.limit = 0
self.line = 1
self.itemwidth = 0
self.itemheight = 0
self.extrawidth = 0
self.extraheight = 0
self.rightpadding = 0
self.bottompadding = 0
self.lastclicktime = 0
self.maxx = 0
self.buttonscrollamount = 0.10
self.mousewheelscrollamount = 5
self.usable = {}
self.unusable = {}
self.lines = {""}
self.internals = {}
self.showindicator = true
self.focus = false
self.multiline = false
self.vbar = false
self.hbar = false
self.alltextselected = false
self.linenumbers = true
self.linenumberspanel = false
self.editable = true
self.internal = false
self.autoscroll = false
self.masked = false
self.OnEnter = nil
self.OnTextChanged = nil
self.OnFocusGained = nil
self.OnFocusLost = nil
self.OnCopy = nil
self.OnPaste = nil
end
--[[---------------------------------------------------------
- func: update(deltatime)
- desc: updates the object
--]]---------------------------------------------------------
function newobject:update(dt)
local state = loveframes.state
local selfstate = self.state
if state ~= selfstate then
return
end
local visible = self.visible
local alwaysupdate = self.alwaysupdate
if not visible then
if not alwaysupdate then
return
end
end
-- check to see if the object is being hovered over
self:CheckHover()
local time = love.timer.getTime()
local keydown = self.keydown
local unicode = self.unicode
local parent = self.parent
local base = loveframes.base
local update = self.Update
local font = self.font
local theight = font:getHeight()
local delay = self.delay
local lines = self.lines
local numlines = #lines
local multiline = self.multiline
local width = self.width
local height = self.height
local vbar = self.vbar
local hbar = self.hbar
local inputobject = loveframes.inputobject
local internals = self.internals
local repeatrate = self.repeatrate
local hover = self.hover
local version = love._version
-- move to parent if there is a parent
if parent ~= base then
local parentx = parent.x
local parenty = parent.y
local staticx = self.staticx
local staticy = self.staticy
self.x = parentx + staticx
self.y = parenty + staticy
end
if inputobject ~= self then
self.focus = false
self.alltextselected = false
end
-- keydown check
if keydown ~= "none" then
local lctrl = love.keyboard.isDown("lctrl")
local rctrl = love.keyboard.isDown("rctrl")
if time > delay then
if (lctrl or rctrl) and keydown == "v" then
self:Paste()
else
self:RunKey(keydown, unicode)
end
self.delay = time + repeatrate
end
end
self:PositionText()
self:UpdateIndicator()
-- calculations for multiline mode
if multiline then
local twidth = 0
local panel = self:GetLineNumbersPanel()
local textoffsetx = self.textoffsetx
local textoffsety = self.textoffsety
local linenumbers = self.linenumbers
local masked = self.masked
local maskchar = self.maskchar
-- get the longest line of text
for k, v in ipairs(lines) do
local linewidth = 0
if masked then
linewidth = font:getWidth(v:gsub(".", maskchar))
else
linewidth = font:getWidth(v)
end
if linewidth > twidth then
twidth = linewidth
end
end
-- item width calculation
if vbar then
self.itemwidth = twidth + 16 + textoffsetx * 2
else
self.itemwidth = twidth
end
if panel then
local panelwidth = panel.width
self.itemwidth = self.itemwidth + panelwidth + textoffsetx + 5
end
-- item height calculation
if hbar then
self.itemheight = theight * numlines + 16 + textoffsety * 2
else
self.itemheight = theight * numlines
end
-- extra width and height calculations
self.extrawidth = self.itemwidth - width
self.extraheight = self.itemheight - height
local itemwidth = self.itemwidth
local itemheight = self.itemheight
if itemheight > height then
if not vbar then
local scrollbody = loveframes.objects["scrollbody"]:new(self, "vertical")
scrollbody.internals[1].internals[1].autoscroll = self.autoscroll
table.insert(self.internals, scrollbody)
self.vbar = true
if hbar then
local vbody = self:GetVerticalScrollBody()
local vbodyheight = vbody:GetHeight() - 15
local hbody = self:GetHorizontalScrollBody()
local hbodywidth = hbody:GetWidth() - 15
vbody:SetHeight(vbodyheight)
hbody:SetWidth(hbodywidth)
end
end
else
if vbar then
self:GetVerticalScrollBody():Remove()
self.vbar = false
self.offsety = 0
if self.hbar then
local hbody = self:GetHorizontalScrollBody()
local hbodywidth = hbody:GetWidth() - 15
hbody:SetWidth(hbodywidth)
end
end
end
if itemwidth > width then
if not hbar then
local scrollbody = loveframes.objects["scrollbody"]:new(self, "horizontal")
scrollbody.internals[1].internals[1].autoscroll = self.autoscroll
table.insert(self.internals, scrollbody)
self.hbar = true
if self.vbar then
local vbody = self:GetVerticalScrollBody()
local hbody = self:GetHorizontalScrollBody()
vbody:SetHeight(vbody:GetHeight() - 15)
hbody:SetWidth(hbody:GetWidth() - 15)
end
end
else
if hbar then
self:GetHorizontalScrollBody():Remove()
self.hbar = false
self.offsetx = 0
if vbar then
local vbody = self:GetVerticalScrollBody()
if vbody then
vbody:SetHeight(vbody:GetHeight() + 15)
end
end
end
end
if linenumbers then
if not self.linenumberspanel then
local linenumberspanel = loveframes.objects["linenumberspanel"]:new(self)
table.insert(internals, linenumberspanel)
self.linenumberspanel = true
end
else
if self.linenumberspanel then
table.remove(internals, 1)
self.linenumberspanel = false
end
end
end
for k, v in ipairs(internals) do
v:update(dt)
end
if update then
update(self, dt)
end
end
--[[---------------------------------------------------------
- func: draw()
- desc: draws the object
--]]---------------------------------------------------------
function newobject:draw()
local state = loveframes.state
local selfstate = self.state
if state ~= selfstate then
return
end
local visible = self.visible
if not visible then
return
end
local x = self.x
local y = self.y
local width = self.width
local height = self.height
local skins = loveframes.skins.available
local skinindex = loveframes.config["ACTIVESKIN"]
local defaultskin = loveframes.config["DEFAULTSKIN"]
local stencilfunc = function() love.graphics.rectangle("fill", x, y, width, height) end
local loveversion = love._version
local selfskin = self.skin
local skin = skins[selfskin] or skins[skinindex]
local drawfunc = skin.DrawTextInput or skins[defaultskin].DrawTextInput
local drawoverfunc = skin.DrawOverTextInput or skins[defaultskin].DrawOverTextInput
local draw = self.Draw
local drawcount = loveframes.drawcount
local internals = self.internals
local vbar = self.vbar
local hbar = self.hbar
-- set the object's draw order
self:SetDrawOrder()
if vbar and hbar then
stencilfunc = function() love.graphics.rectangle("fill", x, y, width - 16, height - 16) end
end
if loveversion == "0.8.0" then
local stencil = love.graphics.newStencil(stencilfunc)
love.graphics.setStencil(stencil)
else
love.graphics.setStencil(stencilfunc)
end
if draw then
draw(self)
else
drawfunc(self)
end
love.graphics.setStencil()
for k, v in ipairs(internals) do
v:draw()
end
if not draw then
drawoverfunc(self)
end
end
--[[---------------------------------------------------------
- func: mousepressed(x, y, button)
- desc: called when the player presses a mouse button
--]]---------------------------------------------------------
function newobject:mousepressed(x, y, button)
local state = loveframes.state
local selfstate = self.state
if state ~= selfstate then
return
end
local visible = self.visible
if not visible then
return
end
local hover = self.hover
local internals = self.internals
local vbar = self.vbar
local hbar = self.hbar
local scrollamount = self.mousewheelscrollamount
local focus = self.focus
local alltextselected = self.alltextselected
local onfocusgained = self.OnFocusGained
local onfocuslost = self.OnFocusLost
local time = love.timer.getTime()
local inputobject = loveframes.inputobject
if hover then
if button == "l" then
if inputobject ~= self then
loveframes.inputobject = self
end
if not alltextselected then
local lastclicktime = self.lastclicktime
if (time > lastclicktime) and time < (lastclicktime + 0.25) then
self.alltextselected = true
end
else
self.alltextselected = false
end
self.focus = true
self.lastclicktime = time
self:GetTextCollisions(x, y)
if onfocusgained and not focus then
onfocusgained(self)
end
local baseparent = self:GetBaseParent()
if baseparent and baseparent.type == "frame" then
baseparent:MakeTop()
end
elseif button == "wu" then
if vbar and not hbar then
local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
vbar:Scroll(-scrollamount)
elseif vbar and hbar then
local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
vbar:Scroll(-scrollamount)
elseif not vbar and hbar then
local hbar = self:GetHorizontalScrollBody().internals[1].internals[1]
hbar:Scroll(-scrollamount)
end
elseif button == "wd" then
if vbar and not hbar then
local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
vbar:Scroll(scrollamount)
elseif vbar and hbar then
local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
vbar:Scroll(scrollamount)
elseif not vbar and hbar then
local hbar = self:GetHorizontalScrollBody().internals[1].internals[1]
hbar:Scroll(scrollamount)
end
end
else
if inputobject == self then
loveframes.inputobject = false
if onfocuslost then
onfocuslost(self)
end
end
end
for k, v in ipairs(internals) do
v:mousepressed(x, y, button)
end
end
--[[---------------------------------------------------------
- func: mousereleased(x, y, button)
- desc: called when the player releases a mouse button
--]]---------------------------------------------------------
function newobject:mousereleased(x, y, button)
local state = loveframes.state
local selfstate = self.state
if state ~= selfstate then
return
end
local visible = self.visible
if not visible then
return
end
local internals = self.internals
for k, v in ipairs(internals) do
v:mousereleased(x, y, button)
end
end
--[[---------------------------------------------------------
- func: keypressed(key)
- desc: called when the player presses a key
--]]---------------------------------------------------------
function newobject:keypressed(key, unicode)
local state = loveframes.state
local selfstate = self.state
if state ~= selfstate then
return
end
local visible = self.visible
if not visible then
return
end
local time = love.timer.getTime()
local lctrl = love.keyboard.isDown("lctrl")
local rctrl = love.keyboard.isDown("rctrl")
local focus = self.focus
local repeatdelay = self.repeatdelay
local alltextselected = self.alltextselected
local editable = self.editable
local version = love._version
self.delay = time + repeatdelay
self.keydown = key
if (lctrl or rctrl) and focus then
if key == "a" then
self.alltextselected = true
elseif key == "c" and alltextselected and version == "0.9.0" then
local text = self:GetText()
local oncopy = self.OnCopy
love.system.setClipboardText(text)
if oncopy then
oncopy(self, text)
end
elseif key == "v" and version == "0.9.0" and editable then
self:Paste()
end
else
local version = love._version
if version == "0.8.0" then
self:RunKey(key, unicode, true)
else
self:RunKey(key, unicode, false)
end
end
end
--[[---------------------------------------------------------
- func: keyreleased(key)
- desc: called when the player releases a key
--]]---------------------------------------------------------
function newobject:keyreleased(key)
local state = loveframes.state
local selfstate = self.state
if state ~= selfstate then
return
end
local visible = self.visible
if not visible then
return
end
self.keydown = "none"
end
--[[---------------------------------------------------------
- func: textinput(text)
- desc: called when the inputs text
--]]---------------------------------------------------------
function newobject:textinput(text)
local unicode = unicode
if text:find("kp.") then
text = text:gsub("kp", "")
end
if text:len() == 1 then
unicode = string.byte(text)
else
unicode = 0
end
self:RunKey(text, unicode, true)
end
--[[---------------------------------------------------------
- func: RunKey(key, unicode)
- desc: runs a key event on the object
--]]---------------------------------------------------------
function newobject:RunKey(key, unicode, is_text)
local visible = self.visible
local focus = self.focus
if not visible then
return
end
if not focus then
return
end
local x = self.x
local offsetx = self.offsetx
local lines = self.lines
local line = self.line
local numlines = #lines
local curline = lines[line]
local text = curline
local ckey = ""
local font = self.font
local swidth = self.width
local textoffsetx = self.textoffsetx
local indicatornum = self.indicatornum
local multiline = self.multiline
local alltextselected = self.alltextselected
local editable = self.editable
local initialtext = self:GetText()
local ontextchanged = self.OnTextChanged
local onenter = self.OnEnter
if key == "left" then
local indicatorx = self.indicatorx
indicatornum = self.indicatornum
if not multiline then
self:MoveIndicator(-1)
if indicatorx <= x and indicatornum ~= 0 then
local width = font:getWidth(text:sub(indicatornum, indicatornum + 1))
self.offsetx = offsetx - width
elseif indicatornum == 0 and offsetx ~= 0 then
self.offsetx = 0
end
else
if indicatornum == 0 then
if line > 1 then
self.line = line - 1
local numchars = #lines[self.line]
self:MoveIndicator(numchars)
end
else
self:MoveIndicator(-1)
end
end
elseif key == "right" then
local indicatorx = self.indicatorx
indicatornum = self.indicatornum
if not multiline then
self:MoveIndicator(1)
if indicatorx >= (x + swidth) and indicatornum ~= #text then
local width = font:getWidth(text:sub(indicatornum, indicatornum))
self.offsetx = offsetx + width
elseif indicatornum == #text and offsetx ~= ((font:getWidth(text)) - swidth + 10) and font:getWidth(text) + textoffsetx > swidth then
self.offsetx = ((font:getWidth(text)) - swidth + 10)
end
else
if indicatornum == #text then
if line < numlines then
self.line = line + 1
self:MoveIndicator(0, true)
end
else
self:MoveIndicator(1)
end
end
elseif key == "up" then
if multiline then
if line > 1 then
self.line = line - 1
if indicatornum > #lines[self.line] then
self.indicatornum = #lines[self.line]
end
end
end
elseif key == "down" then
if multiline then
if line < #lines then
self.line = line + 1
if indicatornum > #lines[self.line] then
self.indicatornum = #lines[self.line]
end
end
end
end
if not editable then
return
end
-- key input checking system
if key == "backspace" then
ckey = key
if alltextselected then
self:Clear()
self.alltextselected = false
indicatornum = self.indicatornum
else
if text ~= "" and indicatornum ~= 0 then
text = self:RemoveFromText(indicatornum)
self:MoveIndicator(-1)
lines[line] = text
end
if multiline then
if line > 1 and indicatornum == 0 then
local newindicatornum = 0
local oldtext = lines[line]
table.remove(lines, line)
self.line = line - 1
if #oldtext > 0 then
newindicatornum = #lines[self.line]
lines[self.line] = lines[self.line] .. oldtext
self:MoveIndicator(newindicatornum)
else
self:MoveIndicator(#lines[self.line])
end
end
end
local masked = self.masked
local cwidth = 0
if masked then
local maskchar = self.maskchar
cwidth = font:getWidth(text:sub(#text):gsub(".", maskchar))
else
cwidth = font:getWidth(text:sub(#text))
end
if self.offsetx > 0 then
self.offsetx = self.offsetx - cwidth
elseif self.offsetx < 0 then
self.offsetx = 0
end
end
elseif key == "delete" then
if not editable then
return
end
ckey = key
if alltextselected then
self:Clear()
self.alltextselected = false
indicatornum = self.indicatornum
else
if text ~= "" and indicatornum < #text then
text = self:RemoveFromText(indicatornum + 1)
lines[line] = text
elseif indicatornum == #text and line < #lines then
local oldtext = lines[line + 1]
if #oldtext > 0 then
newindicatornum = #lines[self.line]
lines[self.line] = lines[self.line] .. oldtext
end
table.remove(lines, line + 1)
end
end
elseif key == "return" or key == "kpenter" then
ckey = key
-- call onenter if it exists
if onenter then
onenter(self, text)
end
-- newline calculations for multiline mode
if multiline then
if alltextselected then
self.alltextselected = false
self:Clear()
indicatornum = self.indicatornum
line = self.line
end
local newtext = ""
if indicatornum == 0 then
newtext = self.lines[line]
self.lines[line] = ""
elseif indicatornum > 0 and indicatornum < #self.lines[line] then
newtext = self.lines[line]:sub(indicatornum + 1, #self.lines[line])
self.lines[line] = self.lines[line]:sub(1, indicatornum)
end
if line ~= #lines then
table.insert(self.lines, line + 1, newtext)
self.line = line + 1
else
table.insert(self.lines, newtext)
self.line = line + 1
end
self.indicatornum = 0
end
elseif key == "tab" then
ckey = key
for i=1, #self.tabreplacement do
local number = string.byte(self.tabreplacement:sub(i, i))
self.lines[self.line] = self:AddIntoText(number, self.indicatornum)
self:MoveIndicator(1)
end
else
if not is_text then
return
end
self.unicode = unicode
if unicode > 31 and unicode < 127 then
-- do not continue if the text limit has been reached or exceeded
if #text >= self.limit and self.limit ~= 0 then
return
end
-- set the current key
ckey = string.char(unicode)
-- check for unusable characters
if #self.usable > 0 then
local found = false
for k, v in ipairs(self.usable) do
if v == ckey then
found = true
end
end
if not found then
return
end
end
-- check for usable characters
if #self.unusable > 0 then
local found = false
for k, v in ipairs(self.unusable) do
if v == ckey then
found = true
end
end
if found then
return
end
end
if alltextselected then
self.alltextselected = false
self:Clear()
indicatornum = self.indicatornum
text = ""
lines = self.lines
line = self.line
end
if indicatornum ~= 0 and indicatornum ~= #text then
text = self:AddIntoText(unicode, indicatornum)
lines[line] = text
self:MoveIndicator(1)
elseif indicatornum == #text then
text = text .. ckey
lines[line] = text
self:MoveIndicator(1)
elseif indicatornum == 0 then
text = self:AddIntoText(unicode, indicatornum)
lines[line] = text
self:MoveIndicator(1)
end
lines = self.lines
line = self.line
curline = lines[line]
text = curline
if not multiline then
local masked = self.masked
local twidth = 0
local cwidth = 0
if masked then
local maskchar = self.maskchar
twidth = font:getWidth(text:gsub(".", maskchar))
cwidth = font:getWidth(ckey:gsub(".", maskchar))
else
twidth = font:getWidth(text)
cwidth = font:getWidth(ckey)
end
-- swidth - 1 is for the "-" character
if (twidth + textoffsetx) >= (swidth - 1) then
self.offsetx = self.offsetx + cwidth
end
end
end
end
local curtext = self:GetText()
if ontextchanged and initialtext ~= curtext then
ontextchanged(self, ckey)
end
end
--[[---------------------------------------------------------
- func: MoveIndicator(num, exact)
- desc: moves the object's indicator
--]]---------------------------------------------------------
function newobject:MoveIndicator(num, exact)
local lines = self.lines
local line = self.line
local curline = lines[line]
local text = curline
local indicatornum = self.indicatornum
if not exact then
self.indicatornum = indicatornum + num
else
self.indicatornum = num
end
if self.indicatornum > #text then
self.indicatornum = #text
elseif self.indicatornum < 0 then
self.indicatornum = 0
end
self.showindicator = true
self:UpdateIndicator()
end
--[[---------------------------------------------------------
- func: UpdateIndicator()
- desc: updates the object's text insertion position
indicator
--]]---------------------------------------------------------
function newobject:UpdateIndicator()
local time = love.timer.getTime()
local indincatortime = self.indincatortime
local indicatornum = self.indicatornum
local lines = self.lines
local line = self.line
local curline = lines[line]
local text = curline
local font = self.font
local theight = font:getHeight()
local offsetx = self.offsetx
local multiline = self.multiline
local showindicator = self.showindicator
local alltextselected = self.alltextselected
local textx = self.textx
local texty = self.texty
local masked = self.masked
if indincatortime < time then
if showindicator then
self.showindicator = false
else
self.showindicator = true
end
self.indincatortime = time + 0.50
end
if alltextselected then
self.showindicator = false
end
local width = 0
for i=1, indicatornum do
if masked then
local char = self.maskchar
width = width + font:getWidth(char)
else
local char = text:sub(i, i)
width = width + font:getWidth(char)
end
end
if multiline then
self.indicatorx = textx + width
self.indicatory = texty + theight * line - theight
else
self.indicatorx = textx + width
self.indicatory = texty
end
end
--[[---------------------------------------------------------
- func: AddIntoText(t, p)
- desc: adds text into the object's text at a given
position
--]]---------------------------------------------------------
function newobject:AddIntoText(t, p)
local lines = self.lines
local line = self.line
local curline = lines[line]
local text = curline
local part1 = text:sub(1, p)
local part2 = text:sub(p + 1)
local new = part1 .. string.char(t) .. part2
return new
end
--[[---------------------------------------------------------
- func: RemoveFromText(p)
- desc: removes text from the object's text a given
position
--]]---------------------------------------------------------
function newobject:RemoveFromText(p)
local lines = self.lines
local line = self.line
local curline = lines[line]
local text = curline
local part1 = text:sub(1, p - 1)
local part2 = text:sub(p + 1)
local new = part1 .. part2
return new
end
--[[---------------------------------------------------------
- func: GetTextCollisions(x, y)
- desc: gets text collisions with the mouse
--]]---------------------------------------------------------
function newobject:GetTextCollisions(x, y)
local font = self.font
local lines = self.lines
local numlines = #lines
local line = self.line
local curline = lines[line]
local text = curline
local xpos = 0
local line = 0
local vbar = self.vbar
local hbar = self.hbar
local multiline = self.multiline
local selfx = self.x
local selfy = self.y
local selfwidth = self.width
local masked = self.masked
if multiline then
local theight = font:getHeight()
local liney = 0
local selfcol
if vbar and not hbar then
selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth - 16, 1, self.height, 1)
elseif hbar and not vbar then
selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth, 1, self.height - 16, 1)
elseif not vbar and not hbar then
selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth, 1, self.height, 1)
elseif vbar and hbar then
selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth - 16, 1, self.height - 16, 1)
end
if selfcol then
local offsety = self.offsety
local textoffsety = self.textoffsety
for i=1, numlines do
local linecol = loveframes.util.BoundingBox(selfx, x, (selfy - offsety) + textoffsety + (theight * i) - theight, y, self.width, 1, theight, 1)
if linecol then
liney = (selfy - offsety) + textoffsety + (theight * i) - theight
self.line = i
end
end
local line = self.line
local curline = lines[line]
for i=1, #curline do
local char = text:sub(i, i)
local width = 0
if masked then
local maskchar = self.maskchar
width = font:getWidth(maskchar)
else
width = font:getWidth(char)
end
local height = font:getHeight()
local tx = self.textx + xpos
local ty = self.texty
local col = loveframes.util.BoundingBox(tx, x, liney, y, width, 1, height, 1)
xpos = xpos + width
if col then
self:MoveIndicator(i - 1, true)
break
else
self.indicatornum = #curline
end
if x < tx then
self:MoveIndicator(0, true)
end
if x > (tx + width) then
self:MoveIndicator(#curline, true)
end
end
if #curline == 0 then
self.indicatornum = 0
end
end
else
for i=1, #text do
local char = text:sub(i, i)
local width = 0
if masked then
local maskchar = self.maskchar
width = font:getWidth(maskchar)
else
width = font:getWidth(char)
end
local height = font:getHeight()
local tx = self.textx + xpos
local ty = self.texty
local col = loveframes.util.BoundingBox(tx, x, ty, y, width, 1, height, 1)
xpos = xpos + width
if col then
self:MoveIndicator(i - 1, true)
break
end
if x < tx then
self:MoveIndicator(0, true)
end
if x > (tx + width) then
self:MoveIndicator(#text, true)
end
end
end
end
--[[---------------------------------------------------------
- func: PositionText()
- desc: positions the object's text
--]]---------------------------------------------------------
function newobject:PositionText()
local multiline = self.multiline
local x = self.x
local y = self.y
local offsetx = self.offsetx
local offsety = self.offsety
local textoffsetx = self.textoffsetx
local textoffsety = self.textoffsety
local linenumberspanel = self.linenumberspanel
if multiline then
if linenumberspanel then
local panel = self:GetLineNumbersPanel()
self.textx = ((x + panel.width) - offsetx) + textoffsetx
self.texty = (y - offsety) + textoffsety
else
self.textx = (x - offsetx) + textoffsetx
self.texty = (y - offsety) + textoffsety
end
else
self.textx = (x - offsetx) + textoffsetx
self.texty = (y - offsety) + textoffsety
end
end
--[[---------------------------------------------------------
- func: SetTextOffsetX(num)
- desc: sets the object's text x offset
--]]---------------------------------------------------------
function newobject:SetTextOffsetX(num)
self.textoffsetx = num
end
--[[---------------------------------------------------------
- func: SetTextOffsetY(num)
- desc: sets the object's text y offset
--]]---------------------------------------------------------
function newobject:SetTextOffsetY(num)
self.textoffsety = num
end
--[[---------------------------------------------------------
- func: SetFont(font)
- desc: sets the object's font
--]]---------------------------------------------------------
function newobject:SetFont(font)
self.font = font
end
--[[---------------------------------------------------------
- func: GetFont()
- desc: gets the object's font
--]]---------------------------------------------------------
function newobject:GetFont()
return self.font
end
--[[---------------------------------------------------------
- func: SetFocus(focus)
- desc: sets the object's focus
--]]---------------------------------------------------------
function newobject:SetFocus(focus)
local inputobject = loveframes.inputobject
local onfocusgained = self.OnFocusGained
local onfocuslost = self.OnFocusLost
self.focus = focus
if focus then
loveframes.inputobject = self
if onfocusgained then
onfocusgained(self)
end
else
if inputobject == self then
loveframes.inputobject = false
end
if onfocuslost then
onfocuslost(self)
end
end
end
--[[---------------------------------------------------------
- func: GetFocus()
- desc: gets the object's focus
--]]---------------------------------------------------------
function newobject:GetFocus()
return self.focus
end
--[[---------------------------------------------------------
- func: GetIndicatorVisibility()
- desc: gets the object's indicator visibility
--]]---------------------------------------------------------
function newobject:GetIndicatorVisibility()
return self.showindicator
end
--[[---------------------------------------------------------
- func: SetLimit(limit)
- desc: sets the object's text limit
--]]---------------------------------------------------------
function newobject:SetLimit(limit)
self.limit = limit
end
--[[---------------------------------------------------------
- func: SetUsable(usable)
- desc: sets what characters can be used for the
object's text
--]]---------------------------------------------------------
function newobject:SetUsable(usable)
self.usable = usable
end
--[[---------------------------------------------------------
- func: GetUsable()
- desc: gets what characters can be used for the
object's text
--]]---------------------------------------------------------
function newobject:GetUsable()
return self.usable
end
--[[---------------------------------------------------------
- func: SetUnusable(unusable)
- desc: sets what characters can not be used for the
object's text
--]]---------------------------------------------------------
function newobject:SetUnusable(unusable)
self.unusable = unusable
end
--[[---------------------------------------------------------
- func: GetUnusable()
- desc: gets what characters can not be used for the
object's text
--]]---------------------------------------------------------
function newobject:GetUnusable()
return self.unusable
end
--[[---------------------------------------------------------
- func: Clear()
- desc: clears the object's text
--]]---------------------------------------------------------
function newobject:Clear()
self.lines = {""}
self.line = 1
self.offsetx = 0
self.offsety = 0
self.indicatornum = 0
end
--[[---------------------------------------------------------
- func: SetText(text)
- desc: sets the object's text
--]]---------------------------------------------------------
function newobject:SetText(text)
local tabreplacement = self.tabreplacement
local multiline = self.multiline
text = tostring(text)
text = text:gsub(string.char(9), tabreplacement)
text = text:gsub(string.char(13), "")
if multiline then
text = text:gsub(string.char(92) .. string.char(110), string.char(10))
local t = loveframes.util.SplitString(text, string.char(10))
if #t > 0 then
self.lines = t
else
self.lines = {""}
end
else
text = text:gsub(string.char(92) .. string.char(110), "")
text = text:gsub(string.char(10), "")
self.lines = {text}
self.line = 1
end
end
--[[---------------------------------------------------------
- func: GetText()
- desc: gets the object's text
--]]---------------------------------------------------------
function newobject:GetText()
local multiline = self.multiline
local lines = self.lines
local text = ""
if multiline then
for k, v in ipairs(lines) do
text = text .. v
if k ~= #lines then
text = text .. "\n"
end
end
else
text = lines[1]
end
return text
end
--[[---------------------------------------------------------
- func: SetMultiline(bool)
- desc: enables or disables allowing multiple lines for
text entry
--]]---------------------------------------------------------
function newobject:SetMultiline(bool)
local text = ""
local lines = self.lines
self.multiline = bool
if bool then
self:Clear()
else
for k, v in ipairs(lines) do
text = text .. v
end
self:SetText(text)
self.internals = {}
self.vbar = false
self.hbar = false
self.linenumberspanel = false
end
end
--[[---------------------------------------------------------
- func: GetMultiLine()
- desc: gets whether or not the object is using multiple
lines
--]]---------------------------------------------------------
function newobject:GetMultiLine()
return self.multiline
end
--[[---------------------------------------------------------
- func: GetVerticalScrollBody()
- desc: gets the object's vertical scroll body
--]]---------------------------------------------------------
function newobject:GetVerticalScrollBody()
local vbar = self.vbar
local internals = self.internals
local item = false
if vbar then
for k, v in ipairs(internals) do
if v.type == "scrollbody" and v.bartype == "vertical" then
item = v
end
end
end
return item
end
--[[---------------------------------------------------------
- func: GetHorizontalScrollBody()
- desc: gets the object's horizontal scroll body
--]]---------------------------------------------------------
function newobject:GetHorizontalScrollBody()
local hbar = self.hbar
local internals = self.internals
local item = false
if hbar then
for k, v in ipairs(internals) do
if v.type == "scrollbody" and v.bartype == "horizontal" then
item = v
end
end
end
return item
end
--[[---------------------------------------------------------
- func: HasVerticalScrollBar()
- desc: gets whether or not the object has a vertical
scroll bar
--]]---------------------------------------------------------
function newobject:HasVerticalScrollBar()
return self.vbar
end
--[[---------------------------------------------------------
- func: HasHorizontalScrollBar()
- desc: gets whether or not the object has a horizontal
scroll bar
--]]---------------------------------------------------------
function newobject:HasHorizontalScrollBar()
return self.hbar
end
--[[---------------------------------------------------------
- func: GetLineNumbersPanel()
- desc: gets the object's line numbers panel
--]]---------------------------------------------------------
function newobject:GetLineNumbersPanel()
local panel = self.linenumberspanel
local internals = self.internals
local item = false
if panel then
for k, v in ipairs(internals) do
if v.type == "linenumberspanel" then
item = v
end
end
end
return item
end
--[[---------------------------------------------------------
- func: ShowLineNumbers(bool)
- desc: sets whether or not to show line numbers when
using multiple lines
--]]---------------------------------------------------------
function newobject:ShowLineNumbers(bool)
local multiline = self.multiline
if multiline then
self.linenumbers = bool
end
end
--[[---------------------------------------------------------
- func: GetTextX()
- desc: gets the object's text x
--]]---------------------------------------------------------
function newobject:GetTextX()
return self.textx
end
--[[---------------------------------------------------------
- func: GetTextY()
- desc: gets the object's text y
--]]---------------------------------------------------------
function newobject:GetTextY()
return self.texty
end
--[[---------------------------------------------------------
- func: IsAllTextSelected()
- desc: gets whether or not all of the object's text is
selected
--]]---------------------------------------------------------
function newobject:IsAllTextSelected()
return self.alltextselected
end
--[[---------------------------------------------------------
- func: GetLines()
- desc: gets the object's lines
--]]---------------------------------------------------------
function newobject:GetLines()
return self.lines
end
--[[---------------------------------------------------------
- func: GetOffsetX()
- desc: gets the object's x offset
--]]---------------------------------------------------------
function newobject:GetOffsetX()
return self.offsetx
end
--[[---------------------------------------------------------
- func: GetOffsetY()
- desc: gets the object's y offset
--]]---------------------------------------------------------
function newobject:GetOffsetY()
return self.offsety
end
--[[---------------------------------------------------------
- func: GetIndicatorX()
- desc: gets the object's indicator's xpos
--]]---------------------------------------------------------
function newobject:GetIndicatorX()
return self.indicatorx
end
--[[---------------------------------------------------------
- func: GetIndicatorY()
- desc: gets the object's indicator's ypos
--]]---------------------------------------------------------
function newobject:GetIndicatorY()
return self.indicatory
end
--[[---------------------------------------------------------
- func: GetLineNumbersEnabled()
- desc: gets whether line numbers are enabled on the
object or not
--]]---------------------------------------------------------
function newobject:GetLineNumbersEnabled()
return self.linenumbers
end
--[[---------------------------------------------------------
- func: GetItemWidth()
- desc: gets the object's item width
--]]---------------------------------------------------------
function newobject:GetItemWidth()
return self.itemwidth
end
--[[---------------------------------------------------------
- func: GetItemHeight()
- desc: gets the object's item height
--]]---------------------------------------------------------
function newobject:GetItemHeight()
return self.itemheight
end
--[[---------------------------------------------------------
- func: SetTabReplacement(tabreplacement)
- desc: sets a string to replace tabs with
--]]---------------------------------------------------------
function newobject:SetTabReplacement(tabreplacement)
self.tabreplacement = tabreplacement
end
--[[---------------------------------------------------------
- func: GetTabReplacement()
- desc: gets the object's tab replacement
--]]---------------------------------------------------------
function newobject:GetTabReplacement()
return self.tabreplacement
end
--[[---------------------------------------------------------
- func: SetEditable(bool)
- desc: sets whether or not the user can edit the
object's text
--]]---------------------------------------------------------
function newobject:SetEditable(bool)
self.editable = bool
end
--[[---------------------------------------------------------
- func: GetEditable
- desc: gets whether or not the user can edit the
object's text
--]]---------------------------------------------------------
function newobject:GetEditable()
return self.editable
end
--[[---------------------------------------------------------
- func: SetButtonScrollAmount(speed)
- desc: sets the scroll amount of the object's scrollbar
buttons
--]]---------------------------------------------------------
function newobject:SetButtonScrollAmount(amount)
self.buttonscrollamount = amount
end
--[[---------------------------------------------------------
- func: GetButtonScrollAmount()
- desc: gets the scroll amount of the object's scrollbar
buttons
--]]---------------------------------------------------------
function newobject:GetButtonScrollAmount()
return self.buttonscrollamount
end
--[[---------------------------------------------------------
- func: SetMouseWheelScrollAmount(amount)
- desc: sets the scroll amount of the mouse wheel
--]]---------------------------------------------------------
function newobject:SetMouseWheelScrollAmount(amount)
self.mousewheelscrollamount = amount
end
--[[---------------------------------------------------------
- func: GetMouseWheelScrollAmount()
- desc: gets the scroll amount of the mouse wheel
--]]---------------------------------------------------------
function newobject:GetButtonScrollAmount()
return self.mousewheelscrollamount
end
--[[---------------------------------------------------------
- func: SetAutoScroll(bool)
- desc: sets whether or not the object should autoscroll
when in multiline mode
--]]---------------------------------------------------------
function newobject:SetAutoScroll(bool)
local internals = self.internals
self.autoscroll = bool
if internals[2] then
internals[2].internals[1].internals[1].autoscroll = bool
end
end
--[[---------------------------------------------------------
- func: GetAutoScroll()
- desc: gets whether or not the object should autoscroll
when in multiline mode
--]]---------------------------------------------------------
function newobject:GetAutoScroll()
return self.autoscroll
end
--[[---------------------------------------------------------
- func: SetRepeatDelay(delay)
- desc: sets the object's repeat delay
--]]---------------------------------------------------------
function newobject:SetRepeatDelay(delay)
self.repeatdelay = delay
end
--[[---------------------------------------------------------
- func: GetRepeatDelay()
- desc: gets the object's repeat delay
--]]---------------------------------------------------------
function newobject:GetRepeatDelay()
return self.repeatdelay
end
--[[---------------------------------------------------------
- func: SetRepeatRate(rate)
- desc: sets the object's repeat rate
--]]---------------------------------------------------------
function newobject:SetRepeatRate(rate)
self.repeatrate = rate
end
--[[---------------------------------------------------------
- func: GetRepeatRate()
- desc: gets the object's repeat rate
--]]---------------------------------------------------------
function newobject:GetRepeatRate()
return self.repeatrate
end
--[[---------------------------------------------------------
- func: SetValue(value)
- desc: sets the object's value (alias of SetText)
--]]---------------------------------------------------------
function newobject:SetValue(value)
self:SetText(value)
end
--[[---------------------------------------------------------
- func: GetValue()
- desc: gets the object's value (alias of GetText)
--]]---------------------------------------------------------
function newobject:GetValue()
return self:GetText()
end
--[[---------------------------------------------------------
- func: SetVisible(bool)
- desc: sets the object's visibility
--]]---------------------------------------------------------
function newobject:SetVisible(bool)
self.visible = bool
if not bool then
self.keydown = "none"
end
end
--[[---------------------------------------------------------
- func: Copy()
- desc: copies the object's text to the user's clipboard
--]]---------------------------------------------------------
function newobject:Copy()
local version = love._version
if version == "0.9.0" then
local text = self:GetText()
love.system.setClipboardText(text)
end
end
--[[---------------------------------------------------------
- func: Paste()
- desc: pastes the current contents of the clipboard
into the object's text
--]]---------------------------------------------------------
function newobject:Paste()
local text = love.system.getClipboardText()
local usable = self.usable
local unusable = self.unusable
local limit = self.limit
local onpaste = self.OnPaste
local ontextchanged = self.OnTextChanged
if limit > 0 then
local curtext = self:GetText()
local curlength = curtext:len()
if curlength == limit then
return
else
local inputlimit = limit - curlength
if text:len() > inputlimit then
text = text:sub(1, inputlimit)
end
end
end
local charcheck = function(a)
if #usable > 0 then
if not loveframes.util.TableHasValue(usable, a) then
return ""
end
elseif #unusable > 0 then
if loveframes.util.TableHasValue(unusable, a) then
return ""
end
end
end
if #usable > 0 or #unusable > 0 then
text = text:gsub(".", charcheck)
end
if alltextselected then
self:SetText(text)
if ontextchanged then
ontextchanged(self, text)
end
else
local tabreplacement = self.tabreplacement
local indicatornum = self.indicatornum
local lines = self.lines
local multiline = self.multiline
if multiline then
local parts = loveframes.util.SplitString(text, string.char(10))
local numparts = #parts
local oldlinedata = {}
local line = self.line
local first = lines[line]:sub(0, indicatornum)
local last = lines[line]:sub(indicatornum + 1)
if numparts > 1 then
for i=1, numparts do
local part = parts[i]:gsub(string.char(13), "")
part = part:gsub(string.char(9), " ")
if i ~= 1 then
table.insert(oldlinedata, lines[line])
lines[line] = part
if i == numparts then
self.indicatornum = part:len()
lines[line] = lines[line] .. last
self.line = line
end
else
lines[line] = first .. part
end
line = line + 1
end
for i=1, #oldlinedata do
lines[line] = oldlinedata[i]
line = line + 1
end
if ontextchanged then
ontextchanged(self, text)
end
elseif numparts == 1 then
text = text:gsub(string.char(10), " ")
text = text:gsub(string.char(13), " ")
text = text:gsub(string.char(9), tabreplacement)
local length = text:len()
local new = first .. text .. last
lines[line] = new
self.indicatornum = indicatornum + length
if ontextchanged then
ontextchanged(self, text)
end
end
else
text = text:gsub(string.char(10), " ")
text = text:gsub(string.char(13), " ")
text = text:gsub(string.char(9), tabreplacement)
local length = text:len()
local linetext = lines[1]
local part1 = linetext:sub(1, indicatornum)
local part2 = linetext:sub(indicatornum + 1)
local new = part1 .. text .. part2
lines[1] = new
self.indicatornum = indicatornum + length
if ontextchanged then
ontextchanged(self, text)
end
end
end
if onpaste then
onpaste(self, text)
end
end
--[[---------------------------------------------------------
- func: SelectAll()
- desc: selects all of the object's text
--]]---------------------------------------------------------
function newobject:SelectAll()
self.alltextselected = true
end
--[[---------------------------------------------------------
- func: DeselectAll()
- desc: deselects all of the object's text
--]]---------------------------------------------------------
function newobject:DeselectAll()
self.alltextselected = false
end
--[[---------------------------------------------------------
- func: SetMasked(masked)
- desc: sets whether or not the object is masked
--]]---------------------------------------------------------
function newobject:SetMasked(masked)
self.masked = masked
end
--[[---------------------------------------------------------
- func: GetMasked()
- desc: gets whether or not the object is masked
--]]---------------------------------------------------------
function newobject:GetMasked()
return self.masked
end
--[[---------------------------------------------------------
- func: SetMaskChar(char)
- desc: sets the object's mask character
--]]---------------------------------------------------------
function newobject:SetMaskChar(char)
self.maskchar = char
end
--[[---------------------------------------------------------
- func: GetMaskChar()
- desc: gets the object's mask character
--]]---------------------------------------------------------
function newobject:GetMaskChar()
return self.maskchar
end