random commit

This commit is contained in:
ELmars Abolinsh 2020-06-21 03:42:26 +03:00
parent bac6287135
commit 32befc1a89
13 changed files with 323 additions and 159 deletions

0
control/context.lua Normal file
View File

61
control/layout.lua Normal file
View File

@ -0,0 +1,61 @@
local path = string.sub(..., 1, string.len(...) - string.len(".core.layout"))
local layout = {}
layout.__index = layout
local element = require(path..'core.element')
local function layout_new(type, x, y, w, h)
local ctx = element.getContext()
--The output will be in pixel numbers regardless of inputs
if x <= 1 or not x then
x = ctx.view.x * (x or 0)
end
if y <= 1 then
y = ctx.view.y * (y or 0)
end
if w <= 1 then
w = ctx.view.w * (w or 1)
end
if h <= 1 then
h = ctx.view.h * (h or 1)
end
return setmetatable({
x = x,
y = y,
w = w,
h = h
}, layout)
end
--Sets mode for the proceding operations
function layout.mode()
end
--Sets padding for the next operations
function layout.pad()
end
--Sets margins for the proceding operations
function layout.margin()
end
function layout.offset()
end
function layout:draw()
end
layout(0,0,1,1)
return layout

0
control/position.lua Normal file
View File

7
control/size.lua Normal file
View File

@ -0,0 +1,7 @@
local path = string.sub(..., 1, string.len(...) - string.len(".control.size"))
local stack = require(path..'.core.stack')
--Sets the computed/minimum size of an element to be used with layout calculations and rendering
return function(w, h)
end

19
control/state.lua Normal file
View File

@ -0,0 +1,19 @@
local path = string.sub(..., 1, string.len(...) - string.len(".control.state"))
local context = require(path.. ".core.context")
return function (base)
local base = base or {}
local fakeBase = {}
local activeContext = context.getContext()
return setmetatable({},{
__index = function(t, index)
return fakeBase[index] or base[index]
end,
__newindex = function(t, index, val)
if fakeBase[index] ~= val then
fakeBase[index] = val
activeContext:bubbleUpdate()
end
end
})
end

View File

