From f85b19c118fa804a354a4a698b11c71d5d7705ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elm=C4=81rs=20=C4=80boli=C5=86=C5=A1?= Date: Tue, 22 Jun 2021 14:12:19 +0300 Subject: [PATCH] Documentation --- README.md | 2 +- core/element.lua | 12 +- core/scene.lua | 5 +- docs/API-reference.md | 0 docs/Core.md | 26 ++++ docs/Hooks.md | 247 ++++++++++++++++++++++++++++++++ docs/Layout.md | 55 +++++++ docs/Modules-Index.md | 46 ++++++ docs/Scenes.md | 0 docs/Shell.md | 79 ++++++++++ docs/State-Input-Guide.md | 7 +- docs/core/Element.md | 87 +++++++++++ docs/{ => core}/Input-events.md | 6 +- docs/core/Scenes.md | 53 +++++++ hooks/setMinSize.lua | 8 ++ shell/slider.lua | 34 ++++- 16 files changed, 653 insertions(+), 14 deletions(-) delete mode 100644 docs/API-reference.md create mode 100644 docs/Core.md create mode 100644 docs/Modules-Index.md delete mode 100644 docs/Scenes.md create mode 100644 docs/Shell.md create mode 100644 docs/core/Element.md rename docs/{ => core}/Input-events.md (75%) create mode 100644 docs/core/Scenes.md create mode 100644 hooks/setMinSize.lua diff --git a/README.md b/README.md index 7dc28c5..dc86c00 100644 --- a/README.md +++ b/README.md @@ -115,4 +115,4 @@ end) [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) -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) diff --git a/core/element.lua b/core/element.lua index 52b356a..0f3e305 100644 --- a/core/element.lua +++ b/core/element.lua @@ -214,15 +214,17 @@ function element:setParam(p) end function element:setSize(w, h) - self.view.w = w or self.view.w - self.view.h = h or self.view.h + local w, h = w or self.view.w, h or self.view.h + + self.view.w = math.max(w, self.view.minW) + self.view.h = math.max(h, self.view.minH) end -function element:setCalculatedSize(w, h) +function element:setMinSize(w, h) self.view.minW = w or self.view.minW self.view.minH = h or self.view.minH - self.view.w = w or self.view.minW - self.view.h = h or self.view.minH + self.view.w = math.max(self.view.w, w, self.view.minW) + self.view.h = math.max(self.view.h, h, self.view.minH) end local dummy = function() end diff --git a/core/scene.lua b/core/scene.lua index 6f79fd7..cd2222c 100644 --- a/core/scene.lua +++ b/core/scene.lua @@ -95,10 +95,9 @@ function scene:drawAtlases(x, y) end ---Updates this scene and it's elements ----@param dt number -function scene:update(dt) +function scene:update() for i = 1, #self.buffer do - if self.buffer[i]:externalUpdate(i) then + if self.buffer[i]:externalUpdate() then table.remove(self.buffer, i) end end diff --git a/docs/API-reference.md b/docs/API-reference.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/Core.md b/docs/Core.md new file mode 100644 index 0000000..d8f8991 --- /dev/null +++ b/docs/Core.md @@ -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) \ No newline at end of file diff --git a/docs/Hooks.md b/docs/Hooks.md index e69de29..2fb5ab2 100644 --- a/docs/Hooks.md +++ b/docs/Hooks.md @@ -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 +``` diff --git a/docs/Layout.md b/docs/Layout.md index e69de29..d8f5dde 100644 --- a/docs/Layout.md +++ b/docs/Layout.md @@ -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 +``` \ No newline at end of file diff --git a/docs/Modules-Index.md b/docs/Modules-Index.md new file mode 100644 index 0000000..ae18e3b --- /dev/null +++ b/docs/Modules-Index.md @@ -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) \ No newline at end of file diff --git a/docs/Scenes.md b/docs/Scenes.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/Shell.md b/docs/Shell.md new file mode 100644 index 0000000..91b6d2d --- /dev/null +++ b/docs/Shell.md @@ -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 \ No newline at end of file diff --git a/docs/State-Input-Guide.md b/docs/State-Input-Guide.md index db22cdc..98eecc0 100644 --- a/docs/State-Input-Guide.md +++ b/docs/State-Input-Guide.md @@ -77,6 +77,7 @@ 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 \ No newline at end of file +See the demo repository with this example 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: [Shell](./Shell.md) +For a more general overview of the whole library: [Module index](./Modules-Index.md) \ No newline at end of file diff --git a/docs/core/Element.md b/docs/core/Element.md new file mode 100644 index 0000000..b695ace --- /dev/null +++ b/docs/core/Element.md @@ -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) +``` diff --git a/docs/Input-events.md b/docs/core/Input-events.md similarity index 75% rename from docs/Input-events.md rename to docs/core/Input-events.md index cbe3df6..f653556 100644 --- a/docs/Input-events.md +++ b/docs/core/Input-events.md @@ -1,3 +1,5 @@ +## Input + There are a few types of input events, some directly acting like love's own input events: ### SIMPLE EVENTS @@ -17,4 +19,6 @@ And some complex events that abstract functionality for ui elements dragged clicked -hover \ No newline at end of file +hover + +This is just a list of events, for a [guide check out this](./../State-Input-Guide.md) \ No newline at end of file diff --git a/docs/core/Scenes.md b/docs/core/Scenes.md new file mode 100644 index 0000000..03b0d8b --- /dev/null +++ b/docs/core/Scenes.md @@ -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. \ No newline at end of file diff --git a/hooks/setMinSize.lua b/hooks/setMinSize.lua new file mode 100644 index 0000000..5735224 --- /dev/null +++ b/hooks/setMinSize.lua @@ -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 \ No newline at end of file diff --git a/shell/slider.lua b/shell/slider.lua index fded8c6..70403ce 100644 --- a/shell/slider.lua +++ b/shell/slider.lua @@ -23,6 +23,38 @@ local function mapCoordToVal(minCoord, maxCoord, minVal, maxVal, divider, coord) return mapToValueRange(calcPercent(minCoord, coord, maxCoord), minVal, maxVal, divider) 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) local vertical = h > w 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, divisions = values.divider or 1, min = values.min or 0, - max = values.max or values.start or 0, + max = values.max or values.value or 0, } local handle = state { x = 0,