summaryrefslogtreecommitdiff
path: root/lua/99/ops/throbber.lua
blob: 4dd586782cefe6f3afc22ff15eb474abb61bb475 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
local time = require("99.time")
local Consts = require("99.consts")

local throb_icons = {
  { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" },
  { "◐", "◓", "◑", "◒" },
  { "⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷" },
  { "◰", "◳", "◲", "◱" },
  { "◜", "◠", "◝", "◞", "◡", "◟" },
}

--- @alias _99.Throbber.ThrobFN fun(perc: number): string
--- @alias _99.Throbber.EaseFN fun(perc: number): number

--- @param ease_fn _99.Throbber.EaseFN
--- @return _99.Throbber.ThrobFN
local function create_throbber(ease_fn)
  ease_fn = ease_fn or function(p)
    return p
  end
  local icon_set = throb_icons[math.random(#throb_icons)]
  return function(percent)
    local eased = ease_fn(percent)
    local index = math.floor(eased * #icon_set) + 1
    return icon_set[math.min(index, #icon_set)]
  end
end

--- @param percent number
--- @return number
--- @diagnostic disable-next-line
local function linear(percent)
  return percent
end

--- @param percent number
--- @return number
--- @diagnostic disable-next-line
local function ease_in_ease_out_quadratic(percent)
  if percent < 0.5 then
    return 2 * percent * percent
  else
    local f = percent - 1
    return 1 - 2 * f * f
  end
end

--- @param percent number
--- @return number
--- @diagnostic disable-next-line
local function ease_in_ease_out_cubic(percent)
  if percent < 0.5 then
    return 4 * percent * percent * percent
  else
    local f = (2 * percent) - 2
    return 1 - (f * f * f / 2)
  end
end

--- @class _99.Throbber
--- @field start_time number
--- @field section_time number
--- @field state "init" | "throbbing" | "cooldown" | "stopped"
--- @field throb_fn _99.Throbber.ThrobFN
--- @field opts _99.Throbber.Opts
--- @field cb fun(str: string): nil
local Throbber = {}
Throbber.__index = Throbber

--- @class _99.Throbber.Opts
--- @field throb_time number
--- @field cooldown_time number
--- @field tick_time number

--- @param cb fun(str: string): nil
--- @param opts _99.Throbber.Opts?
--- @return _99.Throbber
function Throbber.new(cb, opts)
  opts = opts or {}
  opts.throb_time = opts.throb_time or Consts.throbber_throb_time
  opts.cooldown_time = opts.cooldown_time or Consts.throbber_cooldown_time
  opts.tick_time = opts.tick_time or Consts.throbber_tick_time

  return setmetatable({
    state = "init",
    start_time = 0,
    section_time = 0,
    opts = opts,
    cb = cb,
    throb_fn = create_throbber(linear),
  }, Throbber)
end

function Throbber:_run()
  if self.state ~= "throbbing" and self.state ~= "cooldown" then
    return
  end

  local elapsed = time.now() - self.start_time
  local percent = math.min(1, elapsed / self.section_time)
  local icon = self.throb_fn(self.state == "throbbing" and percent or 1)

  if percent == 1 then
    self.state = self.state == "cooldown" and "throbbing" or "cooldown"
    self.start_time = time.now()
    self.section_time = self.state == "cooldown" and self.opts.cooldown_time
      or self.opts.throb_time
  end

  self.cb(icon)
  vim.defer_fn(function()
    self:_run()
  end, self.opts.tick_time)
end

function Throbber:start()
  self.start_time = time.now()
  self.section_time = self.opts.throb_time
  self.state = "throbbing"
  self:_run()
end

function Throbber:stop()
  self.state = "stopped"
end

Throbber._icons = throb_icons

return Throbber