/
spinner.go
187 lines (163 loc) Β· 4.5 KB
/
spinner.go
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package spinner
import (
"sync"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// Internal ID management for text inputs. Necessary for blink integrity when
// multiple text inputs are involved.
var (
lastID int
idMtx sync.Mutex
)
// Return the next ID we should use on the Model.
func nextID() int {
idMtx.Lock()
defer idMtx.Unlock()
lastID++
return lastID
}
// Spinner is a set of frames used in animating the spinner.
type Spinner struct {
Frames []string
FPS time.Duration
}
// Some spinners to choose from. You could also make your own.
var (
Line = Spinner{
Frames: []string{"|", "/", "-", "\\"},
FPS: time.Second / 10, //nolint:gomnd
}
Dot = Spinner{
Frames: []string{"β£Ύ ", "β£½ ", "β£» ", "β’Ώ ", "β‘Ώ ", "β£ ", "β£― ", "β£· "},
FPS: time.Second / 10, //nolint:gomnd
}
MiniDot = Spinner{
Frames: []string{"β ", "β ", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ", "β "},
FPS: time.Second / 12, //nolint:gomnd
}
Jump = Spinner{
Frames: []string{"β’", "β’", "β’", "β‘", "β‘", "β‘", "β‘ "},
FPS: time.Second / 10, //nolint:gomnd
}
Pulse = Spinner{
Frames: []string{"β", "β", "β", "β"},
FPS: time.Second / 8, //nolint:gomnd
}
Points = Spinner{
Frames: []string{"βββ", "βββ", "βββ", "βββ"},
FPS: time.Second / 7, //nolint:gomnd
}
Globe = Spinner{
Frames: []string{"π", "π", "π"},
FPS: time.Second / 4, //nolint:gomnd
}
Moon = Spinner{
Frames: []string{"π", "π", "π", "π", "π", "π", "π", "π"},
FPS: time.Second / 8, //nolint:gomnd
}
Monkey = Spinner{
Frames: []string{"π", "π", "π"},
FPS: time.Second / 3, //nolint:gomnd
}
)
// Model contains the state for the spinner. Use NewModel to create new models
// rather than using Model as a struct literal.
type Model struct {
// Spinner settings to use. See type Spinner.
Spinner Spinner
// Style sets the styling for the spinner. Most of the time you'll just
// want foreground and background coloring, and potentially some padding.
//
// For an introduction to styling with Lip Gloss see:
// https://github.com/charmbracelet/lipgloss
Style lipgloss.Style
frame int
id int
tag int
}
// ID returns the spinner's unique ID.
func (m Model) ID() int {
return m.id
}
// New returns a model with default values.
func New() Model {
return Model{
Spinner: Line,
id: nextID(),
}
}
// NewModel returns a model with default values.
//
// Deprecated. Use New instead.
var NewModel = New
// TickMsg indicates that the timer has ticked and we should render a frame.
type TickMsg struct {
Time time.Time
tag int
ID int
}
// Update is the Tea update function. This will advance the spinner one frame
// every time it's called, regardless the message passed, so be sure the logic
// is setup so as not to call this Update needlessly.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case TickMsg:
// If an ID is set, and the ID doesn't belong to this spinner, reject
// the message.
if msg.ID > 0 && msg.ID != m.id {
return m, nil
}
// If a tag is set, and it's not the one we expect, reject the message.
// This prevents the spinner from receiving too many messages and
// thus spinning too fast.
if msg.tag > 0 && msg.tag != m.tag {
return m, nil
}
m.frame++
if m.frame >= len(m.Spinner.Frames) {
m.frame = 0
}
m.tag++
return m, m.tick(m.id, m.tag)
default:
return m, nil
}
}
// View renders the model's view.
func (m Model) View() string {
if m.frame >= len(m.Spinner.Frames) {
return "(error)"
}
return m.Style.Render(m.Spinner.Frames[m.frame])
}
// Tick is the command used to advance the spinner one frame. Use this command
// to effectively start the spinner.
func (m Model) Tick() tea.Msg {
return TickMsg{
// The time at which the tick occurred.
Time: time.Now(),
// The ID of the spinner that this message belongs to. This can be
// helpful when routing messages, however bear in mind that spinners
// will ignore messages that don't contain ID by default.
ID: m.id,
tag: m.tag,
}
}
func (m Model) tick(id, tag int) tea.Cmd {
return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg {
return TickMsg{
Time: t,
ID: id,
tag: tag,
}
})
}
// Tick is the command used to advance the spinner one frame. Use this command
// to effectively start the spinner.
//
// This method is deprecated. Use Model.Tick instead.
func Tick() tea.Msg {
return TickMsg{Time: time.Now()}
}