bubbles/spinner/spinner.go

198 lines
6.9 KiB
Go
Raw Normal View History

2020-02-10 19:40:52 +03:00
package spinner
import (
"time"
tea "github.com/charmbracelet/bubbletea"
2020-03-27 21:10:09 +03:00
"github.com/muesli/termenv"
2020-02-10 19:40:52 +03:00
)
const defaultFPS = time.Second / 10
// Spinner is a set of frames used in animating the spinner.
type Spinner = []string
2020-02-10 19:40:52 +03:00
var (
// Some spinners to choose from. You could also make your own.
2020-11-04 16:37:49 +03:00
Line = Spinner{"|", "/", "-", "\\"}
Dot = Spinner{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "}
Globe = Spinner{"🌍 ", "🌎 ", "🌏 "}
Moon = Spinner{"🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "}
Monkey = Spinner{"🙈 ", "🙈 ", "🙉 ", "🙊 "}
Jump = Spinner{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"}
Bit8 = Spinner{
"", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⡀", "⡁", "⡂", "⡃", "⡄", "⡅", "⡆", "⡇",
"⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏", "⡈", "⡉", "⡊", "⡋", "⡌", "⡍", "⡎", "⡏",
"⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⡐", "⡑", "⡒", "⡓", "⡔", "⡕", "⡖", "⡗",
"⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟", "⡘", "⡙", "⡚", "⡛", "⡜", "⡝", "⡞", "⡟",
"⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⡠", "⡡", "⡢", "⡣", "⡤", "⡥", "⡦", "⡧",
"⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯", "⡨", "⡩", "⡪", "⡫", "⡬", "⡭", "⡮", "⡯",
"⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⡰", "⡱", "⡲", "⡳", "⡴", "⡵", "⡶", "⡷",
"⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿", "⡸", "⡹", "⡺", "⡻", "⡼", "⡽", "⡾", "⡿",
"⢀", "⢁", "⢂", "⢃", "⢄", "⢅", "⢆", "⢇", "⣀", "⣁", "⣂", "⣃", "⣄", "⣅", "⣆", "⣇",
"⢈", "⢉", "⢊", "⢋", "⢌", "⢍", "⢎", "⢏", "⣈", "⣉", "⣊", "⣋", "⣌", "⣍", "⣎", "⣏",
"⢐", "⢑", "⢒", "⢓", "⢔", "⢕", "⢖", "⢗", "⣐", "⣑", "⣒", "⣓", "⣔", "⣕", "⣖", "⣗",
"⢘", "⢙", "⢚", "⢛", "⢜", "⢝", "⢞", "⢟", "⣘", "⣙", "⣚", "⣛", "⣜", "⣝", "⣞", "⣟",
"⢠", "⢡", "⢢", "⢣", "⢤", "⢥", "⢦", "⢧", "⣠", "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧",
"⢨", "⢩", "⢪", "⢫", "⢬", "⢭", "⢮", "⢯", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯",
"⢰", "⢱", "⢲", "⢳", "⢴", "⢵", "⢶", "⢷", "⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷",
"⢸", "⢹", "⢺", "⢻", "⢼", "⢽", "⢾", "⢿", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿"}
2020-02-10 19:51:08 +03:00
2020-03-27 21:10:09 +03:00
color = termenv.ColorProfile().Color
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 {
// Type is the set of frames to use. See Spinner.
Frames Spinner
// FPS is the speed at which the ticker should tick.
FPS time.Duration
// ForegroundColor sets the background color of the spinner. It can be a
// hex code or one of the 256 ANSI colors. If the terminal emulator can't
// doesn't support the color specified it will automatically degrade
// (per github.com/muesli/termenv).
2020-03-27 21:10:09 +03:00
ForegroundColor string
// BackgroundColor sets the background color of the spinner. It can be a
// hex code or one of the 256 ANSI colors. If the terminal emulator can't
// doesn't support the color specified it will automatically degrade
// (per github.com/muesli/termenv).
2020-03-27 21:10:09 +03:00
BackgroundColor string
// MinimumLifetime is the minimum amount of time the spinner can run. Any
// logic around this can be implemented in view that implements this
// spinner. If HideFor is set MinimumLifetime will be added on top of
// HideFor. In other words, if HideFor is 100ms and MinimumLifetime is
// 200ms then MinimumLifetime will expire after 300ms.
//
// MinimumLifetime is optional.
//
// This is considered experimental and may not appear in future versions of
// this library.
MinimumLifetime time.Duration
// HideFor can be used to wait to show the spinner until a certain amount
// of time has passed. This can be useful for preventing flicking when load
2020-10-02 20:03:04 +03:00
// times are very fast.
// Optional.
//
// This is considered experimental and may not appear in future versions of
// this library.
HideFor time.Duration
frame int
startTime time.Time
}
// Start resets resets the spinner start time. For use with MinimumLifetime and
// MinimumStartTime. Optional.
//
// This is considered experimental and may not appear in future versions of
// this library.
func (m *Model) Start() {
m.startTime = time.Now()
}
// hidden returns whether or not Model.HideFor is in effect.
func (m Model) hidden() bool {
if m.startTime.IsZero() {
return false
}
if m.HideFor == 0 {
return false
}
return m.startTime.Add(m.HideFor).After(time.Now())
}
// finished returns whether Model.MinimumLifetimeReached has been met.
func (m Model) finished() bool {
if m.startTime.IsZero() {
return true
}
if m.MinimumLifetime == 0 {
return true
}
return m.startTime.Add(m.HideFor).Add(m.MinimumLifetime).Before(time.Now())
}
// Visible returns whether or not the view should be rendered. Works in
// conjunction with Model.HideFor and Model.MinimumLifetimeReached. You should
// use this message directly to determine whether or not to render this view in
// the parent view and whether to continue sending spin messaging in the
// parent update function.
//
// Also note that using this function is optional and generally considered for
// advanced use only. Most of the time your application logic will determine
// whether or not this view should be used.
//
// This is considered experimental and may not appear in future versions of
// this library.
func (m Model) Visible() bool {
return !m.hidden() && !m.finished()
2020-02-10 19:40:52 +03:00
}
// NewModel returns a model with default values.
2020-02-10 19:40:52 +03:00
func NewModel() Model {
return Model{
Frames: Line,
FPS: defaultFPS,
2020-02-10 19:40:52 +03:00
}
}
// TickMsg indicates that the timer has ticked and we should render a frame.
type TickMsg struct {
Time time.Time
}
2020-02-10 19:40:52 +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.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg.(type) {
case TickMsg:
m.frame++
if m.frame >= len(m.Frames) {
m.frame = 0
}
return m, m.tick()
default:
return m, nil
2020-02-10 19:40:52 +03:00
}
}
// View renders the model's view.
func (m Model) View() string {
if m.frame >= len(m.Frames) {
return "error"
2020-02-10 19:40:52 +03:00
}
2020-03-27 21:10:09 +03:00
frame := m.Frames[m.frame]
2020-03-27 21:10:09 +03:00
if m.ForegroundColor != "" || m.BackgroundColor != "" {
2020-03-27 21:10:09 +03:00
return termenv.
String(frame).
Foreground(color(m.ForegroundColor)).
Background(color(m.BackgroundColor)).
2020-03-27 21:10:09 +03:00
String()
}
return frame
2020-02-10 19:40:52 +03:00
}
// Tick is the command used to advance the spinner one frame.
func Tick() tea.Msg {
return TickMsg{Time: time.Now()}
}
func (m Model) tick() tea.Cmd {
return tea.Tick(m.FPS, func(t time.Time) tea.Msg {
return TickMsg{
Time: t,
}
})
2020-02-10 19:40:52 +03:00
}