From 3bd423243eba2d2b1684d6bd0532dd3698b69cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elm=C4=81rs=20=C4=80boli=C5=86=C5=A1?= Date: Sun, 28 Jun 2020 18:43:28 +0300 Subject: [PATCH] help --- .gitignore | 2 +- README.md | 64 +++++++------- conf.lua | 12 +-- control/layout.lua | 122 +++++++++++++------------- control/size.lua | 13 +-- control/state.lua | 36 ++++---- core/element.lua | 13 ++- core/input.d.ts | 10 +-- core/signals.lua | 142 +++++++++++++++--------------- core/stack.lua | 174 ++++++++++++++++++------------------ dummy.lua | 2 +- hooks/README.md | 34 +++---- init.d.ts | 66 +++++++------- init.lua | 14 ++- loader.lua | 214 ++++++++++++++++++++++----------------------- 15 files changed, 468 insertions(+), 450 deletions(-) diff --git a/.gitignore b/.gitignore index 8a0a61f..7ad8351 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -local_conf.lua +local_conf.lua diff --git a/README.md b/README.md index 8679193..62f5343 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,33 @@ -![alt text](https://i.imgur.com/ZQBQfsa.png "Helium") -# Helium - ## Major features: - ### Custom elements - Write your own elements, and interface with them however you want to, - Whether you need a generalized button that can have many themes and options or something super specific, it can be done - ### Efficient rendering & updating - The elements only update&re-render when state changes - ### Code hotswap - Change and save a file loaded through the helium.loader, and see changes immediately - -## Basic overview: -Helium is practically more like a UI framework than a fully fledged UI library. -The idea is to build custom, build simple and build fast, encapsulate. - -## Demo's / Practical examples -[There's a repository of examples here](https://github.com/qfluxstudio/helium_demos) - -## Getting started: -Load helium with `local helium = require 'helium'` - -The basic structure for an element is: - -```lua -return function(param,state,view) - --Setup zone - return function() - --Rendering zone - end -end -``` - +![alt text](https://i.imgur.com/ZQBQfsa.png "Helium") +# Helium + ## Major features: + ### Custom elements + Write your own elements, and interface with them however you want to, + Whether you need a generalized button that can have many themes and options or something super specific, it can be done + ### Efficient rendering & updating + The elements only update&re-render when state changes + ### Code hotswap + Change and save a file loaded through the helium.loader, and see changes immediately + +## Basic overview: +Helium is practically more like a UI framework than a fully fledged UI library. +The idea is to build custom, build simple and build fast, encapsulate. + +## Demo's / Practical examples +[There's a repository of examples here](https://github.com/qfluxstudio/helium_demos) + +## Getting started: +Load helium with `local helium = require 'helium'` + +The basic structure for an element is: + +```lua +return function(param,state,view) + --Setup zone + return function() + --Rendering zone + end +end +``` + [The documentation outgrew this readme, see the github wiki](https://github.com/qfluxstudio/helium/wiki/) \ No newline at end of file diff --git a/conf.lua b/conf.lua index ad4cf34..8eceffa 100644 --- a/conf.lua +++ b/conf.lua @@ -1,7 +1,7 @@ -return { - HOTSWAP = true, --Turns on hotswap, disable this once you're deploying a project - AUTO_RUN = true, --Replaces the default love.run - DEBUG = true, --Reserved for later - PURE_G = true, --whether to keep _G pure - HARD_ERROR = true, --Whether to display element errors inside or hard cras +return { + HOTSWAP = true, --Turns on hotswap, disable this once you're deploying a project + AUTO_RUN = true, --Replaces the default love.run + DEBUG = true, --Reserved for later + PURE_G = true, --whether to keep _G pure + HARD_ERROR = true, --Whether to display element errors inside or hard cras } \ No newline at end of file diff --git a/control/layout.lua b/control/layout.lua index 19f0253..da7851e 100644 --- a/control/layout.lua +++ b/control/layout.lua @@ -1,61 +1,61 @@ -local path = string.sub(..., 1, string.len(...) - string.len(".core.layout")) - -local layout = {} -layout.__index = layout -local element = require(path..'core.element') - -local function layout_new(type, x, y, w, h) - local ctx = element.getContext() - - --The output will be in pixel numbers regardless of inputs - if x <= 1 or not x then - x = ctx.view.x * (x or 0) - end - - if y <= 1 then - y = ctx.view.y * (y or 0) - end - - if w <= 1 then - w = ctx.view.w * (w or 1) - end - - if h <= 1 then - h = ctx.view.h * (h or 1) - end - - return setmetatable({ - x = x, - y = y, - w = w, - h = h - }, layout) -end - - ---Sets mode for the proceding operations -function layout.mode() - -end - ---Sets padding for the next operations -function layout.pad() - -end - ---Sets margins for the proceding operations -function layout.margin() - -end - -function layout.offset() - -end - -function layout:draw() - -end - -layout(0,0,1,1) - -return layout +local path = string.sub(..., 1, string.len(...) - string.len(".core.layout")) + +local layout = {} +layout.__index = layout +local element = require(path..'core.element') + +local function layout_new(type, x, y, w, h) + local ctx = element.getContext() + + --The output will be in pixel numbers regardless of inputs + if x <= 1 or not x then + x = ctx.view.x * (x or 0) + end + + if y <= 1 then + y = ctx.view.y * (y or 0) + end + + if w <= 1 then + w = ctx.view.w * (w or 1) + end + + if h <= 1 then + h = ctx.view.h * (h or 1) + end + + return setmetatable({ + x = x, + y = y, + w = w, + h = h + }, layout) +end + + +--Sets mode for the proceding operations +function layout.mode() + +end + +--Sets padding for the next operations +function layout.pad() + +end + +--Sets margins for the proceding operations +function layout.margin() + +end + +function layout.offset() + +end + +function layout:draw() + +end + +layout(0,0,1,1) + +return layout diff --git a/control/size.lua b/control/size.lua index 9bf4b96..ca10138 100644 --- a/control/size.lua +++ b/control/size.lua @@ -1,7 +1,8 @@ -local path = string.sub(..., 1, string.len(...) - string.len(".control.size")) -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 path = string.sub(..., 1, string.len(...) - string.len(".control.size")) +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:setCalculatedSize(w, h) end \ No newline at end of file diff --git a/control/state.lua b/control/state.lua index c819a67..25e5426 100644 --- a/control/state.lua +++ b/control/state.lua @@ -1,19 +1,19 @@ -local path = string.sub(..., 1, string.len(...) - string.len(".control.state")) -local context = require(path.. ".core.context") - -return function (base) - local base = base or {} - local fakeBase = {} - local activeContext = context.getContext() - return setmetatable({},{ - __index = function(t, index) - return fakeBase[index] or base[index] - end, - __newindex = function(t, index, val) - if fakeBase[index] ~= val then - fakeBase[index] = val - activeContext:bubbleUpdate() - end - end - }) +local path = string.sub(..., 1, string.len(...) - string.len(".control.state")) +local context = require(path.. ".core.stack") + +return function (base) + base = base or {} + local fakeBase = {} + local activeContext = context.getContext() + return setmetatable({},{ + __index = function(t, index) + return fakeBase[index] or base[index] + end, + __newindex = function(t, index, val) + if fakeBase[index] ~= val then + fakeBase[index] = val + activeContext:bubbleUpdate() + end + end + }) end \ No newline at end of file diff --git a/core/element.lua b/core/element.lua index 0739329..19583d8 100644 --- a/core/element.lua +++ b/core/element.lua @@ -87,14 +87,23 @@ function element:new(param) self.context = context.new(self) end +function element:setCalculatedSize(w, h) + self.view.minW = w or self.view.minW + self.view.minH = h or self.view.minH + self.view.w = math.max(self.view.minW, self.view.w) + self.view.h = math.max(self.view.minH, self.view.h) +end + function element:updateInputCtx() self.context.inputContext:update() if self.settings.canvasW then + --If canvas too small make a bigger one if self.settings.canvasW < self.view.w or self.settings.canvasH < self.view.h then self.settings.canvasW = self.view.w*1.25 self.settings.canvasH = self.view.h*1.25 self.canvas = love.graphics.newCanvas(self.view.w*1.25, self.view.h*1.25) + --If canvas too big make a smaller one elseif self.settings.canvasW > self.view.w*1.50 or self.settings.canvasH > self.view.h*1.50 then self.settings.canvasW = self.view.w*1.25 self.settings.canvasH = self.view.h*1.25 @@ -246,10 +255,12 @@ local insert = table.insert --Acts as the entrypoint for beginning rendering ---@param x number ---@param y number -function element:draw(x, y) +function element:draw(x, y, w, h) if not self.view.lock then if x then self.view.x = x end if y then self.view.y = y end + if w then self.view.w = self.view.minW<=w and w or self.view.minW end + if h then self.view.h = self.view.minH<=h and h or self.view.minH end end if self.settings.firstDraw then diff --git a/core/input.d.ts b/core/input.d.ts index ce197d9..081eb1d 100644 --- a/core/input.d.ts +++ b/core/input.d.ts @@ -1,6 +1,6 @@ -interface Subscription{ - on():void; - off():void; -} - +interface Subscription{ + on():void; + off():void; +} + export default function input(it:string,cb:(x?:number,y?:number)=>void,doff?:boolean,x?:number,y?:number,w?:number,h?:number): Subscription; \ No newline at end of file diff --git a/core/signals.lua b/core/signals.lua index fb10bf4..26b6934 100644 --- a/core/signals.lua +++ b/core/signals.lua @@ -1,72 +1,72 @@ ---Internal event/zone/perf-log system -local signals = {} -signals.__index = signals - -function signals.newController() - return setmetatable({ - stack = {}, - - eventSubs = {}, - zoneSubs = {}, - - startTime = 0, - totalTime = 0 - }, signals) -end - -function signals:push(name) - self.stack[#self.stack+1] = {name = name} - - self.startTime = love.timer.getTime() - - if self.zoneSubs[name] then - for i, e in ipairs(self.zoneSubs[name]) do - if e.on and e.func() then - - end - end - end -end - -function signals:pop() - local name = self.stack[#self.stack].name - - if self.zoneSubs[name] then - for i, e in ipairs(self.zoneSubs[name]) do - if not e.on and e.func() then - - end - end - end - - self.totalTime = love.timer.getTime() - self.startTime - self.stack[#self.stack] = nil -end - -function signals:emitEvent(name, content) - if self.eventSubs[name] then - for i,e in ipairs(self.eventSubs[name]) do - e.func(content) - end - end -end - -function signals:onEvent(func, event) - if not self.eventSubs[event] then - self.eventSubs[event] = {} - end - self.eventSubs[event][#self.eventSubs[event]+1] = {func = func} - -end - ---on - true when new zone is pushed --- false when zone is popped -function signals:onSignal(func, name, on) - if not self.zoneSubs[name] then - self.zoneSubs[name] = {} - end - self.zoneSubs[name][#self.zoneSubs[name]+1] = {func = func, on = on} - -end - +--Internal event/zone/perf-log system +local signals = {} +signals.__index = signals + +function signals.newController() + return setmetatable({ + stack = {}, + + eventSubs = {}, + zoneSubs = {}, + + startTime = 0, + totalTime = 0 + }, signals) +end + +function signals:push(name) + self.stack[#self.stack+1] = {name = name} + + self.startTime = love.timer.getTime() + + if self.zoneSubs[name] then + for i, e in ipairs(self.zoneSubs[name]) do + if e.on and e.func() then + + end + end + end +end + +function signals:pop() + local name = self.stack[#self.stack].name + + if self.zoneSubs[name] then + for i, e in ipairs(self.zoneSubs[name]) do + if not e.on and e.func() then + + end + end + end + + self.totalTime = love.timer.getTime() - self.startTime + self.stack[#self.stack] = nil +end + +function signals:emitEvent(name, content) + if self.eventSubs[name] then + for i,e in ipairs(self.eventSubs[name]) do + e.func(content) + end + end +end + +function signals:onEvent(func, event) + if not self.eventSubs[event] then + self.eventSubs[event] = {} + end + self.eventSubs[event][#self.eventSubs[event]+1] = {func = func} + +end + +--on - true when new zone is pushed +-- false when zone is popped +function signals:onSignal(func, name, on) + if not self.zoneSubs[name] then + self.zoneSubs[name] = {} + end + self.zoneSubs[name][#self.zoneSubs[name]+1] = {func = func, on = on} + +end + return signals \ No newline at end of file diff --git a/core/stack.lua b/core/stack.lua index ebc20d2..8a952fc 100644 --- a/core/stack.lua +++ b/core/stack.lua @@ -1,88 +1,88 @@ ---Builds the element stack basically - -local path = string.sub(..., 1, string.len(...) - string.len(".core.stack")) -local helium = require(path .. ".dummy") - ----@class context -local context = {} -context.__index = context - -local activeContext - ----@param elem element -function context.new(elem) - local ctx = setmetatable({ - view = elem.view, - element = elem, - childrenContexts = {}, - inputContext = helium.input.newContext(elem) - }, context) - - return ctx -end - -function context:bubbleUpdate() - self.element.settings.pendingUpdate = true - self.element.settings.needsRendering = true - - if self.parentCtx and self.parentCtx~=self then - self.parentCtx:bubbleUpdate() - end -end - -function context:set() - if activeContext then - if not self.parentCtx and activeContext~=self then - self.parentCtx = activeContext - activeContext.childrenContexts[#activeContext.childrenContexts] = self - end - - self.absX = self.parentCtx.absX + self.view.x - self.absY = self.parentCtx.absY + self.view.y - - activeContext = self - else - self.absX = self.view.x - self.absY = self.view.y - - activeContext = self - end - - self.inputContext:set() -end - -function context:unset() - self.inputContext:unset() - self.inputContext:afterLoad() - - if self.parentCtx then - activeContext = self.parentCtx - else - activeContext = nil - end -end - -function context:unsuspend() - self.inputContext:unsuspend() -end - -function context:destroy() - self.elem:undraw() - for i=1,#self.childrenContexts do - self.childrenContexts[i]:destroy() - end -end - -function context:suspend() - self.inputContext:set() - self.inputContext:suspend() - self.inputContext:unset() -end - ---Function meant for external context capture -function context.getContext() - return activeContext -end - - +--Builds the element stack basically + +local path = string.sub(..., 1, string.len(...) - string.len(".core.stack")) +local helium = require(path .. ".dummy") + +---@class context +local context = {} +context.__index = context + +local activeContext + +---@param elem element +function context.new(elem) + local ctx = setmetatable({ + view = elem.view, + element = elem, + childrenContexts = {}, + inputContext = helium.input.newContext(elem) + }, context) + + return ctx +end + +function context:bubbleUpdate() + self.element.settings.pendingUpdate = true + self.element.settings.needsRendering = true + + if self.parentCtx and self.parentCtx~=self then + self.parentCtx:bubbleUpdate() + end +end + +function context:set() + if activeContext then + if not self.parentCtx and activeContext~=self then + self.parentCtx = activeContext + activeContext.childrenContexts[#activeContext.childrenContexts] = self + end + + self.absX = self.parentCtx.absX + self.view.x + self.absY = self.parentCtx.absY + self.view.y + + activeContext = self + else + self.absX = self.view.x + self.absY = self.view.y + + activeContext = self + end + + self.inputContext:set() +end + +function context:unset() + self.inputContext:unset() + self.inputContext:afterLoad() + + if self.parentCtx then + activeContext = self.parentCtx + else + activeContext = nil + end +end + +function context:unsuspend() + self.inputContext:unsuspend() +end + +function context:destroy() + self.elem:undraw() + for i=1,#self.childrenContexts do + self.childrenContexts[i]:destroy() + end +end + +function context:suspend() + self.inputContext:set() + self.inputContext:suspend() + self.inputContext:unset() +end + +--Function meant for external context capture +function context.getContext() + return activeContext +end + + return context \ No newline at end of file diff --git a/dummy.lua b/dummy.lua index 72fb6e4..13e4e72 100644 --- a/dummy.lua +++ b/dummy.lua @@ -1,2 +1,2 @@ ---thicc +--thicc return {} \ No newline at end of file diff --git a/hooks/README.md b/hooks/README.md index 52c2a95..ee71b71 100644 --- a/hooks/README.md +++ b/hooks/README.md @@ -1,18 +1,18 @@ -Hooks are additional functions to utilize the element lifecycle more granularly -e.g. - -```lua -local onDestroyHook = require("helium/hooks/onDestroy") - -return function (param) - - onDestroyHook(function() - doSomething() - end) - - return function() - love.graphics.print("Help") - end -end - +Hooks are additional functions to utilize the element lifecycle more granularly +e.g. + +```lua +local onDestroyHook = require("helium/hooks/onDestroy") + +return function (param) + + onDestroyHook(function() + doSomething() + end) + + return function() + love.graphics.print("Help") + end +end + ``` \ No newline at end of file diff --git a/init.d.ts b/init.d.ts index f3c5c03..0d3646a 100644 --- a/init.d.ts +++ b/init.d.ts @@ -1,33 +1,33 @@ - export interface parameters{ - [index: string]: any; - [index: number]: any; -} - -export interface view{ - x: number; - y: number; - w: number; - h: number; - lock?: boolean; - onChange?(): null; -} - -export interface state{ - [index: string]: any; - [index: number]: any; -} - -interface HeliumElement{ - view: view; - state: state; - parameters: parameters; - draw(this,x:number,y:number): null; - undraw(this): null; -} - -declare function HeliumLoader(filepath:string):(params:parameters, w:number, h:number)=>HeliumElement; - -export module helium{ - export let input: typeof import("./core/input") ; -} -export function helium(chunk:(params:T,state:state,view:view)=>()=>void):(params:T, w:number, h:number)=>HeliumElement; + export interface parameters{ + [index: string]: any; + [index: number]: any; +} + +export interface view{ + x: number; + y: number; + w: number; + h: number; + lock?: boolean; + onChange?(): null; +} + +export interface state{ + [index: string]: any; + [index: number]: any; +} + +interface HeliumElement{ + view: view; + state: state; + parameters: parameters; + draw(this,x:number,y:number): null; + undraw(this): null; +} + +declare function HeliumLoader(filepath:string):(params:parameters, w:number, h:number)=>HeliumElement; + +export module helium{ + export let input: typeof import("./core/input") ; +} +export function helium(chunk:(params:T,state:state,view:view)=>()=>void):(params:T, w:number, h:number)=>HeliumElement; diff --git a/init.lua b/init.lua index 17ddf36..4172d59 100644 --- a/init.lua +++ b/init.lua @@ -12,10 +12,16 @@ helium.input = require(path..".core.input") helium.loader = require(path..".loader") helium.elementBuffer = {} helium.__index = helium -setmetatable(helium, {__call = function(s,chunk) - return function(param,w,h) - return helium.element(chunk,nil,w,h,param) - end + +setmetatable(helium, {__call = function(s, chunk) + return { + __call = function(s, param, w, h) + return helium.element(chunk, nil, w, h, param) + end, + draw = function (param, x, y, w, h) + return helium.element.immediate(param, chunk, x, y, w, h) + end + } end}) function helium.render() diff --git a/loader.lua b/loader.lua index 5cde0f3..75d0b26 100644 --- a/loader.lua +++ b/loader.lua @@ -1,108 +1,108 @@ - -local path = string.sub(..., 1, string.len(...) - string.len(".loader")) -local helium = require(path..'.dummy') -local elements = {} -local debugLoader = {} ---Return level: 1--string; 2--chunk; 3--return value; default: element factory -local function loader(path) - local succ = true - - --File string - local fileContents, err = love.filesystem.read(path) - - if fileContents==nil then - print('Error loading ',path,':',tostring(err),', will continue watching!') - succ = false - end - - local t, lastLoaded - if succ then - t = love.filesystem.getInfo(path) - lastLoaded = t['modtime'] - end - - --Chunk - local status, err - if succ then - status, err = pcall(loadstring,fileContents) - end - - if status==false or status==nil then - print('Error compiling ',path,':',tostring(err),', will continue watching!') - succ = false - end - - --Return values - local ret - if succ then - succ, ret = pcall(err,path) - if not succ then - print('Error calling ',path,':',tostring(ret)) - end - end - - return fileContents, err, ret, lastLoaded -end - -debugLoader.loader = function(path,returnLevel) - local level = returnLevel or 6 - if elements[path] then - return elements[path][level] - end - - local setfuncs = {} - - local fileContents, func, ret, lastLoaded = loader(path) - local reloader = function(setFunc) - setfuncs[#setfuncs+1] = setFunc - end - - local factory = function(param,w,h) - return helium.element(ret, reloader, w, h, param) - end - - elements[path] = {fileContents, func, ret, path, lastLoaded, factory, setfuncs = setfuncs} - return elements[path][level] -end - -local counter = 0 -function debugLoader.update(dt) - counter = counter+dt - if counter>2 then - for ind, elem in pairs(elements) do - --Get the current last save time - local t = love.filesystem.getInfo(elem[4]) - local ll = t['modtime'] - if ll ~= elem[5] then - --If last save time differs then start reload sequence - local _, _, ret, lastLoaded = loader(elem[4]) - - - local setfuncs = {} - - local reloader = function(setFunc) - setfuncs[#setfuncs+1] = setFunc - end - - local factory = function() - return helium.element(ret, reloader) - end - - elem[5] = lastLoaded - - elem[6] = factory - - for i, func in ipairs(elem.setfuncs) do - func(ret) - end - end - end - counter = 0 - end -end - -if helium.conf.PURE_G then - HeliumLoader = debugLoader.loader -end - + +local path = string.sub(..., 1, string.len(...) - string.len(".loader")) +local helium = require(path..'.dummy') +local elements = {} +local debugLoader = {} +--Return level: 1--string; 2--chunk; 3--return value; default: element factory +local function loader(path) + local succ = true + + --File string + local fileContents, err = love.filesystem.read(path) + + if fileContents==nil then + print('Error loading ',path,':',tostring(err),', will continue watching!') + succ = false + end + + local t, lastLoaded + if succ then + t = love.filesystem.getInfo(path) + lastLoaded = t['modtime'] + end + + --Chunk + local status, err + if succ then + status, err = pcall(loadstring,fileContents) + end + + if status==false or status==nil then + print('Error compiling ',path,':',tostring(err),', will continue watching!') + succ = false + end + + --Return values + local ret + if succ then + succ, ret = pcall(err,path) + if not succ then + print('Error calling ',path,':',tostring(ret)) + end + end + + return fileContents, err, ret, lastLoaded +end + +debugLoader.loader = function(path,returnLevel) + local level = returnLevel or 6 + if elements[path] then + return elements[path][level] + end + + local setfuncs = {} + + local fileContents, func, ret, lastLoaded = loader(path) + local reloader = function(setFunc) + setfuncs[#setfuncs+1] = setFunc + end + + local factory = function(param,w,h) + return helium.element(ret, reloader, w, h, param) + end + + elements[path] = {fileContents, func, ret, path, lastLoaded, factory, setfuncs = setfuncs} + return elements[path][level] +end + +local counter = 0 +function debugLoader.update(dt) + counter = counter+dt + if counter>2 then + for ind, elem in pairs(elements) do + --Get the current last save time + local t = love.filesystem.getInfo(elem[4]) + local ll = t['modtime'] + if ll ~= elem[5] then + --If last save time differs then start reload sequence + local _, _, ret, lastLoaded = loader(elem[4]) + + + local setfuncs = {} + + local reloader = function(setFunc) + setfuncs[#setfuncs+1] = setFunc + end + + local factory = function() + return helium.element(ret, reloader) + end + + elem[5] = lastLoaded + + elem[6] = factory + + for i, func in ipairs(elem.setfuncs) do + func(ret) + end + end + end + counter = 0 + end +end + +if helium.conf.PURE_G then + HeliumLoader = debugLoader.loader +end + return debugLoader \ No newline at end of file