mirror of
https://github.com/folke/snacks.nvim
synced 2025-08-04 02:38:46 +00:00
127 lines
3.7 KiB
Lua
127 lines
3.7 KiB
Lua
---@class snacks.animate
|
|
---@overload fun(from: number, to: number, cb: fun(value: number, prev?: number), opts?: snacks.animate.Opts)
|
|
local M = setmetatable({}, {
|
|
__call = function(M, ...)
|
|
return M.animate(...)
|
|
end,
|
|
})
|
|
|
|
-- All easing functions take these parameters:
|
|
--
|
|
-- t = time should go from 0 to duration
|
|
-- b = begin value of the property being ease.
|
|
-- c = change ending value of the property - beginning value of the property
|
|
-- d = duration
|
|
--
|
|
-- Some functions allow additional modifiers, like the elastic functions
|
|
-- which also can receive an amplitud and a period parameters (defaults
|
|
-- are included)
|
|
---@alias snacks.animate.easing.Fn fun(t: number, b: number, c: number, d: number): number
|
|
|
|
---@class snacks.animate.Duration
|
|
---@field step? number ms per step. If total is also set, this is the maximum duration
|
|
---@field total? number maximum duration in ms
|
|
|
|
---@class snacks.animate.Config
|
|
---@field easing? snacks.animate.easing|snacks.animate.easing.Fn
|
|
local defaults = {
|
|
---@type snacks.animate.Duration|number
|
|
duration = 20, -- ms per step
|
|
easing = "linear",
|
|
fps = 30, -- frames per second. Global setting for all animations
|
|
}
|
|
|
|
---@class snacks.animate.Opts: snacks.animate.Config
|
|
---@field int? boolean interpolate the value to an integer
|
|
---@field id? number|string unique identifier for the animation
|
|
|
|
---@class snacks.animate.Animation
|
|
---@field from number
|
|
---@field to number
|
|
---@field duration number
|
|
---@field easing snacks.animate.easing.Fn
|
|
---@field value number
|
|
---@field start number
|
|
---@field int boolean
|
|
---@field cb fun(value: number, prev?: number)
|
|
|
|
local uv = vim.uv or vim.loop
|
|
local _id = 0
|
|
local active = {} ---@type table<number|string, snacks.animate.Animation>
|
|
local timer = assert(uv.new_timer())
|
|
local cbs = {} ---@type table<function, number[]>
|
|
|
|
---@param from number
|
|
---@param to number
|
|
---@param cb fun(value: number, prev?: number)
|
|
---@param opts? snacks.animate.Opts
|
|
function M.animate(from, to, cb, opts)
|
|
opts = Snacks.config.get("animate", defaults, opts) --[[@as snacks.animate.Opts]]
|
|
local d = type(opts.duration) == "table" and opts.duration or { step = opts.duration }
|
|
---@cast d snacks.animate.Duration
|
|
|
|
local duration = 0
|
|
if d.step then
|
|
duration = d.step * math.abs(to - from)
|
|
duration = math.min(duration, d.total or duration)
|
|
elseif d.total then
|
|
duration = d.total
|
|
end
|
|
|
|
local easing = opts.easing or "linear"
|
|
easing = type(easing) == "string" and require("snacks.animate.easing")[easing] or easing
|
|
---@cast easing snacks.animate.easing.Fn
|
|
|
|
_id = _id + 1
|
|
active[opts.id or _id] = {
|
|
from = from,
|
|
to = to,
|
|
value = from,
|
|
int = opts.int or false,
|
|
duration = duration --[[@as number]],
|
|
easing = easing,
|
|
start = uv.hrtime(),
|
|
cb = cb,
|
|
}
|
|
M.start()
|
|
end
|
|
|
|
function M.step()
|
|
for a, anim in pairs(active) do
|
|
local elapsed = (uv.hrtime() - anim.start) / 1e6 -- ms
|
|
local b, c, d = anim.from, anim.to - anim.from, anim.duration
|
|
local t = math.min(elapsed, d)
|
|
local value = t == d and b + c or anim.easing(t, b, c, d)
|
|
value = anim.int and math.floor(value) or value
|
|
local prev = anim.value
|
|
if prev ~= value then
|
|
cbs[anim.cb] = { value, prev }
|
|
anim.value = value
|
|
end
|
|
if t >= d then
|
|
active[a] = nil
|
|
end
|
|
end
|
|
if vim.tbl_isempty(active) then
|
|
timer:stop()
|
|
end
|
|
if not vim.tbl_isempty(cbs) then
|
|
vim.schedule(function()
|
|
for cb, values in pairs(cbs) do
|
|
cb(values[1], values[2])
|
|
end
|
|
cbs = {}
|
|
end)
|
|
end
|
|
end
|
|
|
|
function M.start()
|
|
if timer:is_active() then
|
|
return
|
|
end
|
|
local opts = Snacks.config.get("animate", defaults)
|
|
local ms = 1000 / (opts and opts.fps or 30)
|
|
timer:start(0, ms, M.step)
|
|
end
|
|
|
|
return M
|