slabOS/lib/basalt/objects/Textfield.lua
2025-06-02 10:23:40 +03:00

774 lines
31 KiB
Lua

local tHex = require("tHex")
local rep,find,gmatch,sub,len = string.rep,string.find,string.gmatch,string.sub,string.len
return function(name, basalt)
local base = basalt.getObject("ChangeableObject")(name, basalt)
local objectType = "Textfield"
local hIndex, wIndex, textX, textY = 1, 1, 1, 1
local lines = { "" }
local bgLines = { "" }
local fgLines = { "" }
local keyWords = { }
local rules = { }
local startSelX,endSelX,startSelY,endSelY
local selectionBG,selectionFG = colors.lightBlue,colors.black
base:setSize(30, 12)
base:setZIndex(5)
local function isSelected()
if(startSelX~=nil)and(endSelX~=nil)and(startSelY~=nil)and(endSelY~=nil)then
return true
end
return false
end
local function getSelectionCoordinates()
local sx, ex, sy, ey = startSelX, endSelX, startSelY, endSelY
if isSelected() then
if startSelX < endSelX and startSelY <= endSelY then
sx = startSelX
ex = endSelX
if startSelY < endSelY then
sy = startSelY
ey = endSelY
else
sy = endSelY
ey = startSelY
end
elseif startSelX > endSelX and startSelY >= endSelY then
sx = endSelX
ex = startSelX
if startSelY > endSelY then
sy = endSelY
ey = startSelY
else
sy = startSelY
ey = endSelY
end
elseif startSelY > endSelY then
sx = endSelX
ex = startSelX
sy = endSelY
ey = startSelY
end
return sx, ex, sy, ey
end
end
local function removeSelection(self)
local sx, ex, sy, ey = getSelectionCoordinates()
local startLine = lines[sy]
local endLine = lines[ey]
lines[sy] = startLine:sub(1, sx - 1) .. endLine:sub(ex + 1, endLine:len())
bgLines[sy] = bgLines[sy]:sub(1, sx - 1) .. bgLines[ey]:sub(ex + 1, bgLines[ey]:len())
fgLines[sy] = fgLines[sy]:sub(1, sx - 1) .. fgLines[ey]:sub(ex + 1, fgLines[ey]:len())
for i = ey, sy + 1, -1 do
if i ~= sy then
table.remove(lines, i)
table.remove(bgLines, i)
table.remove(fgLines, i)
end
end
textX, textY = sx, sy
startSelX, endSelX, startSelY, endSelY = nil, nil, nil, nil
return self
end
local function stringGetPositions(str, word)
local pos = {}
if(str:len()>0)then
for w in gmatch(str, word)do
local s, e = find(str, w)
if(s~=nil)and(e~=nil)then
table.insert(pos,s)
table.insert(pos,e)
local startL = sub(str, 1, (s-1))
local endL = sub(str, e+1, str:len())
str = startL..(":"):rep(w:len())..endL
end
end
end
return pos
end
local function updateColors(self, l)
l = l or textY
local fgLine = tHex[self:getForeground()]:rep(fgLines[l]:len())
local bgLine = tHex[self:getBackground()]:rep(bgLines[l]:len())
for k,v in pairs(rules)do
local pos = stringGetPositions(lines[l], v[1])
if(#pos>0)then
for x=1,#pos/2 do
local xP = x*2 - 1
if(v[2]~=nil)then
fgLine = fgLine:sub(1, pos[xP]-1)..tHex[v[2]]:rep(pos[xP+1]-(pos[xP]-1))..fgLine:sub(pos[xP+1]+1, fgLine:len())
end
if(v[3]~=nil)then
bgLine = bgLine:sub(1, pos[xP]-1)..tHex[v[3]]:rep(pos[xP+1]-(pos[xP]-1))..bgLine:sub(pos[xP+1]+1, bgLine:len())
end
end
end
end
for k,v in pairs(keyWords)do
for _,b in pairs(v)do
local pos = stringGetPositions(lines[l], b)
if(#pos>0)then
for x=1,#pos/2 do
local xP = x*2 - 1
fgLine = fgLine:sub(1, pos[xP]-1)..tHex[k]:rep(pos[xP+1]-(pos[xP]-1))..fgLine:sub(pos[xP+1]+1, fgLine:len())
end
end
end
end
fgLines[l] = fgLine
bgLines[l] = bgLine
self:updateDraw()
end
local function updateAllColors(self)
for n=1,#lines do
updateColors(self, n)
end
end
local object = {
getType = function(self)
return objectType
end;
setBackground = function(self, bg)
base.setBackground(self, bg)
updateAllColors(self)
return self
end,
setForeground = function(self, fg)
base.setForeground(self, fg)
updateAllColors(self)
return self
end,
setSelection = function(self, fg, bg)
selectionFG = fg or selectionFG
selectionBG = bg or selectionBG
return self
end,
setSelectionFG = function(self, fg)
return self:setSelection(fg, nil)
end,
setSelectionBG = function(self, bg)
return self:setSelection(nil, bg)
end,
getSelection = function(self)
return selectionFG, selectionBG
end,
getSelectionFG = function(self)
return selectionFG
end,
getSelectionBG = function(self)
return selectionBG
end,
getLines = function(self)
return lines
end,
getLine = function(self, index)
return lines[index]
end,
editLine = function(self, index, text)
lines[index] = text or lines[index]
updateColors(self, index)
self:updateDraw()
return self
end,
clear = function(self)
lines = {""}
bgLines = {""}
fgLines = {""}
startSelX,endSelX,startSelY,endSelY = nil,nil,nil,nil
hIndex, wIndex, textX, textY = 1, 1, 1, 1
self:updateDraw()
return self
end,
addLine = function(self, text, index)
if(text~=nil)then
local bgColor = self:getBackground()
local fgColor = self:getForeground()
if(#lines==1)and(lines[1]=="")then
lines[1] = text
bgLines[1] = tHex[bgColor]:rep(text:len())
fgLines[1] = tHex[fgColor]:rep(text:len())
updateColors(self, 1)
return self
end
if (index ~= nil) then
table.insert(lines, index, text)
table.insert(bgLines, index, tHex[bgColor]:rep(text:len()))
table.insert(fgLines, index, tHex[fgColor]:rep(text:len()))
else
table.insert(lines, text)
table.insert(bgLines, tHex[bgColor]:rep(text:len()))
table.insert(fgLines, tHex[fgColor]:rep(text:len()))
end
end
updateColors(self, index or #lines)
self:updateDraw()
return self
end;
addKeywords = function(self, color, tab)
if(keyWords[color]==nil)then
keyWords[color] = {}
end
for k,v in pairs(tab)do
table.insert(keyWords[color], v)
end
self:updateDraw()
return self
end;
addRule = function(self, rule, fg, bg)
table.insert(rules, {rule, fg, bg})
self:updateDraw()
return self
end;
editRule = function(self, rule, fg, bg)
for k,v in pairs(rules)do
if(v[1]==rule)then
rules[k][2] = fg
rules[k][3] = bg
end
end
self:updateDraw()
return self
end;
removeRule = function(self, rule)
for k,v in pairs(rules)do
if(v[1]==rule)then
table.remove(rules, k)
end
end
self:updateDraw()
return self
end,
setKeywords = function(self, color, tab)
keyWords[color] = tab
self:updateDraw()
return self
end,
removeLine = function(self, index)
if(#lines>1)then
table.remove(lines, index or #lines)
table.remove(bgLines, index or #bgLines)
table.remove(fgLines, index or #fgLines)
else
lines = {""}
bgLines = {""}
fgLines = {""}
end
self:updateDraw()
return self
end,
getTextCursor = function(self)
return textX, textY
end,
getOffset = function(self)
return wIndex, hIndex
end,
setOffset = function(self, xOff, yOff)
wIndex = xOff or wIndex
hIndex = yOff or hIndex
self:updateDraw()
return self
end,
getXOffset = function(self)
return wIndex
end,
setXOffset = function(self, xOff)
return self:setOffset(xOff, nil)
end,
getYOffset = function(self)
return hIndex
end,
setYOffset = function(self, yOff)
return self:setOffset(nil, yOff)
end,
getFocusHandler = function(self)
base.getFocusHandler(self)
local obx, oby = self:getPosition()
self:getParent():setCursor(true, obx + textX - wIndex, oby + textY - hIndex, self:getForeground())
end,
loseFocusHandler = function(self)
base.loseFocusHandler(self)
self:getParent():setCursor(false)
end,
keyHandler = function(self, key)
if (base.keyHandler(self, key)) then
local parent = self:getParent()
local obx, oby = self:getPosition()
local w,h = self:getSize()
if (key == keys.backspace) then
-- on backspace
if(isSelected())then
removeSelection(self)
else
if (lines[textY] == "") then
if (textY > 1) then
table.remove(lines, textY)
table.remove(fgLines, textY)
table.remove(bgLines, textY)
textX = lines[textY - 1]:len() + 1
wIndex = textX - w + 1
if (wIndex < 1) then
wIndex = 1
end
textY = textY - 1
end
elseif (textX <= 1) then
if (textY > 1) then
textX = lines[textY - 1]:len() + 1
wIndex = textX - w + 1
if (wIndex < 1) then
wIndex = 1
end
lines[textY - 1] = lines[textY - 1] .. lines[textY]
fgLines[textY - 1] = fgLines[textY - 1] .. fgLines[textY]
bgLines[textY - 1] = bgLines[textY - 1] .. bgLines[textY]
table.remove(lines, textY)
table.remove(fgLines, textY)
table.remove(bgLines, textY)
textY = textY - 1
end
else
lines[textY] = lines[textY]:sub(1, textX - 2) .. lines[textY]:sub(textX, lines[textY]:len())
fgLines[textY] = fgLines[textY]:sub(1, textX - 2) .. fgLines[textY]:sub(textX, fgLines[textY]:len())
bgLines[textY] = bgLines[textY]:sub(1, textX - 2) .. bgLines[textY]:sub(textX, bgLines[textY]:len())
if (textX > 1) then
textX = textX - 1
end
if (wIndex > 1) then
if (textX < wIndex) then
wIndex = wIndex - 1
end
end
end
if (textY < hIndex) then
hIndex = hIndex - 1
end
end
updateColors(self)
self:setValue("")
elseif (key == keys.delete) then
-- on delete
if(isSelected())then
removeSelection(self)
else
if (textX > lines[textY]:len()) then
if (lines[textY + 1] ~= nil) then
lines[textY] = lines[textY] .. lines[textY + 1]
table.remove(lines, textY + 1)
table.remove(bgLines, textY + 1)
table.remove(fgLines, textY + 1)
end
else
lines[textY] = lines[textY]:sub(1, textX - 1) .. lines[textY]:sub(textX + 1, lines[textY]:len())
fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. fgLines[textY]:sub(textX + 1, fgLines[textY]:len())
bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. bgLines[textY]:sub(textX + 1, bgLines[textY]:len())
end
end
updateColors(self)
elseif (key == keys.enter) then
if(isSelected())then
removeSelection(self)
end
-- on enter
table.insert(lines, textY + 1, lines[textY]:sub(textX, lines[textY]:len()))
table.insert(fgLines, textY + 1, fgLines[textY]:sub(textX, fgLines[textY]:len()))
table.insert(bgLines, textY + 1, bgLines[textY]:sub(textX, bgLines[textY]:len()))
lines[textY] = lines[textY]:sub(1, textX - 1)
fgLines[textY] = fgLines[textY]:sub(1, textX - 1)
bgLines[textY] = bgLines[textY]:sub(1, textX - 1)
textY = textY + 1
textX = 1
wIndex = 1
if (textY - hIndex >= h) then
hIndex = hIndex + 1
end
self:setValue("")
elseif (key == keys.up) then
startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
-- arrow up
if (textY > 1) then
textY = textY - 1
if (textX > lines[textY]:len() + 1) then
textX = lines[textY]:len() + 1
end
if (wIndex > 1) then
if (textX < wIndex) then
wIndex = textX - w + 1
if (wIndex < 1) then
wIndex = 1
end
end
end
if (hIndex > 1) then
if (textY < hIndex) then
hIndex = hIndex - 1
end
end
end
elseif (key == keys.down) then
startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
-- arrow down
if (textY < #lines) then
textY = textY + 1
if (textX > lines[textY]:len() + 1) then
textX = lines[textY]:len() + 1
end
if (wIndex > 1) then
if (textX < wIndex) then
wIndex = textX - w + 1
if (wIndex < 1) then
wIndex = 1
end
end
end
if (textY >= hIndex + h) then
hIndex = hIndex + 1
end
end
elseif (key == keys.right) then
startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
-- arrow right
textX = textX + 1
if (textY < #lines) then
if (textX > lines[textY]:len() + 1) then
textX = 1
textY = textY + 1
end
elseif (textX > lines[textY]:len()) then
textX = lines[textY]:len() + 1
end
if (textX < 1) then
textX = 1
end
if (textX < wIndex) or (textX >= w + wIndex) then
wIndex = textX - w + 1
end
if (wIndex < 1) then
wIndex = 1
end
elseif (key == keys.left) then
startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
-- arrow left
textX = textX - 1
if (textX >= 1) then
if (textX < wIndex) or (textX >= w + wIndex) then
wIndex = textX
end
end
if (textY > 1) then
if (textX < 1) then
textY = textY - 1
textX = lines[textY]:len() + 1
wIndex = textX - w + 1
end
end
if (textX < 1) then
textX = 1
end
if (wIndex < 1) then
wIndex = 1
end
elseif(key == keys.tab)then
if(textX % 3 == 0 )then
lines[textY] = lines[textY]:sub(1, textX - 1) .. " " .. lines[textY]:sub(textX, lines[textY]:len())
fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self:getForeground()] .. fgLines[textY]:sub(textX, fgLines[textY]:len())
bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self:getBackground()] .. bgLines[textY]:sub(textX, bgLines[textY]:len())
textX = textX + 1
end
while textX % 3 ~= 0 do
lines[textY] = lines[textY]:sub(1, textX - 1) .. " " .. lines[textY]:sub(textX, lines[textY]:len())
fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self:getForeground()] .. fgLines[textY]:sub(textX, fgLines[textY]:len())
bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self:getBackground()] .. bgLines[textY]:sub(textX, bgLines[textY]:len())
textX = textX + 1
end
end
if not((obx + textX - wIndex >= obx and obx + textX - wIndex < obx + w) and (oby + textY - hIndex >= oby and oby + textY - hIndex < oby + h)) then
wIndex = math.max(1, lines[textY]:len()-w+1)
hIndex = math.max(1, textY - h + 1)
end
local cursorX = (textX <= lines[textY]:len() and textX - 1 or lines[textY]:len()) - (wIndex - 1)
if (cursorX > self:getX() + w - 1) then
cursorX = self:getX() + w - 1
end
local cursorY = (textY - hIndex < h and textY - hIndex or textY - hIndex - 1)
if (cursorX < 1) then
cursorX = 0
end
parent:setCursor(true, obx + cursorX, oby + cursorY, self:getForeground())
self:updateDraw()
return true
end
end,
charHandler = function(self, char)
if(base.charHandler(self, char))then
local parent = self:getParent()
local obx, oby = self:getPosition()
local w,h = self:getSize()
if(isSelected())then
removeSelection(self)
end
lines[textY] = lines[textY]:sub(1, textX - 1) .. char .. lines[textY]:sub(textX, lines[textY]:len())
fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self:getForeground()] .. fgLines[textY]:sub(textX, fgLines[textY]:len())
bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self:getBackground()] .. bgLines[textY]:sub(textX, bgLines[textY]:len())
textX = textX + 1
if (textX >= w + wIndex) then
wIndex = wIndex + 1
end
updateColors(self)
self:setValue("")
if not((obx + textX - wIndex >= obx and obx + textX - wIndex < obx + w) and (oby + textY - hIndex >= oby and oby + textY - hIndex < oby + h)) then
wIndex = math.max(1, lines[textY]:len()-w+1)
hIndex = math.max(1, textY - h + 1)
end
local cursorX = (textX <= lines[textY]:len() and textX - 1 or lines[textY]:len()) - (wIndex - 1)
if (cursorX > self:getX() + w - 1) then
cursorX = self:getX() + w - 1
end
local cursorY = (textY - hIndex < h and textY - hIndex or textY - hIndex - 1)
if (cursorX < 1) then
cursorX = 0
end
parent:setCursor(true, obx + cursorX, oby + cursorY, self:getForeground())
self:updateDraw()
return true
end
end,
dragHandler = function(self, button, x, y)
if (base.dragHandler(self, button, x, y)) then
local parent = self:getParent()
local obx, oby = self:getAbsolutePosition()
local ox, oy = self:getPosition()
local w,h = self:getSize()
if (lines[y - oby + hIndex] ~= nil) then
if(x - obx + wIndex > 0)and(x - obx + wIndex <= w)then
textX = x - obx + wIndex
textY = y - oby + hIndex
if textX > lines[textY]:len() then
textX = lines[textY]:len() + 1
end
endSelX = textX
endSelY = textY
if textX < wIndex then
wIndex = textX - 1
if wIndex < 1 then
wIndex = 1
end
end
parent:setCursor(not isSelected(), ox + textX - wIndex, oy + textY - hIndex, self:getForeground())
self:updateDraw()
end
end
return true
end
end,
scrollHandler = function(self, dir, x, y)
if (base.scrollHandler(self, dir, x, y)) then
local parent = self:getParent()
local obx, oby = self:getAbsolutePosition()
local anchx, anchy = self:getPosition()
local w,h = self:getSize()
hIndex = hIndex + dir
if (hIndex > #lines - (h - 1)) then
hIndex = #lines - (h - 1)
end
if (hIndex < 1) then
hIndex = 1
end
if (obx + textX - wIndex >= obx and obx + textX - wIndex < obx + w) and (anchy + textY - hIndex >= anchy and anchy + textY - hIndex < anchy + h) then
parent:setCursor(not isSelected(), anchx + textX - wIndex, anchy + textY - hIndex, self:getForeground())
else
parent:setCursor(false)
end
self:updateDraw()
return true
end
end,
mouseHandler = function(self, button, x, y)
if (base.mouseHandler(self, button, x, y)) then
local parent = self:getParent()
local obx, oby = self:getAbsolutePosition()
local anchx, anchy = self:getPosition()
if (lines[y - oby + hIndex] ~= nil) then
textX = x - obx + wIndex
textY = y - oby + hIndex
endSelX = nil
endSelY = nil
startSelX = textX
startSelY = textY
if (textX > lines[textY]:len()) then
textX = lines[textY]:len() + 1
startSelX = textX
end
if (textX < wIndex) then
wIndex = textX - 1
if (wIndex < 1) then
wIndex = 1
end
end
self:updateDraw()
end
parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, self:getForeground())
return true
end
end,
mouseUpHandler = function(self, button, x, y)
if (base.mouseUpHandler(self, button, x, y)) then
local obx, oby = self:getAbsolutePosition()
if (lines[y - oby + hIndex] ~= nil) then
endSelX = x - obx + wIndex
endSelY = y - oby + hIndex
if (endSelX > lines[endSelY]:len()) then
endSelX = lines[endSelY]:len() + 1
end
if(startSelX==endSelX)and(startSelY==endSelY)then
startSelX, endSelX, startSelY, endSelY = nil, nil, nil, nil
end
self:updateDraw()
end
return true
end
end,
eventHandler = function(self, event, paste, ...)
base.eventHandler(self, event, paste, ...)
if(event=="paste")then
if(self:isFocused())then
local parent = self:getParent()
local fgColor, bgColor = self:getForeground(), self:getBackground()
local w, h = self:getSize()
lines[textY] = lines[textY]:sub(1, textX - 1) .. paste .. lines[textY]:sub(textX, lines[textY]:len())
fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[fgColor]:rep(paste:len()) .. fgLines[textY]:sub(textX, fgLines[textY]:len())
bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[bgColor]:rep(paste:len()) .. bgLines[textY]:sub(textX, bgLines[textY]:len())
textX = textX + paste:len()
if (textX >= w + wIndex) then
wIndex = (textX+1)-w
end
local anchx, anchy = self:getPosition()
parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, fgColor)
updateColors(self)
self:updateDraw()
end
end
end,
draw = function(self)
base.draw(self)
self:addDraw("textfield", function()
local w, h = self:getSize()
local bgColor = tHex[self:getBackground()]
local fgColor = tHex[self:getForeground()]
for n = 1, h do
local text = ""
local bg = ""
local fg = ""
if lines[n + hIndex - 1] then
text = lines[n + hIndex - 1]
fg = fgLines[n + hIndex - 1]
bg = bgLines[n + hIndex - 1]
end
text = sub(text, wIndex, w + wIndex - 1)
bg = rep(bgColor, w)
fg = rep(fgColor, w)
self:addText(1, n, text)
self:addBG(1, n, bg)
self:addFG(1, n, fg)
self:addBlit(1, n, text, fg, bg)
end
if startSelX and endSelX and startSelY and endSelY then
local sx, ex, sy, ey = getSelectionCoordinates()
for n = sy, ey do
local line = #lines[n]
local xOffset = 0
if n == sy and n == ey then
xOffset = sx - 1 - (wIndex - 1)
line = line - (sx - 1 - (wIndex - 1)) - (line - ex + (wIndex - 1))
elseif n == ey then
line = line - (line - ex + (wIndex - 1))
elseif n == sy then
line = line - (sx - 1)
xOffset = sx - 1 - (wIndex - 1)
end
local visible_line_length = math.min(line, w - xOffset)
self:addBG(1 + xOffset, n, rep(tHex[selectionBG], visible_line_length))
self:addFG(1 + xOffset, n, rep(tHex[selectionFG], visible_line_length))
end
end
end)
end,
load = function(self)
self:listenEvent("mouse_click")
self:listenEvent("mouse_up")
self:listenEvent("mouse_scroll")
self:listenEvent("mouse_drag")
self:listenEvent("key")
self:listenEvent("char")
self:listenEvent("other_event")
end,
}
object.__index = object
return setmetatable(object, base)
end