@ -2,86 +2,14 @@
--[[ Love is currently a hard dependency, although not in many places ]] --[[ Love is currently a hard dependency, although not in many places ]]
local path = string.sub(..., 1, string.len(...) - string.len(".core.element")) local path = string.sub(..., 1, string.len(...) - string.len(".core.element"))
local helium = require(path .. ".dummy") local helium = require(path .. ".dummy")
local context = require(path.. ".core.stack")
---@class context
local context = {}
context.__index = context
local activeContext
---@param elem element
function context.new(elem)
local ctx = setmetatable({view = elem.view, element = elem, childrenContexts={}}, context)
return ctx
end
function context:bubbleUpdate()
self.element.settings.pendingUpdate = true
self.element.settings.needsRendering = true
if self.parentCtx and self.parentCtx~=self then
self.parentCtx:bubbleUpdate()
end
end
function context:set()
if activeContext then
if not self.parentCtx and activeContext~=self then
self.parentCtx = activeContext
activeContext.childrenContexts[#activeContext.childrenContexts] = self
end
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
function context:destroy()
self.elem:undraw()
for i=1,#self.childrenContexts do
self.childrenContexts[i]:destroy()
end
end
---@class element ---@class element
local element = {} local element = {}
element.__index = element element.__index = element
function element.newProxy(base)
local base = base or {}
local fakeBase = {}
local activeContext = activeContext
return setmetatable({},{
__index = function(t, index)
return fakeBase[index] or base[index]
end,
__newindex = function(t, index, val)
if fakeBase[index] ~= val then
fakeBase[index] = val
activeContext:bubbleUpdate()
end
end
})
end
local type,pcall = type,pcall local type,pcall = type,pcall
setmetatable(element,{ setmetatable(element, {
__call = function(cls, ...) __call = function(cls, ...)
local self local self
local func, loader, w, h, param = ... local func, loader, w, h, param = ...
@ -102,12 +30,9 @@ setmetatable(element,{
end end
}) })
--Dummy functions
function element:renderer() print('no renderer') end
--Control functions --Control functions
--The new function that should be used for element creation --The new function that should be used for element creation
function element:new(w, h, param) function element:new(param)
local dimensions local dimensions
--save the parameters --save the parameters
self.parameters = {} self.parameters = {}
@ -120,11 +45,18 @@ function element:new(w, h, param)
isSetup = false, isSetup = false,
pendingUpdate = true, pendingUpdate = true,
needsRendering = true, needsRendering = true,
--Unused for now?
calculatedDimensions = true, calculatedDimensions = true,
--Is this the first render
firstDraw = true, firstDraw = true,
--Stabilize the internal canvas, draw it twice on first load --Stabilize the internal canvas, draw it twice on first load
stabilize = true, stabilize = true,
inserted = false --Has it been inserted in to the buffer
inserted = false,
--Whether this element is created and drawn instantly (and doesn't need a canvas)
immediate = false,
--Render this element in the external buffer with absolute coordinates
absolutePosition = false,
} }
self.baseState = {} self.baseState = {}
@ -132,11 +64,13 @@ function element:new(w, h, param)
self.baseView = { self.baseView = {
x = 0, x = 0,
y = 0, y = 0,
w = w or 10, w = 10,
h = h or 10, h = 10,
minW = 10,
minH = 10,
} }
self.view = setmetatable({},{ self.view = setmetatable({}, {
__index = function(t, index) __index = function(t, index)
return self.baseView[index] return self.baseView[index]
end, end,
@ -145,41 +79,26 @@ function element:new(w, h, param)
self.baseView[index] = val self.baseView[index] = val
self.context:bubbleUpdate() self.context:bubbleUpdate()
self:updateInputCtx() self:updateInputCtx()
if self.view.onChange then
self.view.onChange()
end
end end
end end
}) })
--Context makes sure element internals don't have to worry about absolute coordinates --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.context = context.new(self)
self.classlessFuncs = {}
--Allows manipulation of the arbitrary state
self.classlessFuncs.getState = function ()
return self.state
end
--Allows manipulation of rendering width height, relative X and relative Y
self.classlessFuncs.getView = function ()
self.settings.restrictView = true
return self.view
end
self:setup()
self.settings.isSetup = true
end end
function element:updateInputCtx() function element:updateInputCtx()
self.inputContext:update() self.context.inputContext:update()
if self.settings.canvasW then if self.settings.canvasW then
if self.settings.canvasW < self.view.w or self.settings.canvasH < self.view.h then if self.settings.canvasW < self.view.w or self.settings.canvasH < self.view.h then
self.settings.canvasW = self.view.w*1.25 self.settings.canvasW = self.view.w*1.25
self.settings.canvasH = self.view.h*1.25 self.settings.canvasH = self.view.h*1.25
self.canvas = love.graphics.newCanvas(self.view.w*1.25, self.view.h*1.25)
elseif self.settings.canvasW > self.view.w*1.50 or self.settings.canvasH > self.view.h*1.50 then
self.settings.canvasW = self.view.w*1.25
self.settings.canvasH = self.view.h*1.25
self.canvas = love.graphics.newCanvas(self.view.w*1.25, self.view.h*1.25) self.canvas = love.graphics.newCanvas(self.view.w*1.25, self.view.h*1.25)
end end
@ -187,46 +106,34 @@ function element:updateInputCtx()
end end
end end
--Immediate mode code(don't call directly)
function element.immediate(param, func, x, y, w, h)
end
--Hotswapping code --Hotswapping code
function element:reLoader(newFunc) function element:reLoader(newFunc)
self.inputContext:set()
self.context:set() self.context:set()
self.inputContext:destroy()
self.parentFunc = newFunc self.parentFunc = newFunc
if type(self.parentFunc)=='function' then if type(self.parentFunc) == 'function' then
self.renderer = self.parentFunc(self.parameters,self.state,self.view) self.renderer = self.parentFunc(self.parameters, self.state, self.view)
else else
self.renderer = self.parentFunc self.renderer = self.parentFunc
end end
self.context:unset() self.context:unset()
self.inputContext:unset()
self.context:bubbleUpdate() self.context:bubbleUpdate()
end end
local newCanvas,newQuad = love.graphics.newCanvas,love.graphics.newQuad local newCanvas,newQuad = love.graphics.newCanvas,love.graphics.newQuad
--Called once dimensions are validated --Called once dimensions are validated
function element:setup() 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
if self.baseState.onUpdate then
self.baseState.onUpdate()
end
self.context:bubbleUpdate()
end
end
})
self.parameters = setmetatable({},{ self.parameters = setmetatable({}, {
__index = function(t, index) __index = function(t, index)
return self.baseParams[index] return self.baseParams[index]
end, end,
@ -238,6 +145,9 @@ function element:setup()
end end
}) })
self.context:set()
self.renderer = self.parentFunc(self.parameters, self.view.w, self.view.h)
self.context:unset()
self.settings.canvasW = self.view.w*1.25 self.settings.canvasW = self.view.w*1.25
self.settings.canvasH = self.view.h*1.25 self.settings.canvasH = self.view.h*1.25
@ -245,21 +155,20 @@ function element:setup()
self.canvas = newCanvas(self.view.w*1.25, self.view.h*1.25) self.canvas = newCanvas(self.view.w*1.25, self.view.h*1.25)
self.quad = newQuad(0, 0, self.view.w, self.view.h, self.view.w*1.25, self.view.h*1.25) self.quad = newQuad(0, 0, self.view.w, self.view.h, self.view.w*1.25, self.view.h*1.25)
self.context:set()
self.inputContext:set()
self.renderer = self.parentFunc(self.parameters,self.state,self.view)
self.inputContext:unset()
self.inputContext:afterLoad()
self.context:unset()
self.settings.isSetup = true self.settings.isSetup = true
end end
local setColor,rectangle,setFont,printf = love.graphics.setColor,love.graphics.rectangle,love.graphics.setFont,love.graphics.printf local setColor,rectangle,setFont,printf = love.graphics.setColor,love.graphics.rectangle,love.graphics.setFont,love.graphics.printf
function element:classlessRender() function element:errorRender(msg)
setColor(1, 0, 0)
rectangle('line', 0, 0, self.view.w, self.view.h)
setColor(1, 1, 1)
printf("Error: "..msg, 0, 0, self.view.w)
end
self.inputContext:set() function element:internalRender()
if type(self.renderer)=='function' then
if type(self.renderer) == 'function' then
love.graphics.push() love.graphics.push()
love.graphics.origin() love.graphics.origin()
local status, err = pcall(self.renderer) local status, err = pcall(self.renderer)
@ -269,44 +178,41 @@ function element:classlessRender()
if helium.conf.HARD_ERROR then if helium.conf.HARD_ERROR then
error(status) error(status)
end end
setColor(1,0,0) self:errorRender(status)
rectangle('line',0,0,self.view.w,self.view.h)
setColor(1,1,1)
printf("Error: "..err,0,0,self.view.w)
end end
elseif type(self.renderer)=='string' then elseif type(self.renderer) == 'string' then
if helium.conf.HARD_ERROR then if helium.conf.HARD_ERROR then
error(self.renderer) error(self.renderer)
end end
setColor(1,0,0) self:errorRender(self.renderer)
rectangle('line',0,0,self.view.w,self.view.h)
setColor(1,1,1)
printf("Error: "..self.renderer,0,0,self.view.w)
end end
self.inputContext:unset()
end end
local getCanvas,setCanvas,clear = love.graphics.getCanvas,love.graphics.setCanvas,love.graphics.clear local getCanvas,setCanvas,clear = love.graphics.getCanvas,love.graphics.setCanvas,love.graphics.clear
function element:renderWrapper() function element:renderWrapper()
if not self.settings.isSetup then
self:setup()
self.settings.isSetup = true
end
self.context:set()
local cnvs = getCanvas() local cnvs = getCanvas()
setCanvas({self.canvas, stencil = true}) setCanvas({self.canvas, stencil = true})
clear() clear()
if self.parameters then if self.parameters then
self:classlessRender() self:internalRender()
self.settings.pendingUpdate = false self.settings.pendingUpdate = false
end end
setCanvas(cnvs) setCanvas(cnvs)
self.context:unset()
end end
local draw = love.graphics.draw local draw = love.graphics.draw
function element:externalRender() function element:externalRender()
self.context:set()
if self.settings.stabilize and not self.settings.needsRendering then if self.settings.stabilize and not self.settings.needsRendering then
self.settings.stabilize = false self.settings.stabilize = false
self.settings.needsRendering = true self.settings.needsRendering = true
@ -319,8 +225,6 @@ function element:externalRender()
setColor(1,1,1) setColor(1,1,1)
draw(self.canvas, self.quad, self.view.x, self.view.y) draw(self.canvas, self.quad, self.view.x, self.view.y)
self.context:unset()
end end
function element:externalUpdate() function element:externalUpdate()
@ -332,10 +236,12 @@ function element:externalUpdate()
self.settings.needsRendering = true self.settings.needsRendering = true
self.settings.pendingUpdate = false self.settings.pendingUpdate = false
end end
return self.settings.remove return self.settings.remove
end end
local insert = table.insert local insert = table.insert
--External functions --External functions
--Acts as the entrypoint for beginning rendering --Acts as the entrypoint for beginning rendering
---@param x number ---@param x number
@ -354,12 +260,7 @@ function element:draw(x, y)
self.settings.firstDraw = false self.settings.firstDraw = false
end end
if not self.settings.isSetup then if context.getContext() then
self.inputContext:unsuspend()
self.settings.isSetup = true
end
if activeContext then
self:externalRender() self:externalRender()
elseif not self.settings.inserted then elseif not self.settings.inserted then
self.settings.inserted = true self.settings.inserted = true
@ -367,16 +268,14 @@ function element:draw(x, y)
end end
end end
function element:undraw() function element:destroy()
if self.baseState.onDestroy then if self.baseState.onDestroy then
self.baseState.onDestroy() self.baseState.onDestroy()
end end
self.settings.remove = true self.settings.remove = true
self.settings.firstDraw = true self.settings.firstDraw = true
self.settings.isSetup = false self.settings.isSetup = false
self.inputContext:set() self.context:destroy()
self.inputContext:suspend()
self.inputContext:unset()
end end
return element return element

72
core/signals.lua Normal file
View File

@ -0,0 +1,72 @@
--Internal event/zone/perf-log system
local signals = {}
signals.__index = signals
function signals.newController()
return setmetatable({
stack = {},
eventSubs = {},
zoneSubs = {},
startTime = 0,
totalTime = 0
}, signals)
end
function signals:push(name)
self.stack[#self.stack+1] = {name = name}
self.startTime = love.timer.getTime()
if self.zoneSubs[name] then
for i, e in ipairs(self.zoneSubs[name]) do
if e.on and e.func() then
end
end
end
end
function signals:pop()
local name = self.stack[#self.stack].name
if self.zoneSubs[name] then
for i, e in ipairs(self.zoneSubs[name]) do
if not e.on and e.func() then
end
end
end
self.totalTime = love.timer.getTime() - self.startTime
self.stack[#self.stack] = nil
end
function signals:emitEvent(name, content)
if self.eventSubs[name] then
for i,e in ipairs(self.eventSubs[name]) do
e.func(content)
end
end
end
function signals:onEvent(func, event)
if not self.eventSubs[event] then
self.eventSubs[event] = {}
end
self.eventSubs[event][#self.eventSubs[event]+1] = {func = func}
end
--on - true when new zone is pushed
-- false when zone is popped
function signals:onSignal(func, name, on)
if not self.zoneSubs[name] then
self.zoneSubs[name] = {}
end
self.zoneSubs[name][#self.zoneSubs[name]+1] = {func = func, on = on}
end
return signals

88
core/stack.lua Normal file
View File

@ -0,0 +1,88 @@
--Builds the element stack basically
local path = string.sub(..., 1, string.len(...) - string.len(".core.stack"))
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,
childrenContexts = {},
inputContext = helium.input.newContext(elem)
}, context)
return ctx
end
function context:bubbleUpdate()
self.element.settings.pendingUpdate = true
self.element.settings.needsRendering = true
if self.parentCtx and self.parentCtx~=self then
self.parentCtx:bubbleUpdate()
end
end
function context:set()
if activeContext then
if not self.parentCtx and activeContext~=self then
self.parentCtx = activeContext
activeContext.childrenContexts[#activeContext.childrenContexts] = self
end
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
self.inputContext:set()
end
function context:unset()
self.inputContext:unset()
self.inputContext:afterLoad()
if self.parentCtx then
activeContext = self.parentCtx
else
activeContext = nil
end
end
function context:unsuspend()
self.inputContext:unsuspend()
end
function context:destroy()
self.elem:undraw()
for i=1,#self.childrenContexts do
self.childrenContexts[i]:destroy()
end
end
function context:suspend()
self.inputContext:set()
self.inputContext:suspend()
self.inputContext:unset()
end
--Function meant for external context capture
function context.getContext()
return activeContext
end
return context

18
hooks/README.md Normal file
View File

@ -0,0 +1,18 @@
Hooks are additional functions to utilize the element lifecycle more granularly
e.g.
```lua
local onDestroyHook = require("helium/hooks/onDestroy")
return function (param)
onDestroyHook(function()
doSomething()
end)
return function()
love.graphics.print("Help")
end
end
```

0
hooks/onDestroy.lua Normal file
View File

0
hooks/onLayout.lua Normal file
View File

0
hooks/onLoad.lua Normal file
View File

0
hooks/onUpdate.lua Normal file
View File