- Created Plugin loading system
- Added lazy loading system for elements (optional feature) - Improved rendering performance - Added ID system which is separated from Eement Names - Added Focussystem for container - Improved container performance by only rendering and handling events from visible childrens instead of all - Added label and input - Added animation and xml
This commit is contained in:
327
src/plugins/animation.lua
Normal file
327
src/plugins/animation.lua
Normal file
@@ -0,0 +1,327 @@
|
||||
local Animation = {}
|
||||
Animation.__index = Animation
|
||||
|
||||
local registeredAnimations = {}
|
||||
|
||||
function Animation.registerAnimation(name, handlers)
|
||||
registeredAnimations[name] = handlers
|
||||
|
||||
Animation[name] = function(self, ...)
|
||||
local args = {...}
|
||||
local easing = "linear"
|
||||
if(type(args[#args]) == "string") then
|
||||
easing = table.remove(args, #args)
|
||||
end
|
||||
local duration = table.remove(args, #args)
|
||||
return self:addAnimation(name, args, duration, easing)
|
||||
end
|
||||
end
|
||||
|
||||
local easings = {
|
||||
linear = function(progress)
|
||||
return progress
|
||||
end,
|
||||
|
||||
easeInQuad = function(progress)
|
||||
return progress * progress
|
||||
end,
|
||||
|
||||
easeOutQuad = function(progress)
|
||||
return 1 - (1 - progress) * (1 - progress)
|
||||
end,
|
||||
|
||||
easeInOutQuad = function(progress)
|
||||
if progress < 0.5 then
|
||||
return 2 * progress * progress
|
||||
end
|
||||
return 1 - (-2 * progress + 2)^2 / 2
|
||||
end
|
||||
}
|
||||
|
||||
function Animation.registerEasing(name, func)
|
||||
easings[name] = func
|
||||
end
|
||||
|
||||
local AnimationInstance = {}
|
||||
AnimationInstance.__index = AnimationInstance
|
||||
|
||||
function AnimationInstance.new(element, animType, args, duration, easing)
|
||||
local self = setmetatable({}, AnimationInstance)
|
||||
self.element = element
|
||||
self.type = animType
|
||||
self.args = args
|
||||
self.duration = duration
|
||||
self.startTime = 0
|
||||
self.isPaused = false
|
||||
self.handlers = registeredAnimations[animType]
|
||||
self.easing = easing
|
||||
return self
|
||||
end
|
||||
|
||||
function AnimationInstance:start()
|
||||
self.startTime = os.epoch("local") / 1000
|
||||
if self.handlers.start then
|
||||
self.handlers.start(self)
|
||||
end
|
||||
end
|
||||
|
||||
function AnimationInstance:update(elapsed)
|
||||
local rawProgress = math.min(1, elapsed / self.duration)
|
||||
local progress = easings[self.easing](rawProgress)
|
||||
return self.handlers.update(self, progress)
|
||||
end
|
||||
|
||||
function AnimationInstance:complete()
|
||||
if self.handlers.complete then
|
||||
self.handlers.complete(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Animation.new(element)
|
||||
local self = {}
|
||||
self.element = element
|
||||
self.sequences = {{}}
|
||||
self.sequenceCallbacks = {}
|
||||
self.currentSequence = 1
|
||||
self.timer = nil
|
||||
setmetatable(self, Animation)
|
||||
return self
|
||||
end
|
||||
|
||||
function Animation:sequence()
|
||||
table.insert(self.sequences, {})
|
||||
self.currentSequence = #self.sequences
|
||||
self.sequenceCallbacks[self.currentSequence] = {
|
||||
start = nil,
|
||||
update = nil,
|
||||
complete = nil
|
||||
}
|
||||
return self
|
||||
end
|
||||
|
||||
function Animation:onStart(callback)
|
||||
if not self.sequenceCallbacks[self.currentSequence] then
|
||||
self.sequenceCallbacks[self.currentSequence] = {}
|
||||
end
|
||||
self.sequenceCallbacks[self.currentSequence].start = callback
|
||||
return self
|
||||
end
|
||||
|
||||
function Animation:onUpdate(callback)
|
||||
if not self.sequenceCallbacks[self.currentSequence] then
|
||||
self.sequenceCallbacks[self.currentSequence] = {}
|
||||
end
|
||||
self.sequenceCallbacks[self.currentSequence].update = callback
|
||||
return self
|
||||
end
|
||||
|
||||
function Animation:onComplete(callback)
|
||||
if not self.sequenceCallbacks[self.currentSequence] then
|
||||
self.sequenceCallbacks[self.currentSequence] = {}
|
||||
end
|
||||
self.sequenceCallbacks[self.currentSequence].complete = callback
|
||||
return self
|
||||
end
|
||||
|
||||
function Animation:addAnimation(type, args, duration, easing)
|
||||
local anim = AnimationInstance.new(self.element, type, args, duration, easing)
|
||||
table.insert(self.sequences[self.currentSequence], anim)
|
||||
return self
|
||||
end
|
||||
|
||||
function Animation:start()
|
||||
|
||||
self.currentSequence = 1
|
||||
if(self.sequenceCallbacks[self.currentSequence])then
|
||||
if(self.sequenceCallbacks[self.currentSequence].start) then
|
||||
self.sequenceCallbacks[self.currentSequence].start(self.element)
|
||||
end
|
||||
end
|
||||
if #self.sequences[self.currentSequence] > 0 then
|
||||
self.timer = os.startTimer(0.05)
|
||||
for _, anim in ipairs(self.sequences[self.currentSequence]) do
|
||||
anim:start()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Animation:event(event, timerId)
|
||||
if event == "timer" and timerId == self.timer then
|
||||
local currentTime = os.epoch("local") / 1000
|
||||
local sequenceFinished = true
|
||||
local remaining = {}
|
||||
local callbacks = self.sequenceCallbacks[self.currentSequence]
|
||||
|
||||
for _, anim in ipairs(self.sequences[self.currentSequence]) do
|
||||
local elapsed = currentTime - anim.startTime
|
||||
local progress = elapsed / anim.duration
|
||||
local finished = anim:update(elapsed)
|
||||
|
||||
if callbacks and callbacks.update then
|
||||
callbacks.update(self.element, progress)
|
||||
end
|
||||
|
||||
if not finished then
|
||||
table.insert(remaining, anim)
|
||||
sequenceFinished = false
|
||||
else
|
||||
anim:complete()
|
||||
end
|
||||
end
|
||||
|
||||
if sequenceFinished then
|
||||
if callbacks and callbacks.complete then
|
||||
callbacks.complete(self.element)
|
||||
end
|
||||
|
||||
if self.currentSequence < #self.sequences then
|
||||
self.currentSequence = self.currentSequence + 1
|
||||
remaining = {}
|
||||
|
||||
local nextCallbacks = self.sequenceCallbacks[self.currentSequence]
|
||||
if nextCallbacks and nextCallbacks.start then
|
||||
nextCallbacks.start(self.element)
|
||||
end
|
||||
|
||||
for _, anim in ipairs(self.sequences[self.currentSequence]) do
|
||||
anim:start()
|
||||
table.insert(remaining, anim)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #remaining > 0 then
|
||||
self.timer = os.startTimer(0.05)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Animation.registerAnimation("move", {
|
||||
start = function(anim)
|
||||
anim.startX = anim.element.get("x")
|
||||
anim.startY = anim.element.get("y")
|
||||
end,
|
||||
|
||||
update = function(anim, progress)
|
||||
local x = anim.startX + (anim.args[1] - anim.startX) * progress
|
||||
local y = anim.startY + (anim.args[2] - anim.startY) * progress
|
||||
anim.element.set("x", math.floor(x))
|
||||
anim.element.set("y", math.floor(y))
|
||||
return progress >= 1
|
||||
end,
|
||||
|
||||
complete = function(anim)
|
||||
anim.element.set("x", anim.args[1])
|
||||
anim.element.set("y", anim.args[2])
|
||||
end
|
||||
})
|
||||
|
||||
Animation.registerAnimation("morphText", {
|
||||
start = function(anim)
|
||||
local startText = anim.element.get(anim.args[1])
|
||||
local targetText = anim.args[2]
|
||||
local maxLength = math.max(#startText, #targetText)
|
||||
local startSpace = string.rep(" ", math.floor(maxLength - #startText)/2)
|
||||
anim.startText = startSpace .. startText .. startSpace
|
||||
anim.targetText = targetText .. string.rep(" ", maxLength - #targetText)
|
||||
anim.length = maxLength
|
||||
end,
|
||||
|
||||
update = function(anim, progress)
|
||||
local currentText = ""
|
||||
|
||||
for i = 1, anim.length do
|
||||
local startChar = anim.startText:sub(i,i)
|
||||
local targetChar = anim.targetText:sub(i,i)
|
||||
|
||||
if progress < 0.5 then
|
||||
currentText = currentText .. (math.random() > progress*2 and startChar or " ")
|
||||
else
|
||||
currentText = currentText .. (math.random() > (progress-0.5)*2 and " " or targetChar)
|
||||
end
|
||||
end
|
||||
|
||||
anim.element.set(anim.args[1], currentText)
|
||||
return progress >= 1
|
||||
end,
|
||||
|
||||
complete = function(anim)
|
||||
anim.element.set(anim.args[1], anim.targetText:gsub("%s+$", "")) -- Entferne trailing spaces
|
||||
end
|
||||
})
|
||||
|
||||
Animation.registerAnimation("typewrite", {
|
||||
start = function(anim)
|
||||
anim.targetText = anim.args[2]
|
||||
anim.element.set(anim.args[1], "")
|
||||
end,
|
||||
|
||||
update = function(anim, progress)
|
||||
local length = math.floor(#anim.targetText * progress)
|
||||
anim.element.set(anim.args[1], anim.targetText:sub(1, length))
|
||||
return progress >= 1
|
||||
end
|
||||
})
|
||||
|
||||
Animation.registerAnimation("fadeText", {
|
||||
start = function(anim)
|
||||
anim.chars = {}
|
||||
for i=1, #anim.args[2] do
|
||||
anim.chars[i] = {char = anim.args[2]:sub(i,i), visible = false}
|
||||
end
|
||||
end,
|
||||
|
||||
update = function(anim, progress)
|
||||
local text = ""
|
||||
for i, charData in ipairs(anim.chars) do
|
||||
if math.random() < progress then
|
||||
charData.visible = true
|
||||
end
|
||||
text = text .. (charData.visible and charData.char or " ")
|
||||
end
|
||||
anim.element.set(anim.args[1], text)
|
||||
return progress >= 1
|
||||
end
|
||||
})
|
||||
|
||||
Animation.registerAnimation("scrollText", {
|
||||
start = function(anim)
|
||||
anim.width = anim.element.get("width")
|
||||
anim.targetText = anim.args[2]
|
||||
anim.element.set(anim.args[1], "")
|
||||
end,
|
||||
|
||||
update = function(anim, progress)
|
||||
local offset = math.floor(anim.width * (1-progress))
|
||||
local spaces = string.rep(" ", offset)
|
||||
anim.element.set(anim.args[1], spaces .. anim.targetText)
|
||||
return progress >= 1
|
||||
end
|
||||
})
|
||||
|
||||
local VisualElement = {hooks={}}
|
||||
|
||||
function VisualElement.hooks.dispatchEvent(self, event, ...)
|
||||
if event == "timer" then
|
||||
local animation = self.get("animation")
|
||||
if animation then
|
||||
animation:event(event, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function VisualElement.setup(element)
|
||||
element.defineProperty(element, "animation", {default = nil, type = "table"})
|
||||
element.listenTo(element, "timer")
|
||||
end
|
||||
|
||||
function VisualElement:animate()
|
||||
local animation = Animation.new(self)
|
||||
self.set("animation", animation)
|
||||
return animation
|
||||
end
|
||||
|
||||
return {
|
||||
VisualElement = VisualElement
|
||||
}
|
||||
23
src/plugins/pluginTemplate.lua
Normal file
23
src/plugins/pluginTemplate.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Will temporary exist so that we don't lose track of how the plugin system works
|
||||
|
||||
local VisualElement = {hooks={init={}}}
|
||||
|
||||
-- Called on Class level to define properties and setup before instance is created
|
||||
function VisualElement.setup(element)
|
||||
element.defineProperty(element, "testProp", {default = 5, type = "number"})
|
||||
end
|
||||
|
||||
-- Hooks into existing methods (you can also use init.pre or init.post)
|
||||
function VisualElement.hooks.init(self)
|
||||
--self.basalt.LOGGER.debug("VisualElement initialized")
|
||||
end
|
||||
|
||||
-- Adds a new method to the class
|
||||
function VisualElement:testFunc()
|
||||
--self.basalt.LOGGER.debug("Hello World", self.get("testProp"))
|
||||
end
|
||||
|
||||
return {
|
||||
VisualElement = VisualElement
|
||||
}
|
||||
|
||||
33
src/plugins/reactive.lua
Normal file
33
src/plugins/reactive.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
local function setupReactiveProperty(element, propertyName, expression)
|
||||
|
||||
end
|
||||
|
||||
local function createReactiveFunction(expression, scope)
|
||||
local code = expression:gsub(
|
||||
"(%w+)%s*%?%s*([^:]+)%s*:%s*([^}]+)",
|
||||
"%1 and %2 or %3"
|
||||
)
|
||||
|
||||
return load(string.format([[
|
||||
return function(self)
|
||||
return %s
|
||||
end
|
||||
]], code), "reactive", "t", scope)()
|
||||
end
|
||||
|
||||
local BaseElement = {}
|
||||
|
||||
function BaseElement:setReactiveProperty(propertyName, expression)
|
||||
setupReactiveProperty(self, propertyName, expression)
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:setReactive(propertyName, expression)
|
||||
local reactiveFunc = createReactiveFunction(expression, self)
|
||||
self.set(propertyName, reactiveFunc)
|
||||
return self
|
||||
end
|
||||
|
||||
return {
|
||||
BaseElement = BaseElement
|
||||
}
|
||||
184
src/plugins/xml.lua
Normal file
184
src/plugins/xml.lua
Normal file
@@ -0,0 +1,184 @@
|
||||
local errorManager = require("errorManager")
|
||||
|
||||
local function parseTag(str)
|
||||
local tag = {
|
||||
attributes = {}
|
||||
}
|
||||
tag.name = str:match("<(%w+)")
|
||||
for k,v in str:gmatch('%s(%w+)="([^"]-)"') do
|
||||
tag.attributes[k] = v
|
||||
end
|
||||
return tag
|
||||
end
|
||||
|
||||
local function parseXML(self, xmlString)
|
||||
local stack = {}
|
||||
local root = {children = {}}
|
||||
local current = root
|
||||
local inCDATA = false
|
||||
local cdataContent = ""
|
||||
|
||||
for line in xmlString:gmatch("[^\r\n]+") do
|
||||
line = line:match("^%s*(.-)%s*$")
|
||||
self.basalt.LOGGER.debug("Parsing line: " .. line)
|
||||
|
||||
if line:match("^<!%[CDATA%[") then
|
||||
inCDATA = true
|
||||
cdataContent = ""
|
||||
elseif line:match("%]%]>$") and inCDATA then
|
||||
inCDATA = false
|
||||
current.content = cdataContent
|
||||
elseif inCDATA then
|
||||
cdataContent = cdataContent .. line .. "\n"
|
||||
elseif line:match("^<[^/]") then
|
||||
local tag = parseTag(line)
|
||||
tag.children = {}
|
||||
tag.content = ""
|
||||
table.insert(current.children, tag)
|
||||
|
||||
if not line:match("/>$") then
|
||||
table.insert(stack, current)
|
||||
current = tag
|
||||
end
|
||||
elseif line:match("^</") then
|
||||
current = table.remove(stack)
|
||||
end
|
||||
end
|
||||
return root
|
||||
end
|
||||
|
||||
local function evaluateExpression(expr, scope)
|
||||
if not expr:match("^%${.*}$") then
|
||||
return expr:gsub("%${(.-)}", function(e)
|
||||
local env = setmetatable({}, {__index = function(_, k)
|
||||
return scope and scope[k] or _ENV[k]
|
||||
end})
|
||||
|
||||
local func, err = load("return " .. e, "expression", "t", env)
|
||||
if not func then
|
||||
errorManager.error("Failed to parse expression: " .. err)
|
||||
end
|
||||
return tostring(func())
|
||||
end)
|
||||
end
|
||||
|
||||
expr = expr:match("^%${(.*)}$")
|
||||
local env = setmetatable({}, {__index = function(_, k)
|
||||
return scope and scope[k] or _ENV[k]
|
||||
end})
|
||||
|
||||
local func, err = load("return " .. expr, "expression", "t", env)
|
||||
if not func then
|
||||
errorManager.error("Failed to parse expression: " .. err)
|
||||
end
|
||||
return func()
|
||||
end
|
||||
|
||||
local function convertValue(value, propertyType, scope)
|
||||
if propertyType == "string" and type(value) == "string" then
|
||||
if value:find("${") then
|
||||
return evaluateExpression(value, scope)
|
||||
end
|
||||
end
|
||||
|
||||
if type(value) == "string" and value:match("^%${.*}$") then
|
||||
return evaluateExpression(value, scope)
|
||||
end
|
||||
|
||||
if propertyType == "number" then
|
||||
return tonumber(value)
|
||||
elseif propertyType == "boolean" then
|
||||
return value == "true"
|
||||
elseif propertyType == "color" then
|
||||
return colors[value]
|
||||
elseif propertyType == "table" then
|
||||
local env = setmetatable({}, { __index = _ENV })
|
||||
local func = load("return "..value, nil, "t", env)
|
||||
if func then
|
||||
return func()
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function handleEvent(node, element, scope)
|
||||
for attr, value in pairs(node.attributes) do
|
||||
if attr:match("^on%u") then
|
||||
local eventName = attr:sub(3,3):lower() .. attr:sub(4)
|
||||
if scope[value] then
|
||||
element["on"..eventName:sub(1,1):upper()..eventName:sub(2)](element, scope[value])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, child in ipairs(node.children or {}) do
|
||||
if child.name and child.name:match("^on%u") then
|
||||
local eventName = child.name:sub(3,3):lower() .. child.name:sub(4)
|
||||
|
||||
if child.content then
|
||||
local code = child.content:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
|
||||
local func, err = load(string.format([[
|
||||
return %s
|
||||
]], code), "event", "t", scope)
|
||||
|
||||
if err then
|
||||
errorManager.error("Failed to parse event: " .. err)
|
||||
elseif func then
|
||||
element["on"..eventName:sub(1,1):upper()..eventName:sub(2)](element, func())
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local BaseElement = {}
|
||||
|
||||
function BaseElement:fromXML(node)
|
||||
for attr, value in pairs(node.attributes) do
|
||||
local config = self:getPropertyConfig(attr)
|
||||
if config then
|
||||
local convertedValue = convertValue(value, config.type)
|
||||
self.set(attr, convertedValue)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
local Container = {}
|
||||
|
||||
function Container:loadXML(content, scope)
|
||||
local tree = parseXML(self, content)
|
||||
|
||||
local function createElements(nodes, parent, scope)
|
||||
for _, node in ipairs(nodes.children) do
|
||||
if not node.name:match("^on") then
|
||||
local elementType = node.name:sub(1,1):upper() .. node.name:sub(2)
|
||||
local element = parent["add"..elementType](parent, node.attributes.name)
|
||||
|
||||
for attr, value in pairs(node.attributes) do
|
||||
local config = element:getPropertyConfig(attr)
|
||||
if config then
|
||||
local convertedValue = convertValue(value, config.type, scope)
|
||||
element.set(attr, convertedValue)
|
||||
end
|
||||
end
|
||||
|
||||
handleEvent(node, element, scope)
|
||||
|
||||
if #node.children > 0 then
|
||||
createElements(node, element, scope)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
createElements(tree, self, scope)
|
||||
return self
|
||||
end
|
||||
|
||||
return {
|
||||
BaseElement = BaseElement,
|
||||
Container = Container
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user