diff options
Diffstat (limited to '.config/awesome/quarrel/animation/init.lua')
-rw-r--r-- | .config/awesome/quarrel/animation/init.lua | 249 |
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 |