This commit is contained in:
Elmārs Āboliņš 2021-01-25 02:18:31 +02:00
parent 58e54dd182
commit 0422dd5cfd
12 changed files with 272 additions and 178 deletions

View File

@ -30,7 +30,7 @@ elementCreator = helium(function(param, view)
end) end)
``` ```
then you call the element factory with parameters and optionally width and height: then you call the element factory with a table of parameters that will get passed to the element and optionally width and height:
```lua ```lua
element = elementCreator({text = 'foo-bar'}, 100, 20) element = elementCreator({text = 'foo-bar'}, 100, 20)

View File

@ -1,38 +1,35 @@
local atlas = {} local atlas = {}
local atlases
atlas.__index = atlas atlas.__index = atlas
local atlases ={}
atlases.__index = atlases
local BLOCK_SIZE = 5 local BLOCK_SIZE = 5
local coefficient = 1.5
function atlas.load()
if not atlases then
atlas.init()
end
end
function atlas.getRatio(index)
return atlases[index].taken_area/atlases[index].ideal_area
end
function atlas.getFreeArea(index)
return atlases[index].ideal_area - atlases[index].taken_area
end
local sw, sh = love.graphics.getDimensions()
function atlas.init()
atlases = {}
atlases[1] = atlas.new(sw, sh)
atlases[2] = atlas.new(sw, sh)
atlas.atlases = atlases
end
local selfRenderTime = false local selfRenderTime = false
local sw, sh = love.graphics.getDimensions()
function atlas.setBench(time) function atlases.create()
local self = {
atlases = {}
}
self.atlases[1] = atlas.new(sw, sh)
self.atlases[2] = atlas.new(sw, sh)
return setmetatable(self, atlases)
end
function atlases.setBench(time)
selfRenderTime = time selfRenderTime = time
end end
local coefficient = 1.5 function atlases:getRatio(index)
function atlas.assign(element) return self.atlases[index].taken_area/self.atlases[index].ideal_area
end
function atlases:getFreeArea(index)
return self.atlases[index].ideal_area - self.atlases[index].taken_area
end
function atlases:assign(element)
local avg, sum, canvasID = 0, 0, element.context:getCanvasIndex(true) or 1 local avg, sum, canvasID = 0, 0, element.context:getCanvasIndex(true) or 1
for i, e in ipairs(element.renderBench) do for i, e in ipairs(element.renderBench) do
@ -41,10 +38,10 @@ function atlas.assign(element)
avg = sum/#element.renderBench avg = sum/#element.renderBench
local areaBelow = atlas.getFreeArea(canvasID) local areaBelow = self:getFreeArea(canvasID)
local area = element.view.h*element.view.w local area = element.view.h*element.view.w
local areaCoef = (2-(atlas.getRatio(canvasID)) )-(area/(areaBelow/(4+3*atlas.getRatio(canvasID)))) local areaCoef = (2-(self:getRatio(canvasID)) )-(area/(areaBelow/(4+3*self:getRatio(canvasID))))
local speedCoef = avg/selfRenderTime local speedCoef = avg/selfRenderTime
if not ((areaCoef+speedCoef)>coefficient) then if not ((areaCoef+speedCoef)>coefficient) then
@ -53,11 +50,11 @@ function atlas.assign(element)
local elW = element.view.w local elW = element.view.w
local elH = element.view.h local elH = element.view.h
local canvas, quad, interQuad = atlases[canvasID]:assignElement(element) local canvas, quad, interQuad = self.atlases[canvasID]:assignElement(element)
if not canvas and atlases[canvasID].ideal_area < atlases[canvasID].taken_area*4 then if not canvas and self.atlases[canvasID].ideal_area < self.atlases[canvasID].taken_area*4 then
--print('refragmenting ;3') --print('refragmenting ;3')
atlases[canvasID]:refragment() self.atlases[canvasID]:refragment()
canvas, quad, interQuad = atlases[canvasID]:assignElement(element) canvas, quad, interQuad = self.atlases[canvasID]:assignElement(element)
if not canvas then if not canvas then
--print('ran out of space') --print('ran out of space')
end end
@ -67,18 +64,23 @@ function atlas.assign(element)
return canvas, quad, interQuad return canvas, quad, interQuad
end end
function atlas.unassign(element) function atlases:unassign(element)
local canvasID = element.context:getCanvasIndex(true) or 1 local canvasID = element.context:getCanvasIndex(true) or 1
atlases[canvasID]:unassignElement(element) self.atlases[canvasID]:unassignElement(element)
end end
function atlas.unassignAll() function atlases:unassignAll()
createdAtlas.users = {} self.atlases[1].users = {}
createdAtlas:unMarkTiles(1, 1, createdAtlas.tileW, createdAtlas.tileH) self.atlases[2].users = {}
createdAtlas.taken_area = 0
self.atlases[1]:unMarkTiles(1, 1, self.atlases[1].tileW, self.atlases[1].tileH)
self.atlases[2]:unMarkTiles(1, 1, self.atlases[2].tileW, self.atlases[2].tileH)
self.atlases[1].taken_area = 0
self.atlases[2].taken_area = 0
end end
function atlas.onscreenchange(newW, newH) function atlases.onscreenchange(newW, newH)
end end
@ -139,7 +141,6 @@ function atlas:assignElement(element)
w = tileSizeX, w = tileSizeX,
h = tileSizeY, h = tileSizeY,
quad = quad, quad = quad,
interQuad = iquad
} }
self:markTiles(x, y, tileSizeX, tileSizeY) self:markTiles(x, y, tileSizeX, tileSizeY)
@ -241,4 +242,4 @@ function atlas:unassignElement(element)
self.users[element] = nil self.users[element] = nil
end end
return atlas return atlases

