2020-02-10 19:40:52 +03:00
|
|
|
package spinner
|
|
|
|
|
|
|
|
import (
|
2021-12-01 19:33:48 +03:00
|
|
|
"sync"
|
2020-02-10 19:40:52 +03:00
|
|
|
"time"
|
|
|
|
|
2020-05-26 02:57:58 +03:00
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
2021-04-12 22:54:56 +03:00
|
|
|
"github.com/charmbracelet/lipgloss"
|
2020-02-10 19:40:52 +03:00
|
|
|
)
|
|
|
|
|
2021-12-01 19:33:48 +03:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-07-15 01:21:48 +03:00
|
|
|
// Spinner is a set of frames used in animating the spinner.
|
2020-11-11 19:48:47 +03:00
|
|
|
type Spinner struct {
|
|
|
|
Frames []string
|
|
|
|
FPS time.Duration
|
|
|
|
}
|
2020-07-15 01:21:48 +03:00
|
|
|
|
2021-04-13 02:33:16 +03:00
|
|
|
// Some spinners to choose from. You could also make your own.
|
2020-02-10 19:40:52 +03:00
|
|
|
var (
|
2020-11-11 19:48:47 +03:00
|
|
|
Line = Spinner{
|
|
|
|
Frames: []string{"|", "/", "-", "\\"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 10, //nolint:gomnd
|
2020-11-11 19:48:47 +03:00
|
|
|
}
|
|
|
|
Dot = Spinner{
|
|
|
|
Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 10, //nolint:gomnd
|
2020-11-11 19:48:47 +03:00
|
|
|
}
|
2020-11-11 19:59:06 +03:00
|
|
|
MiniDot = Spinner{
|
|
|
|
Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 12, //nolint:gomnd
|
2020-11-11 19:59:06 +03:00
|
|
|
}
|
2020-11-11 19:48:47 +03:00
|
|
|
Jump = Spinner{
|
|
|
|
Frames: []string{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 10, //nolint:gomnd
|
2020-11-11 19:48:47 +03:00
|
|
|
}
|
2020-11-11 20:06:03 +03:00
|
|
|
Pulse = Spinner{
|
2020-11-11 20:05:41 +03:00
|
|
|
Frames: []string{"█", "▓", "▒", "░"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 8, //nolint:gomnd
|
2020-11-11 20:05:41 +03:00
|
|
|
}
|
2020-11-11 20:19:37 +03:00
|
|
|
Points = Spinner{
|
|
|
|
Frames: []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 7, //nolint:gomnd
|
2020-11-11 20:19:37 +03:00
|
|
|
}
|
2020-11-11 19:48:47 +03:00
|
|
|
Globe = Spinner{
|
2020-11-11 22:19:54 +03:00
|
|
|
Frames: []string{"🌍", "🌎", "🌏"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 4, //nolint:gomnd
|
2020-11-11 19:48:47 +03:00
|
|
|
}
|
|
|
|
Moon = Spinner{
|
2020-11-11 22:19:54 +03:00
|
|
|
Frames: []string{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 8, //nolint:gomnd
|
2020-11-11 19:48:47 +03:00
|
|
|
}
|
|
|
|
Monkey = Spinner{
|
2020-11-11 22:19:54 +03:00
|
|
|
Frames: []string{"🙈", "🙉", "🙊"},
|
2021-03-12 03:43:13 +03:00
|
|
|
FPS: time.Second / 3, //nolint:gomnd
|
2020-11-11 19:48:47 +03:00
|
|
|
}
|
2020-02-10 19:40:52 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Model contains the state for the spinner. Use NewModel to create new models
|
|
|
|
// rather than using Model as a struct literal.
|
|
|
|
type Model struct {
|
2020-11-11 19:48:47 +03:00
|
|
|
// Spinner settings to use. See type Spinner.
|
|
|
|
Spinner Spinner
|
2020-05-26 02:57:58 +03:00
|
|
|
|
2021-04-12 22:54:56 +03:00
|
|
|
// 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
|
2020-03-27 21:10:09 +03:00
|
|
|
|
2022-04-12 16:40:40 +03:00
|
|
|
frame int
|
|
|
|
id int
|
|
|
|
tag int
|
2020-08-25 03:03:54 +03:00
|
|
|
}
|
|
|
|
|
2022-01-10 22:46:40 +03:00
|
|
|
// ID returns the spinner's unique ID.
|
|
|
|
func (m Model) ID() int {
|
|
|
|
return m.id
|
|
|
|
}
|
|
|
|
|
2022-01-11 02:27:33 +03:00
|
|
|
// New returns a model with default values.
|
|
|
|
func New() Model {
|
2021-12-01 19:33:48 +03:00
|
|
|
return Model{
|
|
|
|
Spinner: Line,
|
|
|
|
id: nextID(),
|
|
|
|
}
|
2020-02-10 19:40:52 +03:00
|
|
|
}
|
|
|
|
|
2022-01-11 02:27:33 +03:00
|
|
|
// NewModel returns a model with default values.
|
|
|
|
//
|
|
|
|
// Deprecated. Use New instead.
|
|
|
|
var NewModel = New
|
|
|
|
|
2020-05-26 02:57:58 +03:00
|
|
|
// TickMsg indicates that the timer has ticked and we should render a frame.
|
2020-08-25 03:03:54 +03:00
|
|
|
type TickMsg struct {
|
2020-08-25 04:09:28 +03:00
|
|
|
Time time.Time
|
2020-12-11 20:00:07 +03:00
|
|
|
tag int
|
2022-01-10 22:46:40 +03:00
|
|
|
ID int
|
2020-08-25 03:03:54 +03:00
|
|
|
}
|
2020-02-10 19:40:52 +03:00
|
|
|
|
2020-05-26 02:57:58 +03:00
|
|
|
// 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.
|
2020-10-27 23:36:29 +03:00
|
|
|
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
2020-12-11 20:00:07 +03:00
|
|
|
switch msg := msg.(type) {
|
2020-10-27 23:36:29 +03:00
|
|
|
case TickMsg:
|
2021-12-01 19:33:48 +03:00
|
|
|
// If an ID is set, and the ID doesn't belong to this spinner, reject
|
|
|
|
// the message.
|
2022-01-10 22:46:40 +03:00
|
|
|
if msg.ID > 0 && msg.ID != m.id {
|
2021-12-01 19:33:48 +03:00
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
2020-12-11 20:00:07 +03:00
|
|
|
// 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
|
2021-06-01 23:30:33 +03:00
|
|
|
// thus spinning too fast.
|
2020-12-11 20:00:07 +03:00
|
|
|
if msg.tag > 0 && msg.tag != m.tag {
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
2020-06-22 21:49:21 +03:00
|
|
|
m.frame++
|
2020-11-11 19:48:47 +03:00
|
|
|
if m.frame >= len(m.Spinner.Frames) {
|
2020-06-22 21:49:21 +03:00
|
|
|
m.frame = 0
|
|
|
|
}
|
2020-12-11 20:00:07 +03:00
|
|
|
|
|
|
|
m.tag++
|
2021-12-01 19:33:48 +03:00
|
|
|
return m, m.tick(m.id, m.tag)
|
2020-10-27 23:36:29 +03:00
|
|
|
default:
|
|
|
|
return m, nil
|
2020-02-10 19:40:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 02:57:58 +03:00
|
|
|
// View renders the model's view.
|
2020-10-27 23:36:29 +03:00
|
|
|
func (m Model) View() string {
|
2020-11-11 19:48:47 +03:00
|
|
|
if m.frame >= len(m.Spinner.Frames) {
|
|
|
|
return "(error)"
|
2020-02-10 19:40:52 +03:00
|
|
|
}
|
2020-03-27 21:10:09 +03:00
|
|
|
|
2022-02-01 20:14:49 +03:00
|
|
|
return m.Style.Render(m.Spinner.Frames[m.frame])
|
2020-02-10 19:40:52 +03:00
|
|
|
}
|
|
|
|
|
2020-12-11 20:00:07 +03:00
|
|
|
// Tick is the command used to advance the spinner one frame. Use this command
|
|
|
|
// to effectively start the spinner.
|
2022-01-10 22:13:28 +03:00
|
|
|
func (m Model) Tick() tea.Msg {
|
|
|
|
return TickMsg{
|
2022-01-10 22:46:40 +03:00
|
|
|
// The time at which the tick occurred.
|
2022-01-10 22:13:28 +03:00
|
|
|
Time: time.Now(),
|
2022-01-10 22:46:40 +03:00
|
|
|
|
|
|
|
// 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,
|
2022-01-10 22:13:28 +03:00
|
|
|
}
|
2020-10-27 23:36:29 +03:00
|
|
|
}
|
|
|
|
|
2021-12-01 19:33:48 +03:00
|
|
|
func (m Model) tick(id, tag int) tea.Cmd {
|
2020-11-11 19:48:47 +03:00
|
|
|
return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg {
|
2020-08-25 03:03:54 +03:00
|
|
|
return TickMsg{
|
2020-08-25 04:09:28 +03:00
|
|
|
Time: t,
|
2022-01-10 22:46:40 +03:00
|
|
|
ID: id,
|
2020-12-11 20:00:07 +03:00
|
|
|
tag: tag,
|
2020-08-25 03:03:54 +03:00
|
|
|
}
|
2020-06-22 21:49:21 +03:00
|
|
|
})
|
2020-02-10 19:40:52 +03:00
|
|
|
}
|
2022-01-10 22:13:28 +03:00
|
|
|
|
|
|
|
// 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()}
|
|
|
|
}
|