mirror of
https://codeberg.org/Duvelcorp/MetaHunt.git
synced 2026-09-22 07:36:58 +00:00
566f3f4925
Animations (all optional, default on, per-effect toggles): - New Options -> Animations tab with master switch + per-effect checkboxes - Embed PizzaSauce animation library (api/PizzaSauce.lua) - fx-celebrate: firework + banner when a pet learns a new ability/rank, triggered from core-scan-beasttraining pet-learn hook - Hunter's Book: slide + fade on open, results list fade on tab switch - Options window: slide + fade on open - All new user-facing strings keyed (ANIM_* namespace) Fixes: - Tooltip #19: own-pet lines (mood/loyalty/happiness/XP) no longer misfire on party members / targets. Removed the fragile title-name fallback in MTH_TT_IsTooltipOnOwnPet; ownership now requires positive unit identity. Temp (remove before release): /mth fx and /mth fxdebug diagnostics.
1348 lines
39 KiB
Lua
1348 lines
39 KiB
Lua
-- PizzaSauce: Smooth, declarative animation library for WoW 1.12
|
|
|
|
local _G = getfenv(0)
|
|
|
|
local lib = {}
|
|
lib.version = "0.1.1"
|
|
_G["PizzaSauce"] = lib
|
|
|
|
lib._types = {}
|
|
lib._easing = {}
|
|
lib._active = {}
|
|
lib._pending = {}
|
|
lib._pendingN = 0
|
|
lib._count = 0
|
|
lib._finBuf = {}
|
|
|
|
lib._frame = CreateFrame("Frame", nil, UIParent)
|
|
lib._frame:Hide()
|
|
|
|
local lastTime = GetTime()
|
|
|
|
local function activate(anim)
|
|
if anim._active then return end
|
|
anim._active = true
|
|
lib._count = lib._count + 1
|
|
lib._active[anim] = true
|
|
if lib._count == 1 then
|
|
lastTime = GetTime()
|
|
lib._frame:Show()
|
|
end
|
|
end
|
|
|
|
local function deactivate(anim)
|
|
if not anim._active then return end
|
|
anim._active = false
|
|
lib._count = lib._count - 1
|
|
lib._active[anim] = nil
|
|
if lib._count <= 0 then
|
|
lib._count = 0
|
|
if lib._pendingN == 0 then lib._frame:Hide() end
|
|
end
|
|
end
|
|
|
|
lib._frame:SetScript("OnUpdate", function()
|
|
local now = GetTime()
|
|
local dt = now - lastTime
|
|
lastTime = now
|
|
if dt <= 0 then return end
|
|
|
|
-- Process pending auto-play animations
|
|
local pendingN = lib._pendingN
|
|
if pendingN > 0 then
|
|
lib._pendingN = 0
|
|
for i = 1, pendingN do
|
|
local anim = lib._pending[i]
|
|
lib._pending[i] = nil
|
|
if anim._autoplay then anim:Play() end
|
|
end
|
|
end
|
|
|
|
-- Hide driver if nothing to do
|
|
if lib._count == 0 then
|
|
lib._frame:Hide()
|
|
return
|
|
end
|
|
|
|
-- Tick all active, collect finished into scratch buffer.
|
|
-- Can't finish inline because _finish() callbacks may create new animations,
|
|
-- which would modify _active while we're iterating it.
|
|
local finN = 0
|
|
for anim in pairs(lib._active) do
|
|
if anim:_tick(dt, now) then
|
|
finN = finN + 1
|
|
lib._finBuf[finN] = anim
|
|
end
|
|
end
|
|
|
|
for i = 1, finN do
|
|
lib._finBuf[i]:_finish()
|
|
lib._finBuf[i] = nil
|
|
end
|
|
end)
|
|
|
|
lib._activate = activate
|
|
lib._deactivate = deactivate
|
|
|
|
function lib._schedulePending(anim)
|
|
anim._autoplay = true
|
|
lib._pendingN = lib._pendingN + 1
|
|
lib._pending[lib._pendingN] = anim
|
|
lastTime = GetTime()
|
|
lib._frame:Show()
|
|
end
|
|
|
|
function lib:RegisterType(name, handlers)
|
|
lib._types[name] = handlers
|
|
end
|
|
|
|
function lib:RegisterEasing(name, fn)
|
|
lib._easing[name] = fn
|
|
end
|
|
|
|
function lib:RegisterHelper(name, fn)
|
|
if lib[name] then
|
|
DEFAULT_CHAT_FRAME:AddMessage(
|
|
"PizzaSauce: RegisterHelper('" .. name .. "') would overwrite an existing method")
|
|
return
|
|
end
|
|
lib[name] = fn
|
|
end
|
|
local E = lib._easing
|
|
|
|
local pi = math.pi
|
|
local sin = math.sin
|
|
local cos = math.cos
|
|
local pow = math.pow
|
|
local sqrt = math.sqrt
|
|
|
|
E["linear"] = function(t) return t end
|
|
E["swing"] = function(t) return 0.5 - cos(t * pi) / 2 end
|
|
|
|
-- Quad (power of 2)
|
|
E["inQuad"] = function(t) return t * t end
|
|
E["outQuad"] = function(t) return t * (2 - t) end
|
|
E["inOutQuad"] = function(t)
|
|
if t < 0.5 then return 2 * t * t end
|
|
return -1 + (4 - 2 * t) * t
|
|
end
|
|
|
|
-- Cubic (power of 3)
|
|
E["inCubic"] = function(t) return t * t * t end
|
|
E["outCubic"] = function(t) local u = t - 1; return u * u * u + 1 end
|
|
E["inOutCubic"] = function(t)
|
|
if t < 0.5 then return 4 * t * t * t end
|
|
local u = 2 * t - 2; return (u * u * u + 2) / 2
|
|
end
|
|
|
|
-- Quart (power of 4)
|
|
E["inQuart"] = function(t) return t * t * t * t end
|
|
E["outQuart"] = function(t) local u = t - 1; return 1 - u * u * u * u end
|
|
E["inOutQuart"] = function(t)
|
|
if t < 0.5 then return 8 * t * t * t * t end
|
|
local u = t - 1; return 1 - 8 * u * u * u * u
|
|
end
|
|
|
|
-- Quint (power of 5)
|
|
E["inQuint"] = function(t) return t * t * t * t * t end
|
|
E["outQuint"] = function(t) local u = t - 1; return 1 + u * u * u * u * u end
|
|
E["inOutQuint"] = function(t)
|
|
if t < 0.5 then return 16 * t * t * t * t * t end
|
|
local u = t - 1; return 1 + 16 * u * u * u * u * u
|
|
end
|
|
|
|
-- Sine
|
|
E["inSine"] = function(t) return 1 - cos(t * pi / 2) end
|
|
E["outSine"] = function(t) return sin(t * pi / 2) end
|
|
E["inOutSine"] = function(t) return 0.5 - cos(t * pi) / 2 end
|
|
|
|
-- Expo
|
|
E["inExpo"] = function(t)
|
|
if t == 0 then return 0 end
|
|
return pow(2, 10 * (t - 1))
|
|
end
|
|
E["outExpo"] = function(t)
|
|
if t == 1 then return 1 end
|
|
return 1 - pow(2, -10 * t)
|
|
end
|
|
E["inOutExpo"] = function(t)
|
|
if t == 0 or t == 1 then return t end
|
|
if t < 0.5 then return pow(2, 20 * t - 10) / 2 end
|
|
return (2 - pow(2, -20 * t + 10)) / 2
|
|
end
|
|
|
|
-- Circ
|
|
E["inCirc"] = function(t) return 1 - sqrt(1 - t * t) end
|
|
E["outCirc"] = function(t) local u = t - 1; return sqrt(1 - u * u) end
|
|
E["inOutCirc"] = function(t)
|
|
if t < 0.5 then return (1 - sqrt(1 - 4 * t * t)) / 2 end
|
|
local u = 2 * t - 2; return (sqrt(1 - u * u) + 1) / 2
|
|
end
|
|
|
|
-- Elastic
|
|
E["inElastic"] = function(t)
|
|
if t == 0 or t == 1 then return t end
|
|
return -pow(2, 10 * t - 10) * sin((t * 10 - 10.75) * (2 * pi) / 3)
|
|
end
|
|
E["outElastic"] = function(t)
|
|
if t == 0 or t == 1 then return t end
|
|
return pow(2, -10 * t) * sin((t * 10 - 0.75) * (2 * pi) / 3) + 1
|
|
end
|
|
E["inOutElastic"] = function(t)
|
|
if t == 0 or t == 1 then return t end
|
|
if t < 0.5 then
|
|
return -pow(2, 20 * t - 10) * sin((20 * t - 11.125) * (2 * pi) / 4.5) / 2
|
|
end
|
|
return pow(2, -20 * t + 10) * sin((20 * t - 11.125) * (2 * pi) / 4.5) / 2 + 1
|
|
end
|
|
|
|
-- Back
|
|
E["inBack"] = function(t)
|
|
local s = 1.70158
|
|
return t * t * ((s + 1) * t - s)
|
|
end
|
|
E["outBack"] = function(t)
|
|
local s = 1.70158
|
|
local u = t - 1
|
|
return u * u * ((s + 1) * u + s) + 1
|
|
end
|
|
E["inOutBack"] = function(t)
|
|
local s = 1.70158 * 1.525
|
|
if t < 0.5 then
|
|
return (2 * t) * (2 * t) * ((s + 1) * (2 * t) - s) / 2
|
|
end
|
|
local u = 2 * t - 2
|
|
return (u * u * ((s + 1) * u + s) + 2) / 2
|
|
end
|
|
|
|
-- Bounce
|
|
E["outBounce"] = function(t)
|
|
if t < 1 / 2.75 then
|
|
return 7.5625 * t * t
|
|
elseif t < 2 / 2.75 then
|
|
t = t - 1.5 / 2.75
|
|
return 7.5625 * t * t + 0.75
|
|
elseif t < 2.5 / 2.75 then
|
|
t = t - 2.25 / 2.75
|
|
return 7.5625 * t * t + 0.9375
|
|
else
|
|
t = t - 2.625 / 2.75
|
|
return 7.5625 * t * t + 0.984375
|
|
end
|
|
end
|
|
E["inBounce"] = function(t) return 1 - E["outBounce"](1 - t) end
|
|
E["inOutBounce"] = function(t)
|
|
if t < 0.5 then return (1 - E["outBounce"](1 - 2 * t)) / 2 end
|
|
return (1 + E["outBounce"](2 * t - 1)) / 2
|
|
end
|
|
local function lerp(a, b, t) return a + (b - a) * t end
|
|
|
|
local function rotCorner(a)
|
|
local r = math.rad(a)
|
|
return 0.5 + math.cos(r) / 1.4142, 0.5 + math.sin(r) / 1.4142
|
|
end
|
|
|
|
local function applyRotation(texture, angle)
|
|
local a = -angle
|
|
local ULx, ULy = rotCorner(a + 225)
|
|
local LLx, LLy = rotCorner(a + 135)
|
|
local URx, URy = rotCorner(a - 45)
|
|
local LRx, LRy = rotCorner(a + 45)
|
|
texture:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
|
|
end
|
|
|
|
lib:RegisterType("alpha", {
|
|
init = function(target, from)
|
|
return from or target:GetAlpha()
|
|
end,
|
|
apply = function(target, from, to, t)
|
|
target:SetAlpha(lerp(from, to, t))
|
|
end,
|
|
})
|
|
|
|
lib:RegisterType("scale", {
|
|
init = function(target, from, to, tween)
|
|
tween._baseW = target:GetWidth()
|
|
tween._baseH = target:GetHeight()
|
|
return from or 1.0
|
|
end,
|
|
apply = function(target, from, to, t, tween)
|
|
local s = lerp(from, to, t)
|
|
target:SetWidth(tween._baseW * s)
|
|
target:SetHeight(tween._baseH * s)
|
|
end,
|
|
})
|
|
|
|
lib:RegisterType("size", {
|
|
init = function(target, from)
|
|
return from or { target:GetWidth(), target:GetHeight() }
|
|
end,
|
|
apply = function(target, from, to, t)
|
|
target:SetWidth(lerp(from[1], to[1], t))
|
|
target:SetHeight(lerp(from[2], to[2], t))
|
|
end,
|
|
})
|
|
|
|
lib:RegisterType("position", {
|
|
init = function(target, from, to, tween)
|
|
local point, rel, relPoint, x, y = target:GetPoint(1)
|
|
tween._point = point or "CENTER"
|
|
tween._rel = rel
|
|
tween._relPoint = relPoint or "CENTER"
|
|
return from or { x or 0, y or 0 }
|
|
end,
|
|
apply = function(target, from, to, t, tween)
|
|
target:ClearAllPoints()
|
|
target:SetPoint(tween._point, tween._rel, tween._relPoint,
|
|
lerp(from[1], to[1], t), lerp(from[2], to[2], t))
|
|
end,
|
|
})
|
|
|
|
lib:RegisterType("color", {
|
|
init = function(target, from)
|
|
return from or { 1, 1, 1, 1 }
|
|
end,
|
|
apply = function(target, from, to, t)
|
|
target:SetVertexColor(
|
|
lerp(from[1], to[1], t),
|
|
lerp(from[2], to[2], t),
|
|
lerp(from[3], to[3], t),
|
|
lerp(from[4] or 1, to[4] or 1, t))
|
|
end,
|
|
})
|
|
|
|
lib:RegisterType("rotation", {
|
|
init = function(target, from)
|
|
return from or 0
|
|
end,
|
|
apply = function(target, from, to, t)
|
|
applyRotation(target, lerp(from, to, t))
|
|
end,
|
|
})
|
|
|
|
lib:RegisterType("custom", {
|
|
init = function(target, from, to, tween)
|
|
if from ~= nil then return from end
|
|
if tween._getter then return tween._getter(target) end
|
|
return 0
|
|
end,
|
|
apply = function(target, from, to, t, tween)
|
|
if tween._setter then
|
|
tween._setter(target, lerp(from, to, t))
|
|
end
|
|
end,
|
|
})
|
|
local function resolveEasing(easing)
|
|
if type(easing) == "function" then return easing end
|
|
return lib._easing[easing] or lib._easing["inOutQuad"]
|
|
end
|
|
|
|
local Tween = {}
|
|
|
|
function Tween:Play()
|
|
self._elapsed = 0
|
|
self._started = false
|
|
self._done = false
|
|
lib._activate(self)
|
|
end
|
|
|
|
function Tween:Cancel()
|
|
if not self._active then return end
|
|
lib._deactivate(self)
|
|
self._done = true
|
|
if self._onCancel then self:_onCancel() end
|
|
end
|
|
|
|
function Tween:IsPlaying()
|
|
return self._active == true
|
|
end
|
|
|
|
function Tween:_start()
|
|
local handler = lib._types[self._type]
|
|
if handler and handler.init then
|
|
self._from = handler.init(self._target, self._from, self._to, self)
|
|
end
|
|
self._started = true
|
|
if self._onStart then self:_onStart() end
|
|
end
|
|
|
|
function Tween:_tick(dt)
|
|
self._elapsed = self._elapsed + dt
|
|
|
|
if self._elapsed < self._delay then return false end
|
|
|
|
if not self._started then self:_start() end
|
|
|
|
local t = self._duration > 0
|
|
and math.min((self._elapsed - self._delay) / self._duration, 1)
|
|
or 1
|
|
|
|
local handler = lib._types[self._type]
|
|
if handler and handler.apply and self._target then
|
|
handler.apply(self._target, self._from, self._to, resolveEasing(self._easing)(t), self)
|
|
end
|
|
|
|
if self._onUpdate then self:_onUpdate(t) end
|
|
|
|
return t >= 1
|
|
end
|
|
|
|
function Tween:_finish()
|
|
lib._deactivate(self)
|
|
self._done = true
|
|
|
|
-- Apply one final time at exactly t=1 -- the last _tick may have landed
|
|
-- slightly before 1.0 depending on frame timing.
|
|
local handler = lib._types[self._type]
|
|
if handler and handler.apply and self._target then
|
|
handler.apply(self._target, self._from, self._to, 1, self)
|
|
end
|
|
if handler and handler.cleanup then
|
|
handler.cleanup(self._target, self)
|
|
end
|
|
|
|
if self._onFinish then self:_onFinish() end
|
|
end
|
|
|
|
function Tween:SetFrom(val) self._from = val end
|
|
function Tween:SetTo(val) self._to = val end
|
|
function Tween:SetDuration(val) self._duration = val end
|
|
function Tween:SetDelay(val) self._delay = val end
|
|
function Tween:SetTarget(val) self._target = val end
|
|
|
|
function Tween:_reset()
|
|
self._elapsed = 0
|
|
self._started = false
|
|
self._done = false
|
|
end
|
|
|
|
local TweenMT = { __index = Tween }
|
|
|
|
local KNOWN_OPTS = {
|
|
type = true, from = true, to = true, duration = true, easing = true,
|
|
delay = true, onStart = true, onUpdate = true, onFinish = true,
|
|
onCancel = true, getter = true, setter = true, defer = true,
|
|
}
|
|
|
|
function lib:Tween(target, opts)
|
|
local tween = setmetatable({
|
|
_target = target,
|
|
_type = opts.type or "alpha",
|
|
_from = opts.from,
|
|
_to = opts.to,
|
|
_duration = opts.duration or 0.3,
|
|
_easing = opts.easing or "inOutQuad",
|
|
_delay = opts.delay or 0,
|
|
_onStart = opts.onStart,
|
|
_onUpdate = opts.onUpdate,
|
|
_onFinish = opts.onFinish,
|
|
_onCancel = opts.onCancel,
|
|
_getter = opts.getter,
|
|
_setter = opts.setter,
|
|
}, TweenMT)
|
|
|
|
-- Forward extra opts to the tween for custom type handlers.
|
|
-- Internal fields use underscore prefix, so user params (vx, radius, etc.)
|
|
-- are accessible as tween.vx, tween.radius without collision.
|
|
for k, v in pairs(opts) do
|
|
if not KNOWN_OPTS[k] then
|
|
tween[k] = v
|
|
end
|
|
end
|
|
|
|
if not opts.defer then
|
|
lib._schedulePending(tween)
|
|
end
|
|
|
|
return tween
|
|
end
|
|
local Sequence = {}
|
|
|
|
function Sequence:Play()
|
|
self._index = 1
|
|
self._loopCount = 0
|
|
self._elapsed = 0
|
|
self._done = false
|
|
self:_resetChildren()
|
|
lib._activate(self)
|
|
end
|
|
|
|
function Sequence:Cancel()
|
|
if not self._active then return end
|
|
local child = self._children[self._index]
|
|
if child and child._started and child.Cancel then child:Cancel() end
|
|
lib._deactivate(self)
|
|
self._done = true
|
|
if self._onCancel then self:_onCancel() end
|
|
end
|
|
|
|
function Sequence:IsPlaying()
|
|
return self._active == true
|
|
end
|
|
|
|
function Sequence:_tick(dt)
|
|
self._elapsed = self._elapsed + dt
|
|
if self._elapsed < self._delay then return false end
|
|
|
|
local child = self._children[self._index]
|
|
if not child then return true end
|
|
|
|
if not child._started then child:_start() end
|
|
|
|
if child:_tick(dt) then
|
|
child:_finish()
|
|
self._index = self._index + 1
|
|
|
|
if self._index > table.getn(self._children) then
|
|
return self:_handleLoop()
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function Sequence:_finish()
|
|
lib._deactivate(self)
|
|
self._done = true
|
|
if self._onFinish then self:_onFinish() end
|
|
end
|
|
|
|
function Sequence:_start()
|
|
self._started = true
|
|
end
|
|
|
|
function Sequence:_reset()
|
|
self._index = 1
|
|
self._loopCount = 0
|
|
self._elapsed = 0
|
|
self._done = false
|
|
self._started = false
|
|
self:_resetChildren()
|
|
end
|
|
|
|
function Sequence:_resetChildren()
|
|
for i = 1, table.getn(self._children) do
|
|
self._children[i]:_reset()
|
|
end
|
|
end
|
|
|
|
function Sequence:_handleLoop()
|
|
self._loopCount = self._loopCount + 1
|
|
if self._loop ~= 0 and self._loopCount >= self._loop then
|
|
return true
|
|
end
|
|
|
|
if self._yoyo then
|
|
local c = self._children
|
|
local n = table.getn(c)
|
|
for i = 1, math.floor(n / 2) do
|
|
c[i], c[n - i + 1] = c[n - i + 1], c[i]
|
|
end
|
|
end
|
|
|
|
self._index = 1
|
|
self:_resetChildren()
|
|
return false
|
|
end
|
|
|
|
local SequenceMT = { __index = Sequence }
|
|
|
|
function lib:Sequence(children, opts)
|
|
opts = opts or {}
|
|
|
|
for i = 1, table.getn(children) do
|
|
children[i]._autoplay = false
|
|
end
|
|
|
|
local seq = setmetatable({
|
|
_children = children,
|
|
_index = 1,
|
|
_loop = opts.loop or 1,
|
|
_yoyo = opts.yoyo or false,
|
|
_loopCount = 0,
|
|
_delay = opts.delay or 0,
|
|
_elapsed = 0,
|
|
_onFinish = opts.onFinish,
|
|
_onCancel = opts.onCancel,
|
|
_done = false,
|
|
}, SequenceMT)
|
|
|
|
if not opts.defer then
|
|
lib._schedulePending(seq)
|
|
end
|
|
|
|
return seq
|
|
end
|
|
local Group = {}
|
|
|
|
function Group:Play()
|
|
self._loopCount = 0
|
|
self._elapsed = 0
|
|
self._done = false
|
|
self:_resetChildren()
|
|
lib._activate(self)
|
|
end
|
|
|
|
function Group:Cancel()
|
|
if not self._active then return end
|
|
for i = 1, table.getn(self._children) do
|
|
local child = self._children[i]
|
|
if child._active or child._started then
|
|
if child.Cancel then child:Cancel() end
|
|
end
|
|
end
|
|
lib._deactivate(self)
|
|
self._done = true
|
|
if self._onCancel then self:_onCancel() end
|
|
end
|
|
|
|
function Group:IsPlaying()
|
|
return self._active == true
|
|
end
|
|
|
|
function Group:_tick(dt)
|
|
self._elapsed = self._elapsed + dt
|
|
if self._elapsed < self._delay then return false end
|
|
|
|
local allDone = true
|
|
|
|
for i = 1, table.getn(self._children) do
|
|
local child = self._children[i]
|
|
if not child._done then
|
|
if not child._started then child:_start() end
|
|
if child:_tick(dt) then
|
|
child:_finish()
|
|
else
|
|
allDone = false
|
|
end
|
|
end
|
|
end
|
|
|
|
if allDone then
|
|
return self:_handleLoop()
|
|
end
|
|
return false
|
|
end
|
|
|
|
function Group:_handleLoop()
|
|
self._loopCount = self._loopCount + 1
|
|
if self._loop ~= 0 and self._loopCount >= self._loop then
|
|
return true
|
|
end
|
|
|
|
if self._yoyo then
|
|
local c = self._children
|
|
local n = table.getn(c)
|
|
for i = 1, math.floor(n / 2) do
|
|
c[i], c[n - i + 1] = c[n - i + 1], c[i]
|
|
end
|
|
end
|
|
|
|
self:_resetChildren()
|
|
return false
|
|
end
|
|
|
|
function Group:_finish()
|
|
lib._deactivate(self)
|
|
self._done = true
|
|
if self._onFinish then self:_onFinish() end
|
|
end
|
|
|
|
function Group:_start()
|
|
self._started = true
|
|
end
|
|
|
|
function Group:_reset()
|
|
self._loopCount = 0
|
|
self._elapsed = 0
|
|
self._done = false
|
|
self._started = false
|
|
self:_resetChildren()
|
|
end
|
|
|
|
function Group:_resetChildren()
|
|
for i = 1, table.getn(self._children) do
|
|
self._children[i]:_reset()
|
|
end
|
|
end
|
|
|
|
local GroupMT = { __index = Group }
|
|
|
|
function lib:Group(children, opts)
|
|
opts = opts or {}
|
|
|
|
for i = 1, table.getn(children) do
|
|
children[i]._autoplay = false
|
|
end
|
|
|
|
local grp = setmetatable({
|
|
_children = children,
|
|
_loop = opts.loop or 1,
|
|
_yoyo = opts.yoyo or false,
|
|
_loopCount = 0,
|
|
_delay = opts.delay or 0,
|
|
_elapsed = 0,
|
|
_onFinish = opts.onFinish,
|
|
_onCancel = opts.onCancel,
|
|
_done = false,
|
|
}, GroupMT)
|
|
|
|
if not opts.defer then
|
|
lib._schedulePending(grp)
|
|
end
|
|
|
|
return grp
|
|
end
|
|
-- Directional offsets for slide animations
|
|
local directions = {
|
|
LEFT = function(d) return { -d, 0 } end,
|
|
RIGHT = function(d) return { d, 0 } end,
|
|
UP = function(d) return { 0, d } end,
|
|
DOWN = function(d) return { 0,-d } end,
|
|
}
|
|
|
|
local function dirOffset(dir, dist)
|
|
local fn = directions[string.upper(dir or "LEFT")]
|
|
return fn and fn(dist) or { 0, 0 }
|
|
end
|
|
|
|
local function getAnchorPos(frame)
|
|
local _, _, _, x, y = frame:GetPoint(1)
|
|
return { x or 0, y or 0 }
|
|
end
|
|
|
|
local EMPTY = {}
|
|
local function opts(o)
|
|
if type(o) == "table" then return o end
|
|
return EMPTY
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Alpha
|
|
-- ============================================================
|
|
|
|
function lib:FadeTo(target, toAlpha, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "alpha", to = toAlpha,
|
|
duration = duration, easing = easing,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:FadeIn(target, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "alpha", from = 0, to = 1,
|
|
duration = duration, easing = easing,
|
|
onStart = function()
|
|
target:SetAlpha(0)
|
|
target:Show()
|
|
if o.onStart then o.onStart() end
|
|
end,
|
|
delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:FadeOut(target, duration, easing, o)
|
|
o = opts(o)
|
|
local onFinish = o.onFinish
|
|
return lib:FadeTo(target, 0, duration, easing, {
|
|
delay = o.delay, onCancel = o.onCancel, defer = o.defer,
|
|
onFinish = function() target:Hide(); if onFinish then onFinish() end end,
|
|
})
|
|
end
|
|
|
|
function lib:Flash(target, count, duration, o)
|
|
o = opts(o)
|
|
count = count or 3
|
|
duration = duration or 0.6
|
|
local half = duration / count / 2
|
|
local steps = {}
|
|
for i = 1, count do
|
|
table.insert(steps, lib:Tween(target, { type = "alpha", to = 0, duration = half, easing = "inQuad", defer = true }))
|
|
table.insert(steps, lib:Tween(target, { type = "alpha", to = 1, duration = half, easing = "outQuad", defer = true }))
|
|
end
|
|
return lib:Sequence(steps, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
function lib:Breathe(target, minAlpha, maxAlpha, duration, o)
|
|
o = opts(o)
|
|
duration = duration or 1.6
|
|
local half = duration / 2
|
|
return lib:Sequence({
|
|
lib:Tween(target, { type = "alpha", from = minAlpha, to = maxAlpha, duration = half, easing = "inOutQuad", defer = true }),
|
|
lib:Tween(target, { type = "alpha", from = maxAlpha, to = minAlpha, duration = half, easing = "inOutQuad", defer = true }),
|
|
}, { loop = 0, delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Scale
|
|
-- ============================================================
|
|
|
|
function lib:ScaleIn(target, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "scale", from = 0, to = 1,
|
|
duration = duration or 0.3, easing = easing or "outBack",
|
|
onStart = function()
|
|
target:Show()
|
|
if o.onStart then o.onStart() end
|
|
end,
|
|
delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:ScaleOut(target, duration, easing, o)
|
|
o = opts(o)
|
|
local onFinish = o.onFinish
|
|
return lib:Tween(target, {
|
|
type = "scale", from = 1, to = 0,
|
|
duration = duration or 0.3, easing = easing or "inBack",
|
|
onStart = o.onStart,
|
|
onFinish = function() target:Hide(); if onFinish then onFinish() end end,
|
|
onCancel = o.onCancel, delay = o.delay, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:Pulse(target, scale, duration, o)
|
|
o = opts(o)
|
|
scale = scale or 1.3
|
|
local half = (duration or 0.4) / 2
|
|
local baseW, baseH = target:GetWidth(), target:GetHeight()
|
|
local function set(f, s)
|
|
f:SetWidth(baseW * s)
|
|
f:SetHeight(baseH * s)
|
|
end
|
|
return lib:Sequence({
|
|
lib:Tween(target, { type = "custom", from = 1.0, to = scale, duration = half, easing = "outQuad", setter = set, defer = true }),
|
|
lib:Tween(target, { type = "custom", from = scale, to = 1.0, duration = half, easing = "inQuad", setter = set, defer = true }),
|
|
}, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
function lib:Rubber(target, scale, duration, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "scale", from = scale or 1.3, to = 1.0,
|
|
duration = duration or 0.5, easing = "outElastic",
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:Flip(target, axis, duration, o)
|
|
o = opts(o)
|
|
local quarter = (duration or 0.8) / 4
|
|
local horizontal = axis ~= "y"
|
|
local base = horizontal and target:GetWidth() or target:GetHeight()
|
|
local set = horizontal
|
|
and function(f, v) f:SetWidth(v) end
|
|
or function(f, v) f:SetHeight(v) end
|
|
|
|
-- Find child textures to mirror via SetTexCoord
|
|
local textures = {}
|
|
if target.GetTexture then
|
|
table.insert(textures, target)
|
|
else
|
|
local children = { target:GetRegions() }
|
|
for i = 1, table.getn(children) do
|
|
if children[i].GetTexture then table.insert(textures, children[i]) end
|
|
end
|
|
end
|
|
|
|
local function mirror()
|
|
for i = 1, table.getn(textures) do
|
|
if horizontal then
|
|
textures[i]:SetTexCoord(1, 0, 0, 1)
|
|
else
|
|
textures[i]:SetTexCoord(0, 1, 1, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function unmirror()
|
|
for i = 1, table.getn(textures) do
|
|
textures[i]:SetTexCoord(0, 1, 0, 1)
|
|
end
|
|
end
|
|
|
|
local shrink = function(f, v) set(f, v) end
|
|
return lib:Sequence({
|
|
-- 0°→90°: shrink to invisible
|
|
lib:Tween(target, { type = "custom", from = base, to = 0.01, duration = quarter, easing = "inQuad", setter = shrink, defer = true }),
|
|
-- 90°→180°: mirror and expand (showing flipped content)
|
|
lib:Tween(target, { type = "custom", from = 0.01, to = base, duration = quarter, easing = "outQuad", setter = shrink, defer = true,
|
|
onStart = function() mirror() end }),
|
|
-- 180°→270°: shrink again
|
|
lib:Tween(target, { type = "custom", from = base, to = 0.01, duration = quarter, easing = "inQuad", setter = shrink, defer = true }),
|
|
-- 270°→360°: unmirror and expand back to original
|
|
lib:Tween(target, { type = "custom", from = 0.01, to = base, duration = quarter, easing = "outQuad", setter = shrink, defer = true,
|
|
onStart = function() unmirror() end }),
|
|
}, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Position
|
|
-- ============================================================
|
|
|
|
function lib:Move(target, from, to, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "position", from = from, to = to,
|
|
duration = duration or 0.3, easing = easing,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:MoveTo(target, to, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "position", to = to,
|
|
duration = duration, easing = easing,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:SlideIn(target, direction, distance, duration, easing, o)
|
|
o = opts(o)
|
|
local dest = getAnchorPos(target)
|
|
local offset = dirOffset(direction, distance or 100)
|
|
local from = { dest[1] - offset[1], dest[2] - offset[2] }
|
|
return lib:Group({
|
|
lib:Tween(target, {
|
|
type = "position", from = from, to = dest, duration = duration or 0.3, easing = easing or "outCubic",
|
|
onStart = function()
|
|
target:SetAlpha(0)
|
|
target:Show()
|
|
if o.onStart then o.onStart() end
|
|
end,
|
|
defer = true,
|
|
}),
|
|
lib:Tween(target, {
|
|
type = "alpha",
|
|
from = 0,
|
|
to = 1,
|
|
duration = (duration or 0.3) * 0.6,
|
|
easing = "outQuad",
|
|
defer = true,
|
|
}),
|
|
}, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
function lib:SlideOut(target, direction, distance, duration, easing, o)
|
|
o = opts(o)
|
|
local from = getAnchorPos(target)
|
|
local offset = dirOffset(direction, distance or 100)
|
|
local dest = { from[1] + offset[1], from[2] + offset[2] }
|
|
local onFinish = o.onFinish
|
|
return lib:Group({
|
|
lib:Tween(target, { type = "position", from = from, to = dest, duration = duration or 0.3, easing = easing or "inCubic", defer = true }),
|
|
lib:Tween(target, { type = "alpha", to = 0, duration = duration or 0.3, easing = "inQuad", defer = true }),
|
|
}, {
|
|
delay = o.delay, onCancel = o.onCancel, defer = o.defer,
|
|
onFinish = function() target:Hide(); if onFinish then onFinish() end end,
|
|
})
|
|
end
|
|
|
|
function lib:Bounce(target, height, count, duration, o)
|
|
o = opts(o)
|
|
height = height or 30
|
|
count = count or 3
|
|
duration = duration or 0.6
|
|
local pos = getAnchorPos(target)
|
|
local steps = {}
|
|
for i = 1, count do
|
|
local h = height * (1 - (i - 1) / count) -- decay
|
|
table.insert(steps, lib:Tween(target, {
|
|
type = "position", from = pos, to = { pos[1], pos[2] + h },
|
|
duration = duration / count / 2, easing = "outQuad", defer = true,
|
|
}))
|
|
table.insert(steps, lib:Tween(target, {
|
|
type = "position", from = { pos[1], pos[2] + h }, to = pos,
|
|
duration = duration / count / 2, easing = "inQuad", defer = true,
|
|
}))
|
|
end
|
|
return lib:Sequence(steps, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
function lib:Shake(target, intensity, duration, o)
|
|
o = opts(o)
|
|
intensity = intensity or 5
|
|
duration = duration or 0.3
|
|
local pos = getAnchorPos(target)
|
|
local steps = {}
|
|
local stepDur = 0.03
|
|
local numSteps = math.floor(duration / stepDur)
|
|
for i = 1, numSteps do
|
|
local decay = 1 - (i - 1) / numSteps
|
|
local dx = (math.random() * intensity * 2 - intensity) * decay
|
|
local dy = (math.random() * intensity * 2 - intensity) * decay
|
|
table.insert(steps, lib:Tween(target, {
|
|
type = "position", to = { pos[1] + dx, pos[2] + dy },
|
|
duration = stepDur, easing = "linear", defer = true,
|
|
}))
|
|
end
|
|
-- Snap back to original position
|
|
table.insert(steps, lib:Tween(target, {
|
|
type = "position", to = pos,
|
|
duration = stepDur, easing = "linear", defer = true,
|
|
}))
|
|
return lib:Sequence(steps, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
function lib:FlyTo(frame, targetFrame, duration, easing, o)
|
|
o = opts(o)
|
|
local toX = targetFrame:GetLeft() + targetFrame:GetWidth() / 2 - UIParent:GetWidth() / 2
|
|
local toY = targetFrame:GetBottom() + targetFrame:GetHeight() / 2 - UIParent:GetHeight() / 2
|
|
return lib:Tween(frame, {
|
|
type = "position", to = { toX, toY },
|
|
duration = duration or 0.5, easing = easing or "inOutQuad",
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Rotation
|
|
-- ============================================================
|
|
|
|
function lib:Spin(texture, degreesPerSecond, o)
|
|
o = opts(o)
|
|
local dps = degreesPerSecond or 90
|
|
return lib:Sequence({
|
|
lib:Tween(texture, {
|
|
type = "rotation",
|
|
from = 0,
|
|
to = dps > 0 and 360 or -360,
|
|
duration = 360 / math.abs(dps),
|
|
easing = "linear",
|
|
defer = true,
|
|
}),
|
|
}, { loop = 0, delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Color
|
|
-- ============================================================
|
|
|
|
function lib:Color(target, from, to, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "color", from = from, to = to,
|
|
duration = duration or 0.3, easing = easing,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:ColorFlash(texture, r, g, b, duration, o)
|
|
o = opts(o)
|
|
duration = duration or 0.4
|
|
local half = duration / 2
|
|
return lib:Sequence({
|
|
lib:Tween(texture, { type = "color", to = { r, g, b, 1 }, duration = half, easing = "outQuad", defer = true }),
|
|
lib:Tween(texture, { type = "color", from = { r, g, b, 1 }, to = { 1, 1, 1, 1 }, duration = half, easing = "inQuad", defer = true }),
|
|
}, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Size
|
|
-- ============================================================
|
|
|
|
function lib:Size(target, from, to, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "size", from = from, to = to,
|
|
duration = duration or 0.3, easing = easing,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:Morph(target, to, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(target, {
|
|
type = "size", to = to,
|
|
duration = duration or 0.3, easing = easing,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Value
|
|
-- ============================================================
|
|
|
|
function lib:Progress(statusbar, toValue, duration, easing, o)
|
|
o = opts(o)
|
|
return lib:Tween(statusbar, {
|
|
type = "custom",
|
|
to = toValue,
|
|
duration = duration or 0.4,
|
|
easing = easing or "outCubic",
|
|
getter = function(bar) return bar:GetValue() end,
|
|
setter = function(bar, val) bar:SetValue(val) end,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Text
|
|
-- ============================================================
|
|
|
|
function lib:Typewriter(fontstring, text, duration, o)
|
|
o = opts(o)
|
|
local len = string.len(text)
|
|
return lib:Tween(fontstring, {
|
|
type = "custom",
|
|
from = 0, to = len,
|
|
duration = duration or len * 0.05,
|
|
easing = "linear",
|
|
setter = function(fs, val)
|
|
fs:SetText(string.sub(text, 1, math.floor(val)))
|
|
end,
|
|
delay = o.delay, onStart = o.onStart, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Clipping (Reveal / Conceal)
|
|
-- ============================================================
|
|
|
|
-- Progressively reveal a texture from a direction by re-anchoring
|
|
-- to one edge, animating width/height, and shifting tex coords.
|
|
local clipAnchors = {
|
|
LEFT = { "TOPLEFT", "TOPLEFT" },
|
|
RIGHT = { "TOPRIGHT", "TOPRIGHT" },
|
|
UP = { "TOPLEFT", "TOPLEFT" },
|
|
DOWN = { "BOTTOMLEFT", "BOTTOMLEFT" },
|
|
}
|
|
|
|
local function clipTween(target, direction, duration, easing, from, to, o)
|
|
o = opts(o)
|
|
direction = string.upper(direction or "LEFT")
|
|
duration = duration or 0.3
|
|
|
|
local parent = target:GetParent()
|
|
local baseW = target._clipBaseW or parent:GetWidth()
|
|
local baseH = target._clipBaseH or parent:GetHeight()
|
|
target._clipBaseW = baseW
|
|
target._clipBaseH = baseH
|
|
|
|
local horizontal = (direction == "LEFT" or direction == "RIGHT")
|
|
local reversed = (direction == "RIGHT" or direction == "UP")
|
|
local anchor = clipAnchors[direction]
|
|
|
|
target:ClearAllPoints()
|
|
target:SetPoint(anchor[1], parent, anchor[2], 0, 0)
|
|
|
|
return lib:Tween(target, {
|
|
type = "custom", from = from, to = to,
|
|
duration = duration, easing = easing or "outQuad",
|
|
setter = function(tex, t)
|
|
if horizontal then
|
|
tex:SetWidth(math.max(baseW * t, 0.01))
|
|
tex:SetHeight(baseH)
|
|
if reversed then
|
|
tex:SetTexCoord(1 - t, 1, 0, 1)
|
|
else
|
|
tex:SetTexCoord(0, t, 0, 1)
|
|
end
|
|
else
|
|
tex:SetWidth(baseW)
|
|
tex:SetHeight(math.max(baseH * t, 0.01))
|
|
if reversed then
|
|
tex:SetTexCoord(0, 1, 1 - t, 1)
|
|
else
|
|
tex:SetTexCoord(0, 1, 0, t)
|
|
end
|
|
end
|
|
end,
|
|
onFinish = function()
|
|
target:SetTexCoord(0, 1, 0, 1)
|
|
target:ClearAllPoints()
|
|
target:SetAllPoints(parent)
|
|
if o.onFinish then o.onFinish() end
|
|
end,
|
|
onCancel = o.onCancel, delay = o.delay, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:Reveal(target, direction, duration, easing, o)
|
|
target:Show()
|
|
return clipTween(target, direction, duration, easing, 0, 1, o)
|
|
end
|
|
|
|
function lib:Conceal(target, direction, duration, easing, o)
|
|
o = opts(o)
|
|
local onFinish = o.onFinish
|
|
return clipTween(target, direction, duration, easing, 1, 0, {
|
|
delay = o.delay, onCancel = o.onCancel, defer = o.defer,
|
|
onFinish = function() target:Hide(); if onFinish then onFinish() end end,
|
|
})
|
|
end
|
|
|
|
-- WipeIn/WipeOut: texture slides in from a direction while being clipped.
|
|
-- Unlike Reveal/Conceal where the image is stationary and a window opens,
|
|
-- here the image physically moves into view (like sliding out from under a cover).
|
|
-- WipeIn/WipeOut: the image slides into/out of view while being clipped.
|
|
-- The texture stays full-size (no stretching) -- we clip by shrinking the
|
|
-- visible region and offsetting tex coords so the revealed portion matches
|
|
-- the correct part of the image.
|
|
--
|
|
-- For "LEFT" (image slides in from the left edge):
|
|
-- anchor at TOPLEFT, width = baseW * t, texCoord left = 1-t
|
|
-- → at t=0 nothing visible; at t=1 full image
|
|
local wipeAnchors = {
|
|
LEFT = "TOPLEFT",
|
|
RIGHT = "TOPRIGHT",
|
|
UP = "TOPLEFT",
|
|
DOWN = "BOTTOMLEFT",
|
|
}
|
|
|
|
local function wipeTween(target, direction, duration, easing, from, to, o)
|
|
o = opts(o)
|
|
direction = string.upper(direction or "LEFT")
|
|
duration = duration or 0.3
|
|
|
|
local parent = target:GetParent()
|
|
local baseW = target._clipBaseW or parent:GetWidth()
|
|
local baseH = target._clipBaseH or parent:GetHeight()
|
|
target._clipBaseW = baseW
|
|
target._clipBaseH = baseH
|
|
|
|
local horizontal = (direction == "LEFT" or direction == "RIGHT")
|
|
local anchor = wipeAnchors[direction]
|
|
|
|
target:ClearAllPoints()
|
|
target:SetPoint(anchor, parent, anchor, 0, 0)
|
|
|
|
return lib:Tween(target, {
|
|
type = "custom", from = from, to = to,
|
|
duration = duration, easing = easing or "outQuad",
|
|
setter = function(tex, t)
|
|
if horizontal then
|
|
tex:SetWidth(math.max(baseW * t, 0.01))
|
|
tex:SetHeight(baseH)
|
|
if direction == "LEFT" then
|
|
tex:SetTexCoord(1 - t, 1, 0, 1)
|
|
else
|
|
tex:SetTexCoord(0, t, 0, 1)
|
|
end
|
|
else
|
|
tex:SetWidth(baseW)
|
|
tex:SetHeight(math.max(baseH * t, 0.01))
|
|
if direction == "DOWN" then
|
|
tex:SetTexCoord(0, 1, 1 - t, 1)
|
|
else
|
|
tex:SetTexCoord(0, 1, 0, t)
|
|
end
|
|
end
|
|
end,
|
|
onFinish = function()
|
|
target:SetTexCoord(0, 1, 0, 1)
|
|
target:ClearAllPoints()
|
|
target:SetAllPoints(parent)
|
|
if o.onFinish then o.onFinish() end
|
|
end,
|
|
onCancel = o.onCancel, delay = o.delay, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
local wipeInFlip = { LEFT="RIGHT", RIGHT="LEFT", UP="DOWN", DOWN="UP" }
|
|
|
|
function lib:WipeIn(target, direction, duration, easing, o)
|
|
target:Show()
|
|
local dir = wipeInFlip[string.upper(direction or "LEFT")] or direction
|
|
return wipeTween(target, dir, duration, easing, 0, 1, o)
|
|
end
|
|
|
|
function lib:WipeOut(target, direction, duration, easing, o)
|
|
o = opts(o)
|
|
local onFinish = o.onFinish
|
|
return wipeTween(target, direction, duration, easing, 1, 0, {
|
|
delay = o.delay, onCancel = o.onCancel, defer = o.defer,
|
|
onFinish = function() target:Hide(); if onFinish then onFinish() end end,
|
|
})
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Meta / Utility
|
|
-- ============================================================
|
|
|
|
function lib:Stagger(targets, animFn, delay, o)
|
|
o = opts(o)
|
|
delay = delay or 0.08
|
|
local children = {}
|
|
for i = 1, table.getn(targets) do
|
|
local anim = animFn(targets[i], i)
|
|
anim._autoplay = false
|
|
if i > 1 then
|
|
table.insert(children, lib:Sequence({ lib:Delay(delay * (i - 1)), anim }, { defer = true }))
|
|
else
|
|
table.insert(children, anim)
|
|
end
|
|
end
|
|
return lib:Group(children, { delay = o.delay, onFinish = o.onFinish, onCancel = o.onCancel, defer = o.defer })
|
|
end
|
|
|
|
function lib:Delay(duration)
|
|
-- A no-op tween that just waits. Uses a dummy target-less approach.
|
|
local dummy = lib._frame
|
|
return lib:Tween(dummy, {
|
|
type = "alpha",
|
|
from = dummy:GetAlpha(),
|
|
to = dummy:GetAlpha(),
|
|
duration = duration or 1,
|
|
defer = true,
|
|
})
|
|
end
|
|
|
|
function lib:Run(fn, o)
|
|
o = opts(o)
|
|
return lib:Tween(nil, {
|
|
type = "custom", from = 0, to = 0, duration = 0,
|
|
onStart = fn, delay = o.delay, defer = o.defer,
|
|
})
|
|
end
|
|
|
|
function lib:Stop(target)
|
|
for anim in pairs(lib._active) do
|
|
if anim._target == target then
|
|
anim:Cancel()
|
|
elseif anim._children then
|
|
lib:_stopInChildren(anim, anim, target)
|
|
end
|
|
end
|
|
-- Also cancel pending (not yet started) animations for this target
|
|
for i = 1, lib._pendingN do
|
|
local anim = lib._pending[i]
|
|
if anim then
|
|
if anim._target == target then
|
|
anim._autoplay = false
|
|
elseif anim._children then
|
|
if lib:_hasTargetInChildren(anim, target) then
|
|
anim._autoplay = false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function lib:_hasTargetInChildren(parent, target)
|
|
for i = 1, table.getn(parent._children) do
|
|
local child = parent._children[i]
|
|
if child._target == target then return true end
|
|
if child._children and lib:_hasTargetInChildren(child, target) then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function lib:_stopInChildren(root, parent, target)
|
|
for i = 1, table.getn(parent._children) do
|
|
local child = parent._children[i]
|
|
if child._target == target then
|
|
root:Cancel()
|
|
return
|
|
elseif child._children then
|
|
lib:_stopInChildren(root, child, target)
|
|
if not root._active then return end
|
|
end
|
|
end
|
|
end
|