226 lines
5.6 KiB
Lua
226 lines
5.6 KiB
Lua
|
-------------------------------------------
|
||
|
-- @author https://github.com/Kasper24
|
||
|
-- @copyright 2021-2022 Kasper24
|
||
|
-------------------------------------------
|
||
|
|
||
|
local GLib = require("lgi").GLib
|
||
|
local gobject = require("gears.object")
|
||
|
local gtable = require("gears.table")
|
||
|
local subscribable = require("lib.animation.subscribable")
|
||
|
local tween = require("lib.animation.tween")
|
||
|
local ipairs = ipairs
|
||
|
local table = table
|
||
|
local pairs = pairs
|
||
|
|
||
|
local animation_manager = {}
|
||
|
animation_manager.easing = {
|
||
|
linear = "linear",
|
||
|
inQuad = "inQuad",
|
||
|
outQuad = "outQuad",
|
||
|
inOutQuad = "inOutQuad",
|
||
|
outInQuad = "outInQuad",
|
||
|
inCubic = "inCubic",
|
||
|
outCubic = "outCubic",
|
||
|
inOutCubic = "inOutCubic",
|
||
|
outInCubic = "outInCubic",
|
||
|
inQuart = "inQuart",
|
||
|
outQuart = "outQuart",
|
||
|
inOutQuart = "inOutQuart",
|
||
|
outInQuart = "outInQuart",
|
||
|
inQuint = "inQuint",
|
||
|
outQuint = "outQuint",
|
||
|
inOutQuint = "inOutQuint",
|
||
|
outInQuint = "outInQuint",
|
||
|
inSine = "inSine",
|
||
|
outSine = "outSine",
|
||
|
inOutSine = "inOutSine",
|
||
|
outInSine = "outInSine",
|
||
|
inExpo = "inExpo",
|
||
|
outExpo = "outExpo",
|
||
|
inOutExpo = "inOutExpo",
|
||
|
outInExpo = "outInExpo",
|
||
|
inCirc = "inCirc",
|
||
|
outCirc = "outCirc",
|
||
|
inOutCirc = "inOutCirc",
|
||
|
outInCirc = "outInCirc",
|
||
|
inElastic = "inElastic",
|
||
|
outElastic = "outElastic",
|
||
|
inOutElastic = "inOutElastic",
|
||
|
outInElastic = "outInElastic",
|
||
|
inBack = "inBack",
|
||
|
outBack = "outBack",
|
||
|
inOutBack = "inOutBack",
|
||
|
outInBack = "outInBack",
|
||
|
inBounce = "inBounce",
|
||
|
outBounce = "outBounce",
|
||
|
inOutBounce = "inOutBounce",
|
||
|
outInBounce = "outInBounce",
|
||
|
}
|
||
|
|
||
|
local animation = {}
|
||
|
|
||
|
local instance = nil
|
||
|
|
||
|
local ANIMATION_FRAME_DELAY = 5
|
||
|
|
||
|
local function second_to_micro(sec)
|
||
|
return sec * 1000000
|
||
|
end
|
||
|
|
||
|
function animation:start(args)
|
||
|
args = args or {}
|
||
|
|
||
|
-- Awestoer/Rubbto compatibility
|
||
|
-- I'd rather this always be a table, but Awestoer/Rubbto
|
||
|
-- except the :set() method to have 1 number value parameter
|
||
|
-- used to set the target
|
||
|
local is_table = type(args) == "table"
|
||
|
local initial = is_table and (args.pos or self.pos) or self.pos
|
||
|
local subject = is_table and (args.subject or self.subject) or self.subject
|
||
|
local target = is_table and (args.target or self.target) or args
|
||
|
local duration = is_table and (args.duration or self.duration) or self.duration
|
||
|
local easing = is_table and (args.easing or self.easing) or self.easing
|
||
|
|
||
|
duration = self._private.anim_manager._private.instant == true and 0.01 or duration
|
||
|
|
||
|
if self.tween == nil or self.reset_on_stop == true then
|
||
|
self.tween = tween.new({
|
||
|
initial = initial,
|
||
|
subject = subject,
|
||
|
target = target,
|
||
|
duration = second_to_micro(duration),
|
||
|
easing = easing,
|
||
|
})
|
||
|
end
|
||
|
|
||
|
if self._private.anim_manager._private.animations[self.index] == nil then
|
||
|
table.insert(self._private.anim_manager._private.animations, self)
|
||
|
end
|
||
|
|
||
|
self.state = true
|
||
|
self.last_elapsed = GLib.get_monotonic_time()
|
||
|
|
||
|
self.started:fire()
|
||
|
self:emit_signal("started")
|
||
|
end
|
||
|
|
||
|
function animation:set(args)
|
||
|
self:start(args)
|
||
|
self:emit_signal("set")
|
||
|
end
|
||
|
|
||
|
function animation:stop()
|
||
|
self.state = false
|
||
|
self:emit_signal("stopped")
|
||
|
end
|
||
|
|
||
|
function animation:abort()
|
||
|
animation:stop()
|
||
|
self:emit_signal("aborted")
|
||
|
end
|
||
|
|
||
|
function animation:initial()
|
||
|
return self._private.initial
|
||
|
end
|
||
|
|
||
|
function animation_manager:set_instant(value)
|
||
|
self._private.instant = value
|
||
|
end
|
||
|
|
||
|
function animation_manager:new(args)
|
||
|
args = args or {}
|
||
|
|
||
|
args.pos = args.pos or 0
|
||
|
args.subject = args.subject or nil
|
||
|
args.target = args.target or nil
|
||
|
args.duration = args.duration or 0
|
||
|
args.easing = args.easing or nil
|
||
|
args.loop = args.loop or false
|
||
|
args.signals = args.signals or {}
|
||
|
args.update = args.update or nil
|
||
|
args.reset_on_stop = args.reset_on_stop == nil and true or args.reset_on_stop
|
||
|
|
||
|
-- Awestoer/Rubbto compatibility
|
||
|
args.subscribed = args.subscribed or nil
|
||
|
local ret = subscribable()
|
||
|
ret.started = subscribable()
|
||
|
ret.ended = subscribable()
|
||
|
if args.subscribed ~= nil then
|
||
|
ret:subscribe(args.subscribed)
|
||
|
end
|
||
|
|
||
|
for sig, sigfun in pairs(args.signals) do
|
||
|
ret:connect_signal(sig, sigfun)
|
||
|
end
|
||
|
if args.update ~= nil then
|
||
|
ret:connect_signal("update", args.update)
|
||
|
end
|
||
|
|
||
|
gtable.crush(ret, args, true)
|
||
|
gtable.crush(ret, animation, true)
|
||
|
|
||
|
ret._private = {}
|
||
|
ret._private.anim_manager = self
|
||
|
ret._private.initial = args.pos
|
||
|
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
local function new()
|
||
|
local ret = gobject({})
|
||
|
gtable.crush(ret, animation_manager, true)
|
||
|
|
||
|
ret._private = {}
|
||
|
ret._private.animations = {}
|
||
|
ret._private.instant = false
|
||
|
|
||
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, ANIMATION_FRAME_DELAY, function()
|
||
|
---@diagnostic disable-next-line: redefined-local
|
||
|
for index, animation in ipairs(ret._private.animations) do
|
||
|
if animation.state == true then
|
||
|
-- compute delta time
|
||
|
local time = GLib.get_monotonic_time()
|
||
|
local delta = time - animation.last_elapsed
|
||
|
animation.last_elapsed = time
|
||
|
|
||
|
-- If pos is true, the animation has ended
|
||
|
local pos = animation.tween:update(delta)
|
||
|
if pos == true then
|
||
|
-- Loop the animation, don't end it.
|
||
|
-- Useful for widgets like the spinning cicle
|
||
|
if animation.loop == true then
|
||
|
animation.tween:reset()
|
||
|
else
|
||
|
-- Snap to end
|
||
|
animation.pos = animation.tween.target
|
||
|
animation:fire(animation.pos)
|
||
|
animation:emit_signal("update", animation.pos)
|
||
|
|
||
|
animation.state = false
|
||
|
animation.ended:fire(pos)
|
||
|
table.remove(ret._private.animations, index)
|
||
|
animation:emit_signal("ended", animation.pos)
|
||
|
end
|
||
|
-- Animation in process, keep updating
|
||
|
else
|
||
|
animation.pos = pos
|
||
|
animation:fire(animation.pos)
|
||
|
animation:emit_signal("update", animation.pos)
|
||
|
end
|
||
|
else
|
||
|
table.remove(ret._private.animations, index)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- call again the function after cooldown
|
||
|
return true
|
||
|
end)
|
||
|
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
if not instance then
|
||
|
instance = new()
|
||
|
end
|
||
|
return instance
|