diff --git a/control/context.lua b/control/context.lua new file mode 100644 index 0000000..e69de29 diff --git a/control/layout.lua b/control/layout.lua new file mode 100644 index 0000000..19f0253 --- /dev/null +++ b/control/layout.lua @@ -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 diff --git a/control/position.lua b/control/position.lua new file mode 100644 index 0000000..e69de29 diff --git a/control/size.lua b/control/size.lua new file mode 100644 index 0000000..9bf4b96 --- /dev/null +++ b/control/size.lua @@ -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 \ No newline at end of file diff --git a/control/state.lua b/control/state.lua new file mode 100644 index 0000000..c819a67 --- /dev/null +++ b/control/state.lua @@ -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 \ No newline at end of file diff --git a/core/element.lua b/core/element.lua index 8cbd2e5..0739329 100644 --- a/core/element.lua +++ b/core/element.lua @@ -2,86 +2,14 @@ --[[ Love is currently a hard dependency, although not in many places ]] 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, 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 +local context = require(path.. ".core.stack") ---@class element local 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 -setmetatable(element,{ +setmetatable(element, { __call = function(cls, ...) local self local func, loader, w, h, param = ... @@ -102,12 +30,9 @@ setmetatable(element,{ end }) ---Dummy functions -function element:renderer() print('no renderer') end - --Control functions --The new function that should be used for element creation -function element:new(w, h, param) +function element:new(param) local dimensions --save the parameters self.parameters = {} @@ -120,11 +45,18 @@ function element:new(w, h, param) isSetup = false, pendingUpdate = true, needsRendering = true, + --Unused for now? calculatedDimensions = true, + --Is this the first render firstDraw = true, --Stabilize the internal canvas, draw it twice on first load 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 = {} @@ -132,11 +64,13 @@ function element:new(w, h, param) self.baseView = { x = 0, y = 0, - w = w or 10, - h = h or 10, + w = 10, + h = 10, + minW = 10, + minH = 10, } - self.view = setmetatable({},{ + self.view = setmetatable({}, { __index = function(t, index) return self.baseView[index] end, @@ -145,41 +79,26 @@ function element:new(w, h, param) self.baseView[index] = val self.context:bubbleUpdate() self:updateInputCtx() - if self.view.onChange then - self.view.onChange() - end end end }) --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.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 function element:updateInputCtx() - self.inputContext:update() + self.context.inputContext:update() if self.settings.canvasW 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.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) end @@ -187,46 +106,34 @@ function element:updateInputCtx() end end +--Immediate mode code(don't call directly) +function element.immediate(param, func, x, y, w, h) + +end + --Hotswapping code function element:reLoader(newFunc) - self.inputContext:set() self.context:set() - self.inputContext:destroy() self.parentFunc = newFunc - if type(self.parentFunc)=='function' then - self.renderer = self.parentFunc(self.parameters,self.state,self.view) + if type(self.parentFunc) == 'function' then + self.renderer = self.parentFunc(self.parameters, self.state, self.view) else self.renderer = self.parentFunc end self.context:unset() - self.inputContext:unset() self.context:bubbleUpdate() end local newCanvas,newQuad = love.graphics.newCanvas,love.graphics.newQuad --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 - if self.baseState.onUpdate then - self.baseState.onUpdate() - end - self.context:bubbleUpdate() - end - end - }) - self.parameters = setmetatable({},{ + self.parameters = setmetatable({}, { __index = function(t, index) return self.baseParams[index] end, @@ -238,6 +145,9 @@ function element:setup() 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.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.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 end 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() - if type(self.renderer)=='function' then +function element:internalRender() + + if type(self.renderer) == 'function' then love.graphics.push() love.graphics.origin() local status, err = pcall(self.renderer) @@ -269,44 +178,41 @@ function element:classlessRender() if helium.conf.HARD_ERROR then error(status) end - setColor(1,0,0) - rectangle('line',0,0,self.view.w,self.view.h) - setColor(1,1,1) - printf("Error: "..err,0,0,self.view.w) + self:errorRender(status) end - elseif type(self.renderer)=='string' then + elseif type(self.renderer) == 'string' then if helium.conf.HARD_ERROR then error(self.renderer) end - setColor(1,0,0) - rectangle('line',0,0,self.view.w,self.view.h) - setColor(1,1,1) - printf("Error: "..self.renderer,0,0,self.view.w) + self:errorRender(self.renderer) end - - self.inputContext:unset() end local getCanvas,setCanvas,clear = love.graphics.getCanvas,love.graphics.setCanvas,love.graphics.clear function element:renderWrapper() + if not self.settings.isSetup then + self:setup() + self.settings.isSetup = true + end + + self.context:set() local cnvs = getCanvas() setCanvas({self.canvas, stencil = true}) clear() if self.parameters then - self:classlessRender() + self:internalRender() self.settings.pendingUpdate = false end setCanvas(cnvs) + self.context:unset() end local draw = love.graphics.draw function element:externalRender() - self.context:set() - if self.settings.stabilize and not self.settings.needsRendering then self.settings.stabilize = false self.settings.needsRendering = true @@ -319,8 +225,6 @@ function element:externalRender() setColor(1,1,1) draw(self.canvas, self.quad, self.view.x, self.view.y) - - self.context:unset() end function element:externalUpdate() @@ -332,10 +236,12 @@ function element:externalUpdate() self.settings.needsRendering = true self.settings.pendingUpdate = false end + return self.settings.remove end local insert = table.insert + --External functions --Acts as the entrypoint for beginning rendering ---@param x number @@ -354,12 +260,7 @@ function element:draw(x, y) self.settings.firstDraw = false end - if not self.settings.isSetup then - self.inputContext:unsuspend() - self.settings.isSetup = true - end - - if activeContext then + if context.getContext() then self:externalRender() elseif not self.settings.inserted then self.settings.inserted = true @@ -367,16 +268,14 @@ function element:draw(x, y) end end -function element:undraw() +function element:destroy() if self.baseState.onDestroy then self.baseState.onDestroy() end self.settings.remove = true self.settings.firstDraw = true self.settings.isSetup = false - self.inputContext:set() - self.inputContext:suspend() - self.inputContext:unset() + self.context:destroy() end return element diff --git a/core/signals.lua b/core/signals.lua new file mode 100644 index 0000000..fb10bf4 --- /dev/null +++ b/core/signals.lua @@ -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 \ No newline at end of file diff --git a/core/stack.lua b/core/stack.lua new file mode 100644 index 0000000..ebc20d2 --- /dev/null +++ b/core/stack.lua @@ -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 \ No newline at end of file diff --git a/hooks/README.md b/hooks/README.md new file mode 100644 index 0000000..52c2a95 --- /dev/null +++ b/hooks/README.md @@ -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 + +``` \ No newline at end of file diff --git a/hooks/onDestroy.lua b/hooks/onDestroy.lua new file mode 100644 index 0000000..e69de29 diff --git a/hooks/onLayout.lua b/hooks/onLayout.lua new file mode 100644 index 0000000..e69de29 diff --git a/hooks/onLoad.lua b/hooks/onLoad.lua new file mode 100644 index 0000000..e69de29 diff --git a/hooks/onUpdate.lua b/hooks/onUpdate.lua new file mode 100644 index 0000000..e69de29