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

646 lines
20 KiB
Lua

local utils = require("utils")
local tHex = require("tHex")
local sub, find, insert = string.sub, string.find, table.insert
return function(name, basalt)
local base = basalt.getObject("Object")(name, basalt)
-- Base object
local objectType = "VisualObject" -- not changeable
local isVisible,ignOffset,isHovered,isClicked,isDragging = true,false,false,false,false
local zIndex = 1
local x, y, width, height = 1,1,1,1
local dragStartX, dragStartY, dragXOffset, dragYOffset = 0, 0, 0, 0
local bgColor,fgColor, transparency = colors.black, colors.white, false
local parent
local preDrawQueue = {}
local drawQueue = {}
local postDrawQueue = {}
local renderObject = {}
local function split(str, d)
local result = {}
if str == "" then
return result
end
d = d or " "
local start = 1
local delim_start, delim_end = find(str, d, start)
while delim_start do
insert(result, {x=start, value=sub(str, start, delim_start - 1)})
start = delim_end + 1
delim_start, delim_end = find(str, d, start)
end
insert(result, {x=start, value=sub(str, start)})
return result
end
local object = {
getType = function(self)
return objectType
end,
getBase = function(self)
return base
end,
isType = function(self, t)
return objectType==t or base.isType~=nil and base.isType(t) or false
end,
getBasalt = function(self)
return basalt
end,
show = function(self)
isVisible = true
self:updateDraw()
return self
end,
hide = function(self)
isVisible = false
self:updateDraw()
return self
end,
isVisible = function(self)
return isVisible
end,
setVisible = function(self, _isVisible)
isVisible = _isVisible or not isVisible
self:updateDraw()
return self
end,
setTransparency = function(self, _transparency)
transparency = _transparency~= nil and _transparency or true
self:updateDraw()
return self
end,
setParent = function(self, newParent, noRemove)
base.setParent(self, newParent, noRemove)
parent = newParent
return self
end,
setFocus = function(self)
if (parent ~= nil) then
parent:setFocusedChild(self)
end
return self
end,
setZIndex = function(self, index)
zIndex = index
if (parent ~= nil) then
parent:updateZIndex(self, zIndex)
self:updateDraw()
end
return self
end,
getZIndex = function(self)
return zIndex
end,
updateDraw = function(self)
if (parent ~= nil) then
parent:updateDraw()
end
return self
end,
setPosition = function(self, xPos, yPos, rel)
local curX, curY = x, y
if(type(xPos)=="number")then
x = rel and x+xPos or xPos
end
if(type(yPos)=="number")then
y = rel and y+yPos or yPos
end
if(parent~=nil)then parent:customEventHandler("basalt_FrameReposition", self) end
if(self:getType()=="Container")then parent:customEventHandler("basalt_FrameReposition", self) end
self:updateDraw()
self:repositionHandler(curX, curY)
return self
end,
getX = function(self)
return x
end,
setX = function(self, newX)
return self:setPosition(newX, y)
end,
getY = function(self)
return y
end,
setY = function(self, newY)
return self:setPosition(x, newY)
end,
getPosition = function(self)
return x, y
end,
setSize = function(self, newWidth, newHeight, rel)
local oldW, oldH = width, height
if(type(newWidth)=="number")then
width = rel and width+newWidth or newWidth
end
if(type(newHeight)=="number")then
height = rel and height+newHeight or newHeight
end
if(parent~=nil)then
parent:customEventHandler("basalt_FrameResize", self)
if(self:getType()=="Container")then parent:customEventHandler("basalt_FrameResize", self) end
end
self:resizeHandler(oldW, oldH)
self:updateDraw()
return self
end,
getHeight = function(self)
return height
end,
setHeight = function(self, newHeight)
return self:setSize(width, newHeight)
end,
getWidth = function(self)
return width
end,
setWidth = function(self, newWidth)
return self:setSize(newWidth, height)
end,
getSize = function(self)
return width, height
end,
setBackground = function(self, color)
bgColor = color
self:updateDraw()
return self
end,
getBackground = function(self)
return bgColor
end,
setForeground = function(self, color)
fgColor = color or false
self:updateDraw()
return self
end,
getForeground = function(self)
return fgColor
end,
getAbsolutePosition = function(self, x, y)
-- relative position to absolute position
if (x == nil) or (y == nil) then
x, y = self:getPosition()
end
if (parent ~= nil) then
local fx, fy = parent:getAbsolutePosition()
x = fx + x - 1
y = fy + y - 1
end
return x, y
end,
ignoreOffset = function(self, ignore)
ignOffset = ignore
if(ignore==nil)then ignOffset = true end
return self
end,
getIgnoreOffset = function(self)
return ignOffset
end,
isCoordsInObject = function(self, x, y)
if(isVisible)and(self:isEnabled())then
if(x==nil)or(y==nil)then return false end
local objX, objY = self:getAbsolutePosition()
local w, h = self:getSize()
if (objX <= x) and (objX + w > x) and (objY <= y) and (objY + h > y) then
return true
end
end
return false
end,
onGetFocus = function(self, ...)
for _,v in pairs(table.pack(...))do
if(type(v)=="function")then
self:registerEvent("get_focus", v)
end
end
return self
end,
onLoseFocus = function(self, ...)
for _,v in pairs(table.pack(...))do
if(type(v)=="function")then
self:registerEvent("lose_focus", v)
end
end
return self
end,
isFocused = function(self)
if (parent ~= nil) then
return parent:getFocused() == self
end
return true
end,
resizeHandler = function(self, ...)
if(self:isEnabled())then
local val = self:sendEvent("basalt_resize", ...)
if(val==false)then return false end
end
return true
end,
repositionHandler = function(self, ...)
if(self:isEnabled())then
local val = self:sendEvent("basalt_reposition", ...)
if(val==false)then return false end
end
return true
end,
onResize = function(self, ...)
for _,v in pairs(table.pack(...))do
if(type(v)=="function")then
self:registerEvent("basalt_resize", v)
end
end
return self
end,
onReposition = function(self, ...)
for _,v in pairs(table.pack(...))do
if(type(v)=="function")then
self:registerEvent("basalt_reposition", v)
end
end
return self
end,
mouseHandler = function(self, button, x, y, isMon)
if(self:isCoordsInObject(x, y))then
local objX, objY = self:getAbsolutePosition()
local val = self:sendEvent("mouse_click", button, x - (objX-1), y - (objY-1), x, y, isMon)
if(val==false)then return false end
if(parent~=nil)then
parent:setFocusedChild(self)
end
isClicked = true
isDragging = true
dragStartX, dragStartY = x, y
return true
end
end,
mouseUpHandler = function(self, button, x, y)
isDragging = false
if(isClicked)then
local objX, objY = self:getAbsolutePosition()
local val = self:sendEvent("mouse_release", button, x - (objX-1), y - (objY-1), x, y)
isClicked = false
end
if(self:isCoordsInObject(x, y))then
local objX, objY = self:getAbsolutePosition()
local val = self:sendEvent("mouse_up", button, x - (objX-1), y - (objY-1), x, y)
if(val==false)then return false end
return true
end
end,
dragHandler = function(self, button, x, y)
if(isDragging)then
local objX, objY = self:getAbsolutePosition()
local val = self:sendEvent("mouse_drag", button, x - (objX-1), y - (objY-1), dragStartX-x, dragStartY-y, x, y)
dragStartX, dragStartY = x, y
if(val~=nil)then return val end
if(parent~=nil)then
parent:setFocusedChild(self)
end
return true
end
if(self:isCoordsInObject(x, y))then
local objX, objY = self:getAbsolutePosition()
dragStartX, dragStartY = x, y
dragXOffset, dragYOffset = objX - x, objY - y
end
end,
scrollHandler = function(self, dir, x, y)
if(self:isCoordsInObject(x, y))then
local objX, objY = self:getAbsolutePosition()
local val = self:sendEvent("mouse_scroll", dir, x - (objX-1), y - (objY-1))
if(val==false)then return false end
if(parent~=nil)then
parent:setFocusedChild(self)
end
return true
end
end,
hoverHandler = function(self, x, y, stopped)
if(self:isCoordsInObject(x, y))then
local val = self:sendEvent("mouse_hover", x, y, stopped)
if(val==false)then return false end
isHovered = true
return true
end
if(isHovered)then
local val = self:sendEvent("mouse_leave", x, y, stopped)
if(val==false)then return false end
isHovered = false
end
end,
keyHandler = function(self, key, isHolding)
if(self:isEnabled())and(isVisible)then
if (self:isFocused()) then
local val = self:sendEvent("key", key, isHolding)
if(val==false)then return false end
return true
end
end
end,
keyUpHandler = function(self, key)
if(self:isEnabled())and(isVisible)then
if (self:isFocused()) then
local val = self:sendEvent("key_up", key)
if(val==false)then return false end
return true
end
end
end,
charHandler = function(self, char)
if(self:isEnabled())and(isVisible)then
if(self:isFocused())then
local val = self:sendEvent("char", char)
if(val==false)then return false end
return true
end
end
end,
getFocusHandler = function(self)
local val = self:sendEvent("get_focus")
if(val~=nil)then return val end
return true
end,
loseFocusHandler = function(self)
isDragging = false
local val = self:sendEvent("lose_focus")
if(val~=nil)then return val end
return true
end,
addDraw = function(self, name, f, pos, typ, active)
local queue = (typ==nil or typ==1) and drawQueue or typ==2 and preDrawQueue or typ==3 and postDrawQueue
pos = pos or #queue+1
if(name~=nil)then
for k,v in pairs(queue)do
if(v.name==name)then
table.remove(queue, k)
break
end
end
local t = {name=name, f=f, pos=pos, active=active~=nil and active or true}
table.insert(queue, pos, t)
end
self:updateDraw()
return self
end,
addPreDraw = function(self, name, f, pos, typ)
self:addDraw(name, f, pos, 2)
return self
end,
addPostDraw = function(self, name, f, pos, typ)
self:addDraw(name, f, pos, 3)
return self
end,
setDrawState = function(self, name, state, typ)
local queue = (typ==nil or typ==1) and drawQueue or typ==2 and preDrawQueue or typ==3 and postDrawQueue
for k,v in pairs(queue)do
if(v.name==name)then
v.active = state
break
end
end
self:updateDraw()
return self
end,
getDrawId = function(self, name, typ)
local queue = typ==1 and drawQueue or typ==2 and preDrawQueue or typ==3 and postDrawQueue or drawQueue
for k,v in pairs(queue)do
if(v.name==name)then
return k
end
end
end,
addText = function(self, x, y, text)
local obj = self:getParent() or self
local xPos,yPos = self:getPosition()
if(parent~=nil)then
local xO, yO = parent:getOffset()
xPos = ignOffset and xPos or xPos - xO
yPos = ignOffset and yPos or yPos - yO
end
if not(transparency)then
obj:setText(x+xPos-1, y+yPos-1, text)
return
end
local t = split(text, "\0")
for k,v in pairs(t)do
if(v.value~="")and(v.value~="\0")then
obj:setText(x+v.x+xPos-2, y+yPos-1, v.value)
end
end
end,
addBG = function(self, x, y, bg, noText)
local obj = parent or self
local xPos,yPos = self:getPosition()
if(parent~=nil)then
local xO, yO = parent:getOffset()
xPos = ignOffset and xPos or xPos - xO
yPos = ignOffset and yPos or yPos - yO
end
if not(transparency)then
obj:setBG(x+xPos-1, y+yPos-1, bg)
return
end
local t = split(bg)
for k,v in pairs(t)do
if(v.value~="")and(v.value~=" ")then
if(noText~=true)then
obj:setText(x+v.x+xPos-2, y+yPos-1, (" "):rep(#v.value))
obj:setBG(x+v.x+xPos-2, y+yPos-1, v.value)
else
table.insert(renderObject, {x=x+v.x-1,y=y,bg=v.value})
obj:setBG(x+xPos-1, y+yPos-1, fg)
end
end
end
end,
addFG = function(self, x, y, fg)
local obj = parent or self
local xPos,yPos = self:getPosition()
if(parent~=nil)then
local xO, yO = parent:getOffset()
xPos = ignOffset and xPos or xPos - xO
yPos = ignOffset and yPos or yPos - yO
end
if not(transparency)then
obj:setFG(x+xPos-1, y+yPos-1, fg)
return
end
local t = split(fg)
for k,v in pairs(t)do
if(v.value~="")and(v.value~=" ")then
obj:setFG(x+v.x+xPos-2, y+yPos-1, v.value)
end
end
end,
addBlit = function(self, x, y, t, fg, bg)
local obj = parent or self
local xPos,yPos = self:getPosition()
if(parent~=nil)then
local xO, yO = parent:getOffset()
xPos = ignOffset and xPos or xPos - xO
yPos = ignOffset and yPos or yPos - yO
end
if not(transparency)then
obj:blit(x+xPos-1, y+yPos-1, t, fg, bg)
return
end
local _text = split(t, "\0")
local _fg = split(fg)
local _bg = split(bg)
for k,v in pairs(_text)do
if(v.value~="")or(v.value~="\0")then
obj:setText(x+v.x+xPos-2, y+yPos-1, v.value)
end
end
for k,v in pairs(_bg)do
if(v.value~="")or(v.value~=" ")then
obj:setBG(x+v.x+xPos-2, y+yPos-1, v.value)
end
end
for k,v in pairs(_fg)do
if(v.value~="")or(v.value~=" ")then
obj:setFG(x+v.x+xPos-2, y+yPos-1, v.value)
end
end
end,
addTextBox = function(self, x, y, w, h, text)
local obj = parent or self
local xPos,yPos = self:getPosition()
if(parent~=nil)then
local xO, yO = parent:getOffset()
xPos = ignOffset and xPos or xPos - xO
yPos = ignOffset and yPos or yPos - yO
end
obj:drawTextBox(x+xPos-1, y+yPos-1, w, h, text)
end,
addForegroundBox = function(self, x, y, w, h, col)
local obj = parent or self
local xPos,yPos = self:getPosition()
if(parent~=nil)then
local xO, yO = parent:getOffset()
xPos = ignOffset and xPos or xPos - xO
yPos = ignOffset and yPos or yPos - yO
end
obj:drawForegroundBox(x+xPos-1, y+yPos-1, w, h, col)
end,
addBackgroundBox = function(self, x, y, w, h, col)
local obj = parent or self
local xPos,yPos = self:getPosition()
if(parent~=nil)then
local xO, yO = parent:getOffset()
xPos = ignOffset and xPos or xPos - xO
yPos = ignOffset and yPos or yPos - yO
end
obj:drawBackgroundBox(x+xPos-1, y+yPos-1, w, h, col)
end,
render = function(self)
if (isVisible)then
self:redraw()
end
end,
redraw = function(self)
for k,v in pairs(preDrawQueue)do
if (v.active)then
v.f(self)
end
end
for k,v in pairs(drawQueue)do
if (v.active)then
v.f(self)
end
end
for k,v in pairs(postDrawQueue)do
if (v.active)then
v.f(self)
end
end
return true
end,
draw = function(self)
self:addDraw("base", function()
local w,h = self:getSize()
if(bgColor~=false)then
self:addTextBox(1, 1, w, h, " ")
self:addBackgroundBox(1, 1, w, h, bgColor)
end
if(fgColor~=false)then
self:addForegroundBox(1, 1, w, h, fgColor)
end
end, 1)
end,
}
object.__index = object
return setmetatable(object, base)
end