View File

@ -3,6 +3,7 @@
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") local context = require(path.. ".core.stack")
local scene = require(path.. ".core.scene")
---@class Element ---@class Element
local element = {} local element = {}
@ -157,9 +158,11 @@ end
local newCanvas, newQuad = love.graphics.newCanvas, love.graphics.newQuad local newCanvas, newQuad = love.graphics.newCanvas, love.graphics.newQuad
function element:createCanvas() function element:createCanvas()
self.canvas, self.quad = helium.atlas.assign(self) self.canvas, self.quad = scene.activeScene.atlas:assign(self)
print('here')
if not self.canvas then if not self.canvas then
print('failed')
self.settings.failedCanvas = true self.settings.failedCanvas = true
self.settings.hasCanvas = false self.settings.hasCanvas = false
return return
@ -266,7 +269,7 @@ function element:externalRender()
self:renderWrapper() self:renderWrapper()
end end
end end
--lg.setScissor() lg.setScissor()
setCanvas(cnvs) setCanvas(cnvs)
@ -282,7 +285,11 @@ end
function element:externalUpdate() function element:externalUpdate()
self.context:zIndex() self.context:zIndex()
if not self.settings.failedCanvas and self.settings.testRenderPasses == 0 and not self.settings.hasCanvas then if not self.settings.failedCanvas
and self.settings.testRenderPasses == 0
and not self.settings.hasCanvas
and scene.activeScene.cached then
self:createCanvas() self:createCanvas()
self.settings.pendingUpdate = true self.settings.pendingUpdate = true
@ -296,7 +303,7 @@ function element:externalUpdate()
if self.deferResize then if self.deferResize then
self.context:sizeChanged() self.context:sizeChanged()
if self.settings.hasCanvas then if self.settings.hasCanvas then
helium.atlas.unassign(self) scene.activeScene.atlas:unassign(self)
self.settings.hasCanvas = false self.settings.hasCanvas = false
self.settings.testRenderPasses = 15 self.settings.testRenderPasses = 15
self.canvas = nil self.canvas = nil
@ -334,7 +341,7 @@ function element:draw(x, y, w, h)
end end
elseif not self.settings.inserted then elseif not self.settings.inserted then
self.settings.inserted = true self.settings.inserted = true
insert(helium.elementInsertionQueue, self) insert(scene.activeScene.buffer, self)
end end
if self.settings.firstDraw then if self.settings.firstDraw then

View File

@ -1,13 +1,51 @@
local path = string.sub(..., 1, string.len(...) - string.len(".core.input")) local path = string.sub(..., 1, string.len(...) - string.len(".core.input"))
local stack = require(path .. ".core.stack") local stack = require(path .. ".core.stack")
local helium = require(path .. ".dummy")
local input = { local input = {
eventHandlers = {}, eventHandlers = {},
subscriptions = {}, subscriptions = setmetatable({}, {__index = function (t, index)
return helium.scene.activeScene and helium.scene.activeScene[index] or nil
end}),
activeEvents = {} activeEvents = {}
} }
input.__index = input input.__index = input
--Middle man functions
local orig = {
mousepressed = love.handlers['mousepressed'],
mousereleased = love.handlers['mousereleased'],
keypressed = love.handlers['keypressed'],
keyreleased = love.handlers['keyreleased'],
mousemoved = love.handlers['mousemoved']
}
love.handlers['mousepressed'] = function(x, y, btn, d, e, f)
if not input.eventHandlers.mousepressed(x, y, btn, d, e ,f) then
orig.mousepressed(x, y, btn, d, e, f)
end
end
love.handlers['mousereleased'] = function(x, y, btn, d, e, f)
if not input.eventHandlers.mousereleased(x, y, btn, d, e ,f) then
orig.mousereleased(x, y, btn, d, e, f)
end
end
love.handlers['keypressed'] = function(key, b, c, d, e, f)
if not input.eventHandlers.keypressed(key, b, c, d, e, f) then
orig.keypressed(key, b, c, d, e, f)
end
end
love.handlers['keyreleased'] = function(key, b, c, d, e, f)
if not input.eventHandlers.keyreleased(key, b, c, d, e, f) then
orig.keyreleased(key, b, c, d, e, f)
end
end
love.handlers['mousemoved'] = function(x, y, dx, dy, e, f)
if not input.eventHandlers.mousemoved(x, y, dx, dy, e, f) then
orig.mousemoved(x, y, dx, dy, e, f)
end
end
local function sortFunc(t1, t2) local function sortFunc(t1, t2)
if t1 == t2 then if t1 == t2 then
return false return false

82
core/scene.lua Normal file
View File

@ -0,0 +1,82 @@
local path = string.sub(..., 1, string.len(...) - string.len(".core.scene"))
local atlas = require(path..'.core.atlas')
local helium = require(path..'.dummy')
local input = require(path..'.core.input')
local scene = {
activeScene = nil
}
scene.__index = scene
function scene.new(cached)
local self = {
atlas = cached and atlas.create() or nil,
cached = cached or false,
subscriptions = {},
buffer = {}
}
return setmetatable(self, scene)
end
local skipframes = 10
function scene.bench()
if skipframes == 0 then
local startTime = love.timer.getTime()
for i = 1, 20 do
love.graphics.print(i,-100,-100)
end
helium.setBench((love.timer.getTime()-startTime)/5)
elseif skipframes>0 then
skipframes = skipframes - 1
end
end
function scene:activate()
scene.activeScene = self
end
--Keeps the scene in memory with potentially the atlas
function scene:deactivate()
scene.activeScene = nil
end
function scene:reload()
self.atlas = self.cached and atlas.create() or nil
self.ioSubscriptions = {}
self.buffer = {}
end
--Nukes the scene and it's elements and atlases and subscriptions from memory
--To achieve same state as after creation, use reload
function scene:unload()
self.atlas = nil
self.buffer = nil
self.ioSubscriptions = nil
end
function scene:draw()
helium.stack.newFrame()
if not helium.benchNum then
scene.bench()
end
--We don't want any side effects affecting internal rendering
love.graphics.reset()
for i, e in ipairs(self.buffer) do
e:externalRender()
end
end
function scene:update(dt)
for i = 1, #self.buffer do
if self.buffer[i]:externalUpdate(i) then
table.remove(self.buffer, i)
end
end
end
return scene

View File

@ -14,13 +14,13 @@ local currentTemporalZ = 0
---@param elem element ---@param elem element
function context.new(elem) function context.new(elem)
local ctx = setmetatable({ local ctx = setmetatable({
capturedChilds = {},
view = elem.view, view = elem.view,
element = elem, element = elem,
childrenContexts = {}, childrenContexts = {},
childRenderTime = 0, childRenderTime = 0,
deferChildren = false, deferChildren = false,
events = event.new(), events = event.new(),
capturedChilds = {},
temporalZ = {z = nil}, temporalZ = {z = nil},
}, context) }, context)
@ -221,6 +221,13 @@ function context:offPosChange(callback)
self.events:unsub('poschange', callback) self.events:unsub('poschange', callback)
end end
function context:onEveryChild(func)
func(self.element)
for i, e in ipairs(self.childrenContexts) do
self.childrenContexts:onEveryChild(func)
end
end
--Function meant for external context capture --Function meant for external context capture
function context.getContext() function context.getContext()
return activeContext return activeContext

0
docs/API-reference.md Normal file
View File

0
docs/Hooks.md Normal file
View File

0
docs/Input-events.md Normal file
View File

0
docs/Layout.md Normal file
View File

79
docs/State-Input-Guide.md Normal file
View File

@ -0,0 +1,79 @@
# State and Input
This guide assumes you already have read the getting started in [README.md](../README.md)
And will use the [hello world repo](https://github.com/qeffects/helium-demo/) as a starting point
## State
UI elements tend to have various state, be it a button being pressed, a scroll bar's current value, current open tab, animations etc.
In helium you introduce state to your elements by importing the state module like this:
Note that you can have other values in the top level function(obviously), but changing them doesn't guarantee a display update
```lua
local useState = require 'helium.control.state'
```
And then using it inside the element like:
```lua
local elementCreator = helium(function(param, view)
local elementState = useState({var = 10})
.
return function()
end
end)
```
Now you can change the values of `elementState` and see the value update:
```lua
local elementCreator = helium(function(param, view)
local elementState = useState({var = 10})
return function()
elementState.var = elementState.var + 1
love.graphics.setColor(1, 1, 1)
love.graphics.print('elementState.var: '..elementState.var)
end
end)
```
This is where
## Input
comes in
Input is a convenient module for various input subscriptions, import it like this:
```lua
local input = require 'helium.core.input'
```
Now you can use it in conjunction with state in your element like this:
```lua
local elementCreator = helium(function(param, view)
local elementState = useState({down = false})
input('clicked', function()
elementState.down = not elementState.down
end)
return function()
if elementState.down then
love.graphics.setColor(1, 0, 0)
else
love.graphics.setColor(0, 1, 1)
end
love.graphics.print('button text')
end
end)
```
The text now should toggle between 2 colors whenever pressed
The full call signature of input is:
`local sub = input(eventType, callback, startOn, x, y, w, h)`
See the demo repository with this example here: ~~link
See all event types explained here: ~~link
There are a few pre-made hooks that abstract away state management, see here: ~~link

134
init.lua
View File

@ -17,19 +17,23 @@ else
end end
helium.utils = require(path..".utils") helium.utils = require(path..".utils")
helium.scene = require(path..".core.scene")
helium.element = require(path..".core.element") helium.element = require(path..".core.element")
helium.input = require(path..".core.input") helium.input = require(path..".core.input")
helium.loader = require(path..".loader") helium.loader = require(path..".loader")
helium.stack = require(path..".core.stack") helium.stack = require(path..".core.stack")
helium.atlas = require(path..".core.atlas") helium.atlas = require(path..".core.atlas")
helium.elementBuffer = {}
helium.elementInsertionQueue = {}
helium.__index = helium helium.__index = helium
function helium.setBench(time)
helium.benchNum = time
helium.element.setBench(time)
helium.atlas.setBench(time)
end
setmetatable(helium, {__call = function(s, chunk) setmetatable(helium, {__call = function(s, chunk)
return setmetatable({ return setmetatable({
draw = function (param, inputs, x, y, w, h) draw = function (param, inputs, x, y, w, h)
return helium.element.immediate(param, inputs, chunk, x, y, w, h)
end end
}, },
{__call = function(s, param, w, h) {__call = function(s, param, w, h)
@ -37,130 +41,6 @@ setmetatable(helium, {__call = function(s, chunk)
end,}) end,})
end}) end})
local skipframes = 10
local skip = true
function helium.load()
helium.atlas.load()
end
helium.load()
function helium.unload()
helium.atlas.unassignAll()
helium.elementBuffer = {}
end
function helium.draw()
helium.stack.newFrame()
if skipframes == 0 then
local startTime = love.timer.getTime()
for i = 1, 20 do
love.graphics.print(i,-100,-100)
end
helium.element.setBench((love.timer.getTime()-startTime)/5)
helium.atlas.setBench((love.timer.getTime()-startTime)/5)
elseif skipframes>0 then
skipframes = skipframes - 1
end
--We don't want any side effects affecting internal rendering
love.graphics.reset()
for i, e in ipairs(helium.elementBuffer) do
e:externalRender()
end
for i, e in ipairs(helium.elementInsertionQueue) do
table.insert(helium.elementBuffer, e)
end
helium.elementInsertionQueue = {}
end
function helium.update(dt)
for i = 1, #helium.elementBuffer do
if helium.elementBuffer[i]:externalUpdate(i) then
table.remove(helium.elementBuffer,i)
end
end
end
--[[
A user doesn't have to use this particular love.run
helium.render()
helium.update(dt)
Need to be called either through love.update and love.draw respectively
or put in to your custom love.run
And for inputs to work the love.event part needs to look something like this:
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a
end
end
if not(helium.eventHandlers[name]) or not(helium.eventHandlers[name](a, b, c, d, e, f)) then
love.handlers[name](a, b, c, d, e, f)
end
end
]]
if helium.conf.AUTO_RUN then
function love.run()
if love.load then love.load() end--love.arg.parseGameArguments(arg), arg) end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
local dt = 0
-- Main loop time.
return function()
-- Process events.
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a or 0
end
end
if not(helium.input.eventHandlers[name]) or not(helium.input.eventHandlers[name](a, b, c, d, e, f)) then
love.handlers[name](a, b, c, d, e, f)
end
end
end
-- Update dt, as we'll be passing it to update
if love.timer then dt = love.timer.step() end
-- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
helium.update(dt)
if love.graphics and love.graphics.isActive() then
love.graphics.origin()
love.graphics.clear(love.graphics.getBackgroundColor())
helium.draw()
if love.draw then love.draw() end
love.graphics.present()
end
if love.timer then love.timer.sleep(0.001) end
end
end
end
--Typescript --Typescript
helium.helium = helium helium.helium = helium
return helium return helium