mirror of
https://github.com/Maks1mS/bubbles.git
synced 2024-12-24 22:54:39 +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/atotto/clipboard v0.1.2
|
||||||
github.com/charmbracelet/bubbletea v0.13.1
|
github.com/charmbracelet/bubbletea v0.13.1
|
||||||
github.com/charmbracelet/lipgloss v0.1.2
|
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/lucasb-eyer/go-colorful v1.2.0
|
||||||
github.com/mattn/go-runewidth v0.0.12
|
github.com/mattn/go-runewidth v0.0.12
|
||||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68
|
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/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 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
|
||||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
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/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.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
@ -3,13 +3,36 @@ package progress
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/fogleman/ease"
|
||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
"github.com/muesli/reflow/ansi"
|
"github.com/muesli/reflow/ansi"
|
||||||
"github.com/muesli/termenv"
|
"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
|
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"),
|
// WithRamp("#ff0000", "#0000ff"),
|
||||||
// WithoutPercentage(),
|
// WithoutPercentage(),
|
||||||
// )
|
// )
|
||||||
|
//
|
||||||
type Option func(*Model) error
|
type Option func(*Model) error
|
||||||
|
|
||||||
// WithDefaultGradient sets a gradient fill with default colors.
|
// 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.
|
// Model stores values we'll use when rendering the progress bar.
|
||||||
type Model struct {
|
type Model struct {
|
||||||
|
// The internal identifier for this model.
|
||||||
|
id int
|
||||||
|
|
||||||
// Total width of the progress bar, including percentage, if set.
|
// Total width of the progress bar, including percentage, if set.
|
||||||
Width int
|
Width int
|
||||||
|
|
||||||
// "Filled" sections of the progress bar
|
// "Filled" sections of the progress bar.
|
||||||
Full rune
|
Full rune
|
||||||
FullColor string
|
FullColor string
|
||||||
|
|
||||||
// "Empty" sections of progress bar
|
// "Empty" sections of progress bar.
|
||||||
Empty rune
|
Empty rune
|
||||||
EmptyColor string
|
EmptyColor string
|
||||||
|
|
||||||
// Settings for rendering the numeric percentage
|
// Settings for rendering the numeric percentage.
|
||||||
ShowPercentage bool
|
ShowPercentage bool
|
||||||
PercentFormat string // a fmt string for a float
|
PercentFormat string // a fmt string for a float
|
||||||
PercentageStyle *termenv.Style
|
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
|
useRamp bool
|
||||||
rampColorA colorful.Color
|
rampColorA colorful.Color
|
||||||
rampColorB colorful.Color
|
rampColorB colorful.Color
|
||||||
@ -104,28 +146,37 @@ type Model struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewModel returns a model with default values.
|
// NewModel returns a model with default values.
|
||||||
func NewModel(opts ...Option) (*Model, error) {
|
func NewModel(opts ...Option) (Model, error) {
|
||||||
m := &Model{
|
m := Model{
|
||||||
Width: defaultWidth,
|
id: nextID(),
|
||||||
Full: '█',
|
Width: defaultWidth,
|
||||||
FullColor: "#7571F9",
|
Full: '█',
|
||||||
Empty: '░',
|
FullColor: "#7571F9",
|
||||||
EmptyColor: "#606060",
|
Empty: '░',
|
||||||
ShowPercentage: true,
|
EmptyColor: "#606060",
|
||||||
PercentFormat: " %3.0f%%",
|
ShowPercentage: true,
|
||||||
|
PercentFormat: " %3.0f%%",
|
||||||
|
FPS: defaultFPS,
|
||||||
|
TransitionDuration: defaultTransitionDuration,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
if err := opt(m); err != nil {
|
if err := opt(&m); err != nil {
|
||||||
return nil, err
|
return Model{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// View renders the progress bar as a given percentage.
|
// View renders the an animated progress bar in its current state. To render
|
||||||
func (m Model) View(percent float64) string {
|
// 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{}
|
b := strings.Builder{}
|
||||||
if m.ShowPercentage {
|
if m.ShowPercentage {
|
||||||
percentage := fmt.Sprintf(m.PercentFormat, percent*100) //nolint:gomnd
|
percentage := fmt.Sprintf(m.PercentFormat, percent*100) //nolint:gomnd
|
||||||
@ -140,6 +191,48 @@ func (m Model) View(percent float64) string {
|
|||||||
return b.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) {
|
func (m Model) bar(b *strings.Builder, percent float64, textWidth int) {
|
||||||
var (
|
var (
|
||||||
tw = m.Width - textWidth // total width
|
tw = m.Width - textWidth // total width
|
||||||
|
Loading…
Reference in New Issue
Block a user