mirror of
https://github.com/Maks1mS/bubbles.git
synced 2024-12-23 22:32:59 +03:00
Progress can now animate itself
This commit is contained in:
parent
09a4cf419d
commit
20ead8fb7d
1
go.mod
1
go.mod
@ -6,6 +6,7 @@ require (
|
||||
github.com/atotto/clipboard v0.1.2
|
||||
github.com/charmbracelet/bubbletea v0.13.1
|
||||
github.com/charmbracelet/lipgloss v0.1.2
|
||||
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-runewidth v0.0.12
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68
|
||||
|
2
go.sum
2
go.sum
@ -6,6 +6,8 @@ github.com/charmbracelet/lipgloss v0.1.2 h1:D+LUMg34W7n2pkuMrevKVxT7HXqnoRHm7Ioo
|
||||
github.com/charmbracelet/lipgloss v0.1.2/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg=
|
||||
github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
|
||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
||||
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 h1:VRIbnDWRmAh5yBdz+J6yFMF5vso1It6vn+WmM/5l7MA=
|
||||
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776/go.mod h1:9wvnDu3YOfxzWM9Cst40msBF1C2UdQgDv962oTxSuMs=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
|
@ -3,13 +3,36 @@ package progress
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/fogleman/ease"
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
"github.com/muesli/reflow/ansi"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
const defaultWidth = 40
|
||||
// Internal ID management. Used during animating to assure that frame messages
|
||||
// can only be received by progress components that sent them.
|
||||
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
|
||||
}
|
||||
|
||||
const (
|
||||
defaultWidth = 40
|
||||
defaultFPS = time.Second / 60
|
||||
defaultTransitionDuration = time.Millisecond * 350
|
||||
)
|
||||
|
||||
var color func(string) termenv.Color = termenv.ColorProfile().Color
|
||||
|
||||
@ -19,6 +42,7 @@ var color func(string) termenv.Color = termenv.ColorProfile().Color
|
||||
// WithRamp("#ff0000", "#0000ff"),
|
||||
// WithoutPercentage(),
|
||||
// )
|
||||
//
|
||||
type Option func(*Model) error
|
||||
|
||||
// WithDefaultGradient sets a gradient fill with default colors.
|
||||
@ -74,25 +98,43 @@ func WithWidth(w int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// FrameMsg indicates that an animation step should occur.
|
||||
type FrameMsg struct {
|
||||
id int
|
||||
}
|
||||
|
||||
// Model stores values we'll use when rendering the progress bar.
|
||||
type Model struct {
|
||||
// The internal identifier for this model.
|
||||
id int
|
||||
|
||||
// Total width of the progress bar, including percentage, if set.
|
||||
Width int
|
||||
|
||||
// "Filled" sections of the progress bar
|
||||
// "Filled" sections of the progress bar.
|
||||
Full rune
|
||||
FullColor string
|
||||
|
||||
// "Empty" sections of progress bar
|
||||
// "Empty" sections of progress bar.
|
||||
Empty rune
|
||||
EmptyColor string
|
||||
|
||||
// Settings for rendering the numeric percentage
|
||||
// Settings for rendering the numeric percentage.
|
||||
ShowPercentage bool
|
||||
PercentFormat string // a fmt string for a float
|
||||
PercentageStyle *termenv.Style
|
||||
|
||||
// Animation options.
|
||||
FPS time.Duration
|
||||
TransitionDuration time.Duration
|
||||
|
||||
// Values for the internal animation state.
|
||||
progress float64
|
||||
startPercent float64
|
||||
endPercent float64
|
||||
transitionStart time.Time
|
||||
|
||||
// Gradient settings
|
||||
useRamp bool
|
||||
rampColorA colorful.Color
|
||||
rampColorB colorful.Color
|
||||
@ -104,28 +146,37 @@ type Model struct {
|
||||
}
|
||||
|
||||
// NewModel returns a model with default values.
|
||||
func NewModel(opts ...Option) (*Model, error) {
|
||||
m := &Model{
|
||||
Width: defaultWidth,
|
||||
Full: '█',
|
||||
FullColor: "#7571F9",
|
||||
Empty: '░',
|
||||
EmptyColor: "#606060",
|
||||
ShowPercentage: true,
|
||||
PercentFormat: " %3.0f%%",
|
||||
func NewModel(opts ...Option) (Model, error) {
|
||||
m := Model{
|
||||
id: nextID(),
|
||||
Width: defaultWidth,
|
||||
Full: '█',
|
||||
FullColor: "#7571F9",
|
||||
Empty: '░',
|
||||
EmptyColor: "#606060",
|
||||
ShowPercentage: true,
|
||||
PercentFormat: " %3.0f%%",
|
||||
FPS: defaultFPS,
|
||||
TransitionDuration: defaultTransitionDuration,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(m); err != nil {
|
||||
return nil, err
|
||||
if err := opt(&m); err != nil {
|
||||
return Model{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// View renders the progress bar as a given percentage.
|
||||
func (m Model) View(percent float64) string {
|
||||
// View renders the an animated progress bar in its current state. To render
|
||||
// a static progress bar based on your own calculations use ViewAs instead.
|
||||
func (m Model) View() string {
|
||||
return m.ViewAs(m.progress)
|
||||
}
|
||||
|
||||
// ViewAs renders the progress bar with a given percentage.
|
||||
func (m Model) ViewAs(percent float64) string {
|
||||
b := strings.Builder{}
|
||||
if m.ShowPercentage {
|
||||
percentage := fmt.Sprintf(m.PercentFormat, percent*100) //nolint:gomnd
|
||||
@ -140,6 +191,48 @@ func (m Model) View(percent float64) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case FrameMsg:
|
||||
if msg.id != m.id {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
elapsed := time.Since(m.transitionStart)
|
||||
totalDuration := m.transitionStart.
|
||||
Add(m.TransitionDuration).
|
||||
Sub(m.transitionStart)
|
||||
|
||||
f := func(d time.Duration) float64 { return float64(int64(d)) }
|
||||
|
||||
segmentProgress := f(elapsed) / f(totalDuration)
|
||||
if segmentProgress >= 1.0 {
|
||||
m.progress = m.endPercent
|
||||
return m, nil
|
||||
}
|
||||
|
||||
segmentSize := m.endPercent - m.startPercent
|
||||
m.progress = ease.OutQuad(segmentProgress)*segmentSize + m.startPercent
|
||||
return m, m.nextFrame()
|
||||
|
||||
default:
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) SetPercent(p float64) tea.Cmd {
|
||||
m.startPercent = m.endPercent
|
||||
m.endPercent = p
|
||||
m.transitionStart = time.Now()
|
||||
return m.nextFrame()
|
||||
}
|
||||
|
||||
func (m Model) nextFrame() tea.Cmd {
|
||||
return tea.Tick(m.FPS, func(time.Time) tea.Msg {
|
||||
return FrameMsg{m.id}
|
||||
})
|
||||
}
|
||||
|
||||
func (m Model) bar(b *strings.Builder, percent float64, textWidth int) {
|
||||
var (
|
||||
tw = m.Width - textWidth // total width
|
||||
|
Loading…
Reference in New Issue
Block a user