diff options
Diffstat (limited to '.config/awesome/quarrel/animation')
| -rw-r--r-- | .config/awesome/quarrel/animation/bezier.lua | 89 | ||||
| -rw-r--r-- | .config/awesome/quarrel/animation/init.lua | 249 | ||||
| -rw-r--r-- | .config/awesome/quarrel/animation/subscribable/init.lua | 47 | ||||
| -rw-r--r-- | .config/awesome/quarrel/animation/tween/init.lua | 554 |
4 files changed, 939 insertions, 0 deletions
diff --git a/.config/awesome/quarrel/animation/bezier.lua b/.config/awesome/quarrel/animation/bezier.lua new file mode 100644 index 0000000..a505e48 --- /dev/null +++ b/.config/awesome/quarrel/animation/bezier.lua @@ -0,0 +1,89 @@ +local gtable = require "gears.table" + +-- port of https://github.com/WebKit/WebKit/blob/da934454c84ac2dcbf9fca9e5f4ac2644ef25d72/Source/WebCore/platform/graphics/UnitBezier.h + +local bezier = {} + +function bezier:sample_x(t) + -- `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + return ((self.ax * t + self.bx) * t + self.cx) * t +end + +function bezier:sample_y(t) + return ((self.ay * t + self.by) * t + self.cy) * t +end + +function bezier:sample_derivative_x(t) + return (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx +end + +function bezier:solve_x(x, epsilon) + local x2, d2 + local t2 = x + + -- First try a few iterations of Newton's method -- normally very fast. + for _ = 1, 8 do + x2 = self:sample_x(t2) - x + if math.abs(x2) < epsilon then + return t2 + end + d2 = self:sample_derivative_x(t2) + if math.abs(d2) < 1e-6 then + break + end + t2 = t2 - x2 / d2 + end + + -- Fall back to the bisection method for reliability. + local t0 = 0 + local t1 = 1 + t2 = x + + if t2 < t0 then + return t0 + end + if t2 > t1 then + return t1 + end + + while t0 < t1 do + x2 = self:sample_x(t2) + if math.abs(x2 - x) < epsilon then + return t2 + end + if x > x2 then + t0 = t2 + else + t1 = t2 + end + t2 = (t1 - t0) * 0.5 + t0 + end + + -- Failure. + return t2 +end + +function bezier:solve(x, epsilon) + return self:sample_y(self:solve_x(x, epsilon)) +end + +local function new(x1, y1, x2, y2) + local obj = gtable.crush({}, bezier) + + -- Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). + obj.cx = 3.0 * x1 + obj.bx = 3.0 * (x2 - x1) - obj.cx + obj.ax = 1.0 - obj.cx - obj.bx + + obj.cy = 3.0 * y1 + obj.by = 3.0 * (y2 - y1) - obj.cy + obj.ay = 1.0 - obj.cy - obj.by + + return obj +end + +return setmetatable(bezier, { + __call = function(_, ...) + return new(...) + end, +}) 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 diff --git a/.config/awesome/quarrel/animation/subscribable/init.lua b/.config/awesome/quarrel/animation/subscribable/init.lua new file mode 100644 index 0000000..9542704 --- /dev/null +++ b/.config/awesome/quarrel/animation/subscribable/init.lua @@ -0,0 +1,47 @@ +------------------------------------------- +-- @author https://github.com/Kasper24 +-- @copyright 2021-2025 Kasper24 +-- Adopted from rubato +------------------------------------------- +local gobject = require "gears.object" + +-- Kidna copying awesotre's stores on a surface level for added compatibility +local function subscribable(args) + local ret = gobject {} + local subscribed = {} + + -- Subscrubes a function to the object so that it's called when `fire` is + -- Calls subscribe_callback if it exists as well + function ret:subscribe(func) + local id = tostring(func):gsub("function: ", "") + subscribed[id] = func + + if self.subscribe_callback then + self.subscribe_callback(func) + end + end + + -- Unsubscribes a function and calls unsubscribe_callback if it exists + function ret:unsubscribe(func) + if not func then + subscribed = {} + else + local id = tostring(func):gsub("function: ", "") + subscribed[id] = nil + end + + if self.unsubscribe_callback then + self.unsubscribe_callback(func) + end + end + + function ret:fire(...) + for _, func in pairs(subscribed) do + func(...) + end + end + + return ret +end + +return subscribable diff --git a/.config/awesome/quarrel/animation/tween/init.lua b/.config/awesome/quarrel/animation/tween/init.lua new file mode 100644 index 0000000..5f4ce51 --- /dev/null +++ b/.config/awesome/quarrel/animation/tween/init.lua @@ -0,0 +1,554 @@ +------------------------------------------- +-- @author https://github.com/Kasper24 +-- @copyright 2021-2025 Kasper24 +------------------------------------------- +-- easing +-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits. +-- For all easing functions: +-- t = time == how much time has to pass for the tweening to complete +-- b = begin == starting property value +-- c = change == ending - beginning +-- d = duration == running time. How much time has passed *right now* +-- local Color = require "external.lua-color" +local gobject = require "gears.object" +local gtable = require "gears.table" +local tostring = tostring +local assert = assert +local table = table +local pairs = pairs +local error = error + +local type = type + +local tween = { + _VERSION = "tween 2.1.1", + _DESCRIPTION = "tweening for lua", + _URL = "https://github.com/kikito/tween.lua", + _LICENSE = [[ + MIT LICENSE + Copyright (c) 2014 Enrique GarcĂa Cota, Yuichi Tateno, Emmanuel Oga + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]], +} + +local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin + +-- linear +local function linear(t, b, c, d) + return c * t / d + b +end + +-- quad +local function inQuad(t, b, c, d) + return c * pow(t / d, 2) + b +end +local function outQuad(t, b, c, d) + t = t / d + return -c * t * (t - 2) + b +end +local function inOutQuad(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * pow(t, 2) + b + end + return -c / 2 * ((t - 1) * (t - 3) - 1) + b +end +local function outInQuad(t, b, c, d) + if t < d / 2 then + return outQuad(t * 2, b, c / 2, d) + end + return inQuad((t * 2) - d, b + c / 2, c / 2, d) +end + +-- cubic +local function inCubic(t, b, c, d) + return c * pow(t / d, 3) + b +end +local function outCubic(t, b, c, d) + return c * (pow(t / d - 1, 3) + 1) + b +end +local function inOutCubic(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * t * t * t + b + end + t = t - 2 + return c / 2 * (t * t * t + 2) + b +end +local function outInCubic(t, b, c, d) + if t < d / 2 then + return outCubic(t * 2, b, c / 2, d) + end + return inCubic((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quart +local function inQuart(t, b, c, d) + return c * pow(t / d, 4) + b +end +local function outQuart(t, b, c, d) + return -c * (pow(t / d - 1, 4) - 1) + b +end +local function inOutQuart(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * pow(t, 4) + b + end + return -c / 2 * (pow(t - 2, 4) - 2) + b +end +local function outInQuart(t, b, c, d) + if t < d / 2 then + return outQuart(t * 2, b, c / 2, d) + end + return inQuart((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quint +local function inQuint(t, b, c, d) + return c * pow(t / d, 5) + b +end +local function outQuint(t, b, c, d) + return c * (pow(t / d - 1, 5) + 1) + b +end +local function inOutQuint(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * pow(t, 5) + b + end + return c / 2 * (pow(t - 2, 5) + 2) + b +end +local function outInQuint(t, b, c, d) + if t < d / 2 then + return outQuint(t * 2, b, c / 2, d) + end + return inQuint((t * 2) - d, b + c / 2, c / 2, d) +end + +-- sine +local function inSine(t, b, c, d) + return -c * cos(t / d * (pi / 2)) + c + b +end +local function outSine(t, b, c, d) + return c * sin(t / d * (pi / 2)) + b +end +local function inOutSine(t, b, c, d) + return -c / 2 * (cos(pi * t / d) - 1) + b +end +local function outInSine(t, b, c, d) + if t < d / 2 then + return outSine(t * 2, b, c / 2, d) + end + return inSine((t * 2) - d, b + c / 2, c / 2, d) +end + +-- expo +local function inExpo(t, b, c, d) + if t == 0 then + return b + end + return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 +end +local function outExpo(t, b, c, d) + if t == d then + return b + c + end + return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b +end +local function inOutExpo(t, b, c, d) + if t == 0 then + return b + end + if t == d then + return b + c + end + t = t / d * 2 + if t < 1 then + return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 + end + return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b +end +local function outInExpo(t, b, c, d) + if t < d / 2 then + return outExpo(t * 2, b, c / 2, d) + end + return inExpo((t * 2) - d, b + c / 2, c / 2, d) +end + +-- circ +local function inCirc(t, b, c, d) + return (-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) +end +local function outCirc(t, b, c, d) + return (c * sqrt(1 - pow(t / d - 1, 2)) + b) +end +local function inOutCirc(t, b, c, d) + t = t / d * 2 + if t < 1 then + return -c / 2 * (sqrt(1 - t * t) - 1) + b + end + t = t - 2 + return c / 2 * (sqrt(1 - t * t) + 1) + b +end +local function outInCirc(t, b, c, d) + if t < d / 2 then + return outCirc(t * 2, b, c / 2, d) + end + return inCirc((t * 2) - d, b + c / 2, c / 2, d) +end + +-- elastic +local function calculatePAS(p, a, c, d) + p, a = p or d * 0.3, a or 0 + if a < abs(c) then + return p, c, p / 4 + end -- p, a, s + return p, a, p / (2 * pi) * asin(c / a) -- p,a,s +end +local function inElastic(t, b, c, d, a, p) + local s + if t == 0 then + return b + end + t = t / d + if t == 1 then + return b + c + end + p, a, s = calculatePAS(p, a, c, d) + t = t - 1 + return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b +end +local function outElastic(t, b, c, d, a, p) + local s + if t == 0 then + return b + end + t = t / d + if t == 1 then + return b + c + end + p, a, s = calculatePAS(p, a, c, d) + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b +end +local function inOutElastic(t, b, c, d, a, p) + local s + if t == 0 then + return b + end + t = t / d * 2 + if t == 2 then + return b + c + end + p, a, s = calculatePAS(p, a, c, d) + t = t - 1 + if t < 0 then + return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b + end + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) * 0.5 + c + b +end +local function outInElastic(t, b, c, d, a, p) + if t < d / 2 then + return outElastic(t * 2, b, c / 2, d, a, p) + end + return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p) +end + +-- back +local function inBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d + return c * t * t * ((s + 1) * t - s) + b +end +local function outBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d - 1 + return c * (t * t * ((s + 1) * t + s) + 1) + b +end +local function inOutBack(t, b, c, d, s) + s = (s or 1.70158) * 1.525 + t = t / d * 2 + if t < 1 then + return c / 2 * (t * t * ((s + 1) * t - s)) + b + end + t = t - 2 + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b +end +local function outInBack(t, b, c, d, s) + if t < d / 2 then + return outBack(t * 2, b, c / 2, d, s) + end + return inBack((t * 2) - d, b + c / 2, c / 2, d, s) +end + +-- bounce +local function outBounce(t, b, c, d) + t = t / d + if t < 1 / 2.75 then + return c * (7.5625 * t * t) + b + end + if t < 2 / 2.75 then + t = t - (1.5 / 2.75) + return c * (7.5625 * t * t + 0.75) + b + elseif t < 2.5 / 2.75 then + t = t - (2.25 / 2.75) + return c * (7.5625 * t * t + 0.9375) + b + end + t = t - (2.625 / 2.75) + return c * (7.5625 * t * t + 0.984375) + b +end +local function inBounce(t, b, c, d) + return c - outBounce(d - t, 0, c, d) + b +end +local function inOutBounce(t, b, c, d) + if t < d / 2 then + return inBounce(t * 2, 0, c, d) * 0.5 + b + end + return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b +end +local function outInBounce(t, b, c, d) + if t < d / 2 then + return outBounce(t * 2, b, c / 2, d) + end + return inBounce((t * 2) - d, b + c / 2, c / 2, d) +end + +tween.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, +} + +-- Private interface +local function copyTables(destination, keysTable, valuesTable) + valuesTable = valuesTable or keysTable + local mt = getmetatable(keysTable) + if mt and getmetatable(destination) == nil then + setmetatable(destination, mt) + end + + for k, v in pairs(keysTable) do + if type(v) == "table" then + destination[k] = copyTables({}, v, valuesTable[k]) + else + destination[k] = valuesTable[k] + end + end + return destination +end + +local function checkSubjectAndTargetRecursively(subject, target, path) + path = path or {} + local targetType, newPath + for k, targetValue in pairs(target) do + targetType, newPath = type(targetValue), copyTables({}, path) + table.insert(newPath, tostring(k)) + if targetType == "number" then + assert( + type(subject[k]) == "number", + "Parameter '" .. table.concat(newPath, "/") .. "' is missing from subject or isn't a number" + ) + elseif targetType == "table" then + checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) + else + assert( + targetType == "number", + "Parameter '" .. table.concat(newPath, "/") .. "' must be a number or table of numbers" + ) + end + end +end + +local function checkNewParams(initial, duration, subject, target, easing) + -- assert(type(initial) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + -- assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + assert(type(easing) == "function", "easing must be a function. Was " .. tostring(easing)) + + if subject and target then + local tsubject = type(subject) + assert( + tsubject == "table" or tsubject == "userdata", + "subject must be a table or userdata. Was " .. tostring(subject) + ) + assert(type(target) == "table", "target must be a table. Was " .. tostring(target)) + checkSubjectAndTargetRecursively(subject, target) + end +end + +local function getEasingFunction(easing) + easing = easing or "linear" + if type(easing) == "string" then + local name = easing + easing = tween.easing[name] + if type(easing) ~= "function" then + error("The easing function name '" .. name .. "' is invalid") + end + end + return easing +end + +local function performEasingOnSubject(subject, target, initial, clock, duration, easing) + local t, b, c, d + for k, v in pairs(target) do + if type(v) == "table" then + performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) + else + t, b, c, d = clock, initial[k], v - initial[k], duration + subject[k] = easing(t, b, c, d) + end + end +end + +-- local function performEasingOnColor(initial, target, clock, duration, easing) +-- initial = Color(initial) +-- target = Color(target) +-- +-- local r = easing(clock, initial.r, target.r - initial.r, duration) +-- local g = easing(clock, initial.g, target.g - initial.g, duration) +-- local b = easing(clock, initial.b, target.b - initial.b, duration) +-- local a = easing(clock, initial.a, target.a - initial.a, duration) +-- +-- return tostring(Color { r = r, g = g, b = b, a = a }) +-- end + +local function performEasing(table, initial, target, clock, duration, easing) + if type(target) == "table" then + local t, b, c, d + for k, target in pairs(target) do + if type(target) == "table" then + table[k] = {} + performEasing(table[k], initial[k], target, clock, duration, easing) + -- elseif type(target) == "string" and target:sub(1, 1) == "#" then + -- table[k] = performEasingOnColor(initial[k], target, clock, duration, easing) + else + t, b, c, d = clock, initial[k], target - initial[k], duration + table[k] = easing(t, b, c, d) + end + end + + return table + -- elseif type(target) == "string" and target:sub(1, 1) == "#" then + -- return performEasingOnColor(initial, target, clock, duration, easing) + else + local t, b, c, d = clock, initial, target - initial, duration + return easing(t, b, c, d) + end +end + +-- Public interface +local Tween = {} + +function Tween:set(clock) + assert(type(clock) == "number", "clock must be a positive number or 0") + + if self.subject and self.initial == 0 then + self.initial = copyTables({}, self.target, self.subject) + end + + self.clock = clock + + if self.clock <= 0 then + self.clock = 0 + if self.subject then + copyTables(self.subject, self.initial) + end + elseif self.clock >= self.duration then -- the tween has expired + self.clock = self.duration + + if self.subject then + copyTables(self.subject, self.target) + end + else + if self.subject then + performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) + else + local pos = {} + return performEasing(pos, self.initial, self.target, self.clock, self.duration, self.easing) + end + end + + return self.clock >= self.duration +end + +function Tween:update(dt) + assert(type(dt) == "number", "dt must be a number") + return self:set(self.clock + dt) +end + +function Tween:reset() + return self:set(0) +end + +function tween.new(args) + args = args or {} + + args.initial = args.initial 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.easing = getEasingFunction(args.easing) + checkNewParams(args.initial, args.duration, args.subject, args.target, args.easing) + + local ret = gobject {} + ret.clock = 0 + + gtable.crush(ret, args, true) + gtable.crush(ret, Tween, true) + + return ret +end + +return tween |
