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

@ -20,7 +20,7 @@ function(param, view)
end
```
and you can make that function in to an element 'factory' like this:
and you can make that function into an element 'factory' like this:
```lua
elementCreator = helium(function(param, view)
@ -30,7 +30,7 @@ elementCreator = helium(function(param, view)
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
element = elementCreator({text = 'foo-bar'}, 100, 20)

View File

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

View File

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

View File

@ -1,13 +1,51 @@
local path = string.sub(..., 1, string.len(...) - string.len(".core.input"))
local stack = require(path .. ".core.stack")
local helium = require(path .. ".dummy")
local input = {
eventHandlers = {},
subscriptions = {},
subscriptions = setmetatable({}, {__index = function (t, index)
return helium.scene.activeScene and helium.scene.activeScene[index] or nil
end}),
activeEvents = {}
}
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)
if t1 == t2 then
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
function context.new(elem)
local ctx = setmetatable({
capturedChilds = {},
view = elem.view,
element = elem,
childrenContexts = {},
childRenderTime = 0,
deferChildren = false,
events = event.new(),
capturedChilds = {},
temporalZ = {z = nil},
}, context)
@ -221,6 +221,13 @@ function context:offPosChange(callback)
self.events:unsub('poschange', callback)
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 context.getContext()
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
helium.utils = require(path..".utils")
helium.scene = require(path..".core.scene")
helium.element = require(path..".core.element")
helium.input = require(path..".core.input")
helium.loader = require(path..".loader")
helium.stack = require(path..".core.stack")
helium.atlas = require(path..".core.atlas")
helium.elementBuffer = {}
helium.elementInsertionQueue = {}
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)
return setmetatable({
draw = function (param, inputs, x, y, w, h)
return helium.element.immediate(param, inputs, chunk, x, y, w, h)
end
},
{__call = function(s, param, w, h)
@ -37,130 +41,6 @@ setmetatable(helium, {__call = function(s, chunk)
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
helium.helium = helium
return helium