Files
Basalt2/docs/references/plugins/animation.md
2025-02-16 14:12:49 +00:00

9.4 KiB

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 = easingsself.easing 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) VisualElementBaseDispatchEvent = element.dispatchEvent 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 }