aboutsummaryrefslogtreecommitdiff
path: root/.config/awesome/quarrel/animation/init.lua
diff options
context:
space:
mode:
Diffstat (limited to '.config/awesome/quarrel/animation/init.lua')
-rw-r--r--.config/awesome/quarrel/animation/init.lua249
1 files changed, 249 insertions, 0 deletions
diff --git a/.config/awesome/quarrel/animation/init.lua b/.config/awesome/quarrel/animation/init.lua
new file mode 100644
index 0000000..c57b01a
--- /dev/null
+++ b/.config/awesome/quarrel/animation/init.lua
@@ -0,0 +1,249 @@
+-------------------------------------------
+-- @author https://github.com/Kasper24
+-- @copyright 2021-2025 Kasper24
+-------------------------------------------
+local GLib = require("lgi").GLib
+local gobject = require "gears.object"
+local gpcall = require "gears.protected_call"
+local gtable = require "gears.table"
+local gtimer = require "gears.timer"
+local subscribable = require "quarrel.animation.subscribable"
+-- local qconsts = require "quarrel.consts"
+local qtween = require "quarrel.animation.tween"
+-- local qbezier = require "quarrel.animation.bezier"
+local ipairs = ipairs
+local table = table
+local pairs = pairs
+
+local animation_manager = {}
+
+-- local easing_bezier = qbezier(0.2, 0, 0, 1)
+-- animation_manager.consts = qconsts.protect(function()
+-- local duration = 0.3
+-- return {
+-- DURATION = duration,
+-- INTRO = duration / 4,
+-- EASING = function(t, b, c, d)
+-- local epsilon = 1000 / d
+-- return c * easing_bezier:solve(t/d, epsilon) + b
+-- end
+-- }
+-- end)
+
+local animation = {}
+
+local function second_to_micro(sec)
+ return sec * 1000000
+end
+
+local function framerate_tomilli(framerate)
+ return 1000 / framerate
+end
+
+local function on_no_running_animations(self, callback)
+ gtimer.start_new(0.1, function()
+ if #self._private.animations <= 0 then
+ callback()
+ return false
+ else
+ local has_non_looped_anim = false
+ for _, animation in ipairs(self._private.animations) do
+ if animation.loop == false then
+ has_non_looped_anim = true
+ end
+ end
+
+ if has_non_looped_anim == false then
+ callback()
+ return false
+ end
+ end
+
+ return true
+ end)
+end
+
+local function animation_loop(self)
+ self._private.source_id = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ framerate_tomilli(self._private.framerate),
+ function()
+ for index, animation in ipairs(self._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 = gpcall(animation.tween.update, animation.tween, 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
+ animation.state = false
+
+ -- Snap to end
+ animation.pos = animation.tween.target
+
+ gpcall(animation.emit_signal, animation, "update", animation.pos)
+ gpcall(animation.fire, animation, animation.pos)
+
+ gpcall(animation.emit_signal, animation, "ended", animation.pos)
+ gpcall(animation.ended.fire, animation, animation.pos)
+
+ table.remove(self._private.animations, index)
+ end
+ -- Animation in process, keep updating
+ else
+ animation.pos = pos
+
+ gpcall(animation.emit_signal, animation, "update", animation.pos)
+ gpcall(animation.fire, animation, animation.pos)
+ end
+ else
+ table.remove(self._private.animations, index)
+ end
+ end
+
+ -- call again the function after cooldown
+ return true
+ end
+ )
+end
+
+function animation:set(args)
+ args = args or {}
+
+ -- Awestoer/Rubbto compatibility
+ -- I'd rather this always be a table, but Awestore/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
+
+ if self.tween == nil or self.reset_on_stop == true then
+ self.tween = qtween.new {
+ initial = initial,
+ subject = subject,
+ target = target,
+ duration = second_to_micro(duration),
+ easing = easing,
+ }
+ end
+
+ if self._private.anim_manager._private.instant and self.override_instant ~= true then
+ self.pos = self.tween.target
+ self:fire(self.pos)
+ self:emit_signal("update", self.pos)
+
+ self.state = false
+ self.ended:fire(self.pos)
+ self:emit_signal("ended", self.pos)
+ return
+ 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
+
+-- Rubato compatibility
+function animation:abort()
+ self.state = false
+end
+
+function animation:stop()
+ self.state = false
+end
+
+function animation:initial()
+ return self._private.initial
+end
+
+function animation_manager:set_instant(value)
+ if value == true and self._private.instant == false then
+ on_no_running_animations(self, function()
+ -- GLib.source_remove(self._private.source_id)
+ self._private.instant = true
+ end)
+ elseif self._private.instant == true then
+ self._private.instant = false
+ -- animation_loop(self)
+ end
+end
+
+function animation_manager:set_framerate(value)
+ self._private.framerate = value
+ -- if self._private.instant == false then
+ on_no_running_animations(self, function()
+ GLib.source_remove(self._private.source_id)
+ animation_loop(self)
+ end)
+ -- end
+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
+ -- Can't have it private for rubato compatibility
+ ret.state = false
+
+ return ret
+end
+
+local instance = gobject {}
+gtable.crush(instance, animation_manager, true)
+
+instance._private = {}
+instance._private.animations = {}
+instance._private.instant = false
+instance._private.framerate = 60
+
+animation_loop(instance)
+
+return instance