Documentation

This commit is contained in:
Elmārs Āboliņš 2021-06-22 14:12:19 +03:00
parent 33c16efaff
commit f85b19c118
16 changed files with 653 additions and 14 deletions

View File

@ -115,4 +115,4 @@ end)
[View the resulting hello world repository here](https://github.com/qeffects/helium-demo/) [View the resulting hello world repository here](https://github.com/qeffects/helium-demo/)
Or continue on to the State and Input guide: [Here](./docs/State-Input-Guide.md) Or continue on to the State and Input guide: [Here](./docs/State-Input-Guide.md)
If you are using gamestates, scene guide will be of interest: ~~ If you are using gamestates, scene guide will be of interest: [Here](./docs/core/Scenes.md)

View File

@ -214,15 +214,17 @@ function element:setParam(p)
end end
function element:setSize(w, h) function element:setSize(w, h)
self.view.w = w or self.view.w local w, h = w or self.view.w, h or self.view.h
self.view.h = h or self.view.h
self.view.w = math.max(w, self.view.minW)
self.view.h = math.max(h, self.view.minH)
end end
function element:setCalculatedSize(w, h) function element:setMinSize(w, h)
self.view.minW = w or self.view.minW self.view.minW = w or self.view.minW
self.view.minH = h or self.view.minH self.view.minH = h or self.view.minH
self.view.w = w or self.view.minW self.view.w = math.max(self.view.w, w, self.view.minW)
self.view.h = h or self.view.minH self.view.h = math.max(self.view.h, h, self.view.minH)
end end
local dummy = function() end local dummy = function() end

View File

@ -95,10 +95,9 @@ function scene:drawAtlases(x, y)
end end
---Updates this scene and it's elements ---Updates this scene and it's elements
---@param dt number function scene:update()
function scene:update(dt)
for i = 1, #self.buffer do for i = 1, #self.buffer do
if self.buffer[i]:externalUpdate(i) then if self.buffer[i]:externalUpdate() then
table.remove(self.buffer, i) table.remove(self.buffer, i)
end end
end end

View File

26
docs/Core.md Normal file
View File

@ -0,0 +1,26 @@
## Core
Core is what makes helium churn, without devolving in to boring technicals it includes helium's auto ui texture atlassing tech, element creation, scenes, and input subscriptions.
Relevant user facing modules:
### Element
Element is the class for every ui element, in practice it's glue between the code you write and the other parts of helium.
[Find more here](./core/Element.md)
### Input
Input allows to create callbacks for helium internal input events
[Find more here](./core/State-Input-Guide.md)
[and here](./core/Input-events.md)
### Scene
Scenes are just that, a composition of elements you can render, update, switch between etc.
[Find more here](./core/Scenes.md)

View File

@ -0,0 +1,247 @@
## Hooks
Hooks are small single purpose modules for interaction with element lifecycle, they must be called inside the element function.
I prefer to use them like this
```lua
local useState = require('helium.hooks.state')
return function(param, view)
local elementState = useState({foo='bar'})
return function()
end
end
```
### /hooks/callback.lua
Allows you to create a callback for this element that will be accessible on the element object
`useCallback(name:string, callback)`
Usage:
```lua
--Name will set the name of the accessible callback
local useCallback = require('helium.hooks.callback')
local element = helium(function(param, view)
useCallback('fooCallback', function () print('here') end)
return function()
end
end)({}, 20, 20)
element.fooCallback()
```
### /hooks/state.lua
Creates an *attached state object*, this function will return a wrapper around the provided table that will re-render this element if any of the fields is changed
**It's important to use the direct table field like fooState.blah, instead of local x = fooState.blah**
**Avoid nested tables inside of states, changing nested table fields will not make elements render**
```lua
local useState = require('helium.hooks.state')
function(param, view)
local fooState = useState({foo = 'bar',hello = true})
fooState.hello = false
print(fooState.foo)
return function()
end
end
```
### /hooks/context.lua
Creates a *context attached state object*, this will be a table that can be accessed in children elements, if an indexed field updates the children elements will be rerendered like they should be, so you can use this for communication to adjacent elements (in a form), dynamic style, a repository of game state data to be used for rendering etc. You can and should have multiple contexts
**Just like with state it's important to use the direct table field like fooCtx.blah, instead of local x = fooCtx.blah**
**Avoid nested tables inside of contexts, changing nested table fields will not queue elements for rendering**
`context.use(name, baseTable)` Creates a new context with the default values of baseTable
`context.get(name)` Gets an existing context with the name
Usage:
```lua
local context = require('helium.hooks.context')
--Parent element
function(param, view)
local fooCtx = context.use('fooCtx',{foo = 'bar', asd = true})
--Some children element created here
return function()
end
end
--Child element
function(param, view)
local fooCtx = context.get('fooCtx')
--This change will propogate to all fooCtx instances
fooCtx.asd = not fooCtx.asd
print(fooCtx.foo)
return function()
end
end
```
### /hooks/onDestroy.lua
Will create a callback that is called when an element is ended with the :destroy() method
`onDestroy(callback)`
Usage:
```lua
local onDestroy = require('helium.hooks.onDestroy')
function(param, view)
onDestroy(function () print('element ended') end)
return function()
end
end
```
### /hooks/onLoad.lua
Will create a callback that is called when an element is loaded
`onLoad(callback)`
Usage:
```lua
--The call signature is (callback)
local onLoad = require('helium.hooks.onLoad')
function(param, view)
onLoad(function () print('element loaded') end)
return function()
end
end
```
### /hooks/onPosChange.lua
Will create a callback that is called when an element is resized
`onPosChange(callback)`
Usage:
```lua
--The call signature is (callback)
local onPosChange = require('helium.hooks.onPosChange')
function(param, view)
onPosChange(function (xnew, ynew) print('element moved') end)
return function()
end
end
```
### /hooks/onSizeChange.lua
Will create a callback that is called when an element is resized
`onSizeChange(callback)`
Usage:
```lua
local onSizeChange = require('helium.hooks.onSizeChange')
function(param, view)
onSizeChange(function (newW,newH) print('element resized') end)
return function()
end
end
```
### /hooks/onUpdate.lua
Will create a callback that is called when an element is updated
There's not much practical benefit besides seperating update code outside of the rendering function
`onUpdate(callback)`
Usage:
```lua
local onUpdate = require('helium.hooks.onUpdate')
function(param, view)
onUpdate(function () print('element updated') end)
return function()
end
end
```
### /hooks/setMinSize.lua
Sets the minimum size of this element, use this to set height from a font, lines of text, images
Mandatory to set at least something for layouting
`setMinSize(w:number, h:number)`
Usage:
```lua
local setMinSize = require('helium.hooks.setMinSize')
function(param, view)
setMinSize(100, 100)
return function()
end
end
```
### /hooks/setPos.lua
Sets the position of the element, this is relative to the root
`setPos(x:number, y:number)`
Usage:
```lua
local setPos = require('helium.hooks.setPos')
function(param, view)
setPos(100, 100)
return function()
end
end
```
### /hooks/setSize.lua
Sets the size of the element (if it's bigger than the minimum set size)
`setSize(w:number, h:number)`
Usage:
```lua
local setSize = require('helium.hooks.setSize')
local setMinSize = require('helium.hooks.setMinSize')
function(param, view)
setMinSize(10, 10)
setSize(100, 100)
return function()
end
end
```

View File

@ -0,0 +1,55 @@
## Layout
Layouts are modules to help you laying elements out within a viewport, note they can only be used within elements themselves
Current layout schemes are:
### Column
Very basic column layout, it'll place one element after the next in a column
### Row
Very basic row layout, it'll place one element after the next in a row
### Container
Use this to position a single element within a parent container
### Grid
Something akin to CSS grids, can create responsive layouts
Use em inside an element like:
```lua
local layoutScheme = require('helium.layout.column')
local someChildElementFactory
function ()
local someChildElement = someChildElementFactory({}, 20, 20)
return function()
local layout = layoutScheme.new()
someChildElement:draw()
layout:draw()
end
end
```
Each of these layout schemes will return a generic layout object which has a bunch of chainable methods, for setting padding, layout size etc.
Some of the methods follow the size scheme of 0-1 = relative to the container (0-100%) and above = pixels, so that's something to keep in mind
Layouts have some defaults set, like covering the whole width and height of the conteiner by default.
```lua
local layoutScheme = require('helium.layout.column')
local someChildElementFactory
function ()
local someChildElement = someChildElementFactory({}, 20, 20)
return function()
local layout = layoutScheme.new():width(300):height(300)
someChildElement:draw()
layout:draw()
end
end
```

46
docs/Modules-Index.md Normal file
View File

@ -0,0 +1,46 @@
## Modules
Helium is subdivided in to a few 'modules'
### Core
Core includes everything helium *needs* to run, this is the only critical module
Current core files relative to root:
./init.lua
and everything in the ./core folder
With core you can create elements, scenes, subscribe to inputs inside of elements etc.
[Find more here](./Core.md)
### Hooks
Hooks are files/functions for interacting with element lifecycle, requires **core**
Hooks are the files inside
./hooks/
They allow you to create state proxy tables, set size, position, various callbacks on load, update etc.
[Find more here](./Hooks.md)
### Shell
Shell includes higher level abstractions of state hooks and input subscriptions, requires **core** and **hooks**
Shell files are inside
./shell/
They abstract common element setups like buttons, checkboxes, text inputs, sliders etc.
### Layout
Layout includes common layout schemes, requires **core**
Layouts are inside ./layout/
[Find more here](./Layout.md)

View File

79
docs/Shell.md Normal file
View File

@ -0,0 +1,79 @@
## Shell
The shell module is basically abstractions over state and input subscriptions for common elements
In practice the result is that lenghty code like:
```lua
local input = require('helium.core.input')
local x = function()
local elementState = useState({down = false, over = false})
input('clicked', function()
elementState.down = true
return function()
elementState.down = false
end
end)
input('hovered', function()
elementState.over = true
return function()
elementState.over = false
end
end)
return function()
if elementState.down then
love.graphics.setColor(1, 0, 0)
else
love.graphics.setColor(0, 1, 1)
end
love.graphics.print(elementState.over and 'Thanks' or 'Hover over me!')
end
end
```
Gets simplified down to:
```lua
local useButton = require('helium.shell.button')
local x = function()
local buttonState = useButton()
return function()
if buttonState.down then
love.graphics.setColor(1, 0, 0)
else
love.graphics.setColor(0, 1, 1)
end
love.graphics.print(buttonState.over and 'Thanks' or 'Hover over me!')
end
end
```
For a more detailed description of each shell function check out the individual files in /shell/
If you're looking to build a big library of widgets with helium this pattern is certainly something i think you should look in to using, you can build your own set of shell functions, you can use any of the helium hooks, inputs, etc in there just fine.
For an idea of a 'shell' I can imagine creating a style abstraction hook with contexts
```lua
local context = require('helium.hooks.context')
return function()
--This assumes an existing context named style
local ctx = context.get('style')
return {
setPrimaryFont = function() love.graphics.setFont(ctx.primaryFont) end,
setPrimaryColor = function() love.graphics.setColor(ctx.primaryColor) end,
}
end
```
The rest of the functions you can let your imagination run wild, but after creating those you'd
#1 have a common repository of all those values
#2 change those values everywhere at once

View File

@ -78,5 +78,6 @@ The full call signature of input is:
`local sub = input(eventType, callback, startOn, x, y, w, h)` `local sub = input(eventType, callback, startOn, x, y, w, h)`
See the demo repository with this example here: ~~link See the demo repository with this example here: ~~link
See all event types explained here: ~~link See all event types explained here: [Input events](./core/Input-events.md)
There are a few pre-made hooks that abstract away state management, see here: ~~link There are a few pre-made hooks that abstract away state management, see here: [Shell](./Shell.md)
For a more general overview of the whole library: [Module index](./Modules-Index.md)

87
docs/core/Element.md Normal file
View File

@ -0,0 +1,87 @@
## Element
The element is helium's basic building block, in practice it consists of 2 nested functions, the basic pattern looks like this
```lua
element = function()
return function()
end
end
```
So, what's what?
Well this outer part is what i call the loader function, it runs once per created element
```lua
element = function()
end
```
It's where you'll set up variables for later, callbacks and state
```lua
element = function()
local x = 'blah'
end
```
Of course, the variables in this first function become attached to the 'closure'
```lua
element = function()
local x = 'blah'
return function()
--so you can use X in here
print(x)
end
end
```
So now we have a function, but this isn't an element yet, to create an element you need to import helium and call the function with helium
```lua
local helium = require('helium')
local elementFactory = helium(function()
return function()
end
end)
```
This returns a new 'factory', that is a function that will create a new element when called:
```lua
local element = elementFactory(params, width, height, flags)
```
Param is an arbitrary table that you can pass in for the element to use, width and height are the 'minimum' dimensions of the new element and flags is an arbitrary table of flags, currently only used in layout.
Now that we've learned about params, the first function gets passed them and another table:
```lua
element = function(param, view)
return function()
end
end
```
The first parameter is what got passed to the factory, view table looks like : {x, y, w, h} and it contains the size and coordinates of the element.
so:
```lua
local elementFactory = helium(function(param, view)
print(param.x)
return function()
end
end)
elementFactory({x = 10}, 10, 10)
```

View File

@ -1,3 +1,5 @@
## Input
There are a few types of input events, some directly acting like love's own input events: There are a few types of input events, some directly acting like love's own input events:
### SIMPLE EVENTS ### SIMPLE EVENTS
@ -18,3 +20,5 @@ And some complex events that abstract functionality for ui elements
dragged dragged
clicked clicked
hover hover
This is just a list of events, for a [guide check out this](./../State-Input-Guide.md)

53
docs/core/Scenes.md Normal file
View File

@ -0,0 +1,53 @@
## Scenes
Scenes are a collection of elements and their associated input subscriptions, they each *can* contain a seperate caching atlas
### Atlas
The caching atlas is an important part of helium's performance optimizations, in practice it's all magick'd away and you don't really have to worry about it besides the consideration, of balancing memory use and performance, if enabled there will be 2 full screen canvases created for a scene. So to balance them out, gauge for how long the scene will be rendered for, if it's a short temporary scene then there's no point of atlassing, if it's the main HUD or something then perhaps there's a point.
### Using scenes
```lua
local testScene = scene.new(cache: boolean)
```
Will return a scene object, cache true or false will disable or enable the atlases
```lua
testScene:activate()
```
Will set this scene as active, after setting it, you can start creating the elements with factories, they will be bound to the current active scene
```lua
testScene:deactivate()
```
Will deactivate this scene
```lua
testScene:reload()
testScene:unload()
```
Unload will destroy the scene, and clear used memory by the scene
Use it together with reload to recreate the scene from new, you'll need to also re-run the element factories and such
```lua
testScene:draw()
testScene:update(dt)
testScene:resize(newW, newH)
```
Put these in the corresponding love callbacks like draw, update and resized
They'll draw, update and resize the scene, you can control the draw order, or draw multiple scenes at once too, the one set with :activate will be the one recieving input subscriptions though.

8
hooks/setMinSize.lua Normal file
View File

@ -0,0 +1,8 @@
local path = string.sub(..., 1, string.len(...) - string.len(".hooks.setMinSize"))
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)
local currentStack = stack.getContext()
currentStack.element:setMinSize(w, h)
end

View File

@ -23,6 +23,38 @@ local function mapCoordToVal(minCoord, maxCoord, minVal, maxVal, divider, coord)
return mapToValueRange(calcPercent(minCoord, coord, maxCoord), minVal, maxVal, divider) return mapToValueRange(calcPercent(minCoord, coord, maxCoord), minVal, maxVal, divider)
end end
---@class SliderValuesTable
---@field value number @Starting value of the slider
---@field divider number @The divider (rounding) for your slider
---@field min number @The minimum slider number
---@field max number @The maximum slider number
---@class SliderStateTable
---@field value number @Current value of the slider
---@field divisions number @Current divisions of the slider
---@field min number @Min value of the slider
---@field max number @Max value of the slider
---@class HandleStateTable
---@field down boolean @Whether the mouse is pressing the handle
---@field over boolean @Whether the mouse is over the handle
---@field x number @X position of the handle
---@field y number @Y position of the handle
---Sets up a slider for your element
---@param values SliderValuesTable
---@param w number
---@param h number
---@param onChange fun(sliderValue:number)
---@param onFinish fun(sliderValue:number)
---@param onClick fun()
---@param onRelease fun()
---@param onEnter fun()
---@param onExit fun()
---@param x any
---@param y any
---@return SliderStateTable
---@return HandleStateTable
return function(values, w, h, onChange, onFinish, onClick, onRelease, onEnter, onExit, x, y) return function(values, w, h, onChange, onFinish, onClick, onRelease, onEnter, onExit, x, y)
local vertical = h > w local vertical = h > w
local originx, originy = x or 0, y or 0 local originx, originy = x or 0, y or 0
@ -30,7 +62,7 @@ return function(values, w, h, onChange, onFinish, onClick, onRelease, onEnter, o
value = values.value or ((values.max - values.min)/2)+values.min or 0, value = values.value or ((values.max - values.min)/2)+values.min or 0,
divisions = values.divider or 1, divisions = values.divider or 1,
min = values.min or 0, min = values.min or 0,
max = values.max or values.start or 0, max = values.max or values.value or 0,
} }
local handle = state { local handle = state {
x = 0, x = 0,