Scene
This commit is contained in:
parent
58e54dd182
commit
0422dd5cfd
@ -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)
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
82
core/scene.lua
Normal 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
|
||||
@ -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
0
docs/API-reference.md
Normal file
0
docs/Hooks.md
Normal file
0
docs/Hooks.md
Normal file
0
docs/Input-events.md
Normal file
0
docs/Input-events.md
Normal file
0
docs/Layout.md
Normal file
0
docs/Layout.md
Normal file
79
docs/State-Input-Guide.md
Normal file
79
docs/State-Input-Guide.md
Normal 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
134
init.lua
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user