helium/core/element.lua
2020-01-27 00:43:20 +02:00

322 lines
7.4 KiB
Lua
Executable File

--[[ Element superclass ]]
local path = string.sub(..., 1, string.len(...) - string.len(".core.element"))
local helium = require(path .. ".dummy")
---@class context
local context = {}
context.__index = context
local activeContext
---@param elem element
function context.new(elem)
local ctx = setmetatable({view = elem.view, element = elem}, context)
return ctx
end
function context:bubbleUpdate()
self.element.settings.pendingUpdate = true
self.element.settings.needsRendering = true
if self.parentCtx then
self.parentCtx:bubbleUpdate()
end
end
function context:set()
if activeContext then
self.parentCtx = activeContext
self.absX = self.parentCtx.absX + self.view.x
self.absY = self.parentCtx.absY + self.view.y
activeContext = self
else
self.absX = self.view.x
self.absY = self.view.y
activeContext = self
end
end
function context:unset()
if self.parentCtx then
activeContext = self.parentCtx
else
activeContext = nil
end
end
---@class element
local element = {}
element.__index = element
setmetatable(element,{
__call = function(cls, ...)
local self
local func, loader = ...
if type(func)=='function' then
self = setmetatable({}, element)
self.renderer = ...
self.classless = true
elseif type(cls)=='table' then
self = setmetatable({}, cls)
self.classless = false
end
if loader then
local function f(newFunc)
self:reLoader(newFunc)
end
loader(f)
end
self:new()
return self
end
})
--Dummy functions
function element:renderer() print('no renderer') end
function element:updater() end
function element:constructor() end
--Control functions
--The new function that should be used for element creation
function element:new()
local dimensions
--save the parameters
self.parameters = {}
--The element canvas
self.canvas = nil
--Internal settings
self.settings = {
isSetup = false,
pendingUpdate = true,
needsRendering = true,
calculatedDimensions = true,
inserted = false
}
self.view = {
x = 0,
y = 0,
w = 10,
h = 10,
}
if self.classless then
self.classlessState = {}
self.classlessData = {}
end
end
--Hotswapping code
function element:reLoader(newFunc)
self.renderer = newFunc
self.context:bubbleUpdate()
end
--Called once dimensions are validated
function element:setup()
self.state =
setmetatable(
{},
{
__index = function(t, index)
return self.baseState[index]
end,
__newindex = function(t, index, val)
if self.baseState[index] ~= val then
self.baseState[index] = val
self.context:bubbleUpdate()
end
end
}
)
self.parameters =
setmetatable(
{},
{
__index = function(t, index)
return self.baseParams[index] or nil
end,
__newindex = function(t, index, val)
if self.baseParams[index] ~= val then
self.baseParams[index] = val
self.context:bubbleUpdate()
end
end
}
)
self.canvas = love.graphics.newCanvas(self.view.w, self.view.h)
self.quad = love.graphics.newQuad(0, 0, self.view.w, self.view.h, self.view.w, self.view.h)
--Context makes sure element internals don't have to worry about absolute coordinates
self.inputContext = helium.input.newContext(self)
self.context = context.new(self)
self.settings.isSetup = true
--Classless rendering
if self.classless then
self.classlessData.loadEffect = function (func)
if self.classlessData.loaded and self.classlessData.loadCaptured then
return self.classlessData.loadCaptured
elseif not self.classlessData.loaded then
self.classlessData.loadCaptured = func()
self.classlessData.loaded = true
if self.classlessData.loadCaptured then
return self.classlessData.loadCaptured
end
end
end
---@param initial any
---@return any
---@return function
self.classlessData.useState = function (initial)
self.settings.indice = self.settings.indice + 1
local indice = self.settings.indice
if self.classlessState[indice] and self.classlessState[indice].state then
return self.classlessState[indice].state, self.classlessState[indice].setState
else
self.classlessState[indice] = {}
self.classlessState[indice].state = initial
self.classlessState[indice].setState = function(set)
self.classlessState[indice].state = set
self.context:bubbleUpdate()
end
self.classlessState[indice].getState = function()
return self.classlessState[indice].state
end
return self.classlessState[indice].state, self.classlessState[indice].setState, self.classlessState[indice].getState
end
end
end
end
function element:classlessRender()
self.inputContext:set()
self.settings.indice = 0
if type(self.renderer)=='function' then
local denv = getfenv(self.renderer)
denv['useState'] = self.classlessData.useState
denv['loadEffect'] = self.classlessData.loadEffect
local status,err = pcall(self.renderer,self.parameters, self.view.w, self.view.h)
if not status then
love.graphics.setColor(1,0,0)
love.graphics.rectangle('line',0,0,self.view.w,self.view.h)
love.graphics.setColor(1,1,1)
love.graphics.printf("Error: "..err,0,0,self.view.w)
end
denv['useState'] = nil
denv['loadEffect'] = nil
elseif type(self.renderer)=='string' then
love.graphics.setColor(1,0,0)
love.graphics.rectangle('line',0,0,self.view.w,self.view.h)
love.graphics.setColor(1,1,1)
love.graphics.printf("Error: "..self.renderer,0,0,self.view.w)
end
self.inputContext:unset()
end
function element:renderWrapper()
local cnvs = love.graphics.getCanvas()
love.graphics.setCanvas({self.canvas, stencil = true})
love.graphics.clear()
if self.classless and self.parameters then
self:classlessRender()
self.settings.pendingUpdate = false
else
self:renderer()
end
love.graphics.setCanvas(cnvs)
end
function element:externalRender()
self.context:set()
if self.settings.needsRendering then
self:renderWrapper()
self.settings.needsRendering = false
end
love.graphics.setColor(1,1,1)
love.graphics.draw(self.canvas, self.quad, self.view.x, self.view.y)
self.context:unset()
end
function element:externalUpdate()
if self.settings.pendingUpdate then
if self.updater then
self:updater()
end
self.settings.needsRendering = true
self.settings.pendingUpdate = false
end
end
--External functions
--Acts as the entrypoint for beginning rendering
---@param params any
---@param x number
---@param y number
---@param w number
---@param h number
function element:draw(params, x, y, w, h)
self.view.x = x or self.view.x
self.view.y = y or self.view.y
self.view.w = w or self.view.w
self.view.h = h or self.view.h
if params then
if type(params)=='table' and self.baseParams then
helium.utils.tableMerge(params, self.parameters)
elseif self.baseParams==nil then
self.baseParams = params
else
self.parameters = params
end
end
if not self.settings.isSetup then
self:setup()
end
if activeContext then
self:externalRender()
elseif not self.settings.inserted then
self.settings.inserted = true
table.insert(helium.elementBuffer, self)
end
end
function element:undraw()
self.settings.remove = true
self.settings.isSetup = false
end
return element