bubbles/progress/progress.go

174 lines
4.1 KiB
Go
Raw Normal View History

2020-11-04 16:42:44 +03:00
package progress
import (
2020-11-18 22:27:41 +03:00
"fmt"
2020-11-04 16:42:44 +03:00
"strings"
"github.com/lucasb-eyer/go-colorful"
2020-11-18 22:27:41 +03:00
"github.com/muesli/reflow/ansi"
2020-11-04 16:42:44 +03:00
"github.com/muesli/termenv"
)
2020-11-18 23:59:10 +03:00
var color func(string) termenv.Color = termenv.ColorProfile().Color
// Option is used to set options in NewModel. For example:
//
// progress := NewModel(
// WithRamp("#ff0000", "#0000ff"),
// WithoutPercentage(),
// )
type Option func(*Model)
// WithDefaultRamp sets a gradient fill with default colors.
func WithDefaultRamp() Option {
return WithRamp("#00dbde", "#fc00ff")
}
// WithRamp sets a gradient fill blending between two colors.
func WithRamp(colorA, colorB string) Option {
return func(m *Model) {
m.setRamp(colorA, colorB, false)
}
}
// WithDefaultScaledRamp sets a gradient with default colors, and scales the
// gradient to fit the filled portion of the ramp.
func WithDefaultScaledRamp() Option {
return WithScaledRamp("#00dbde", "#fc00ff")
}
// WithScaledRamp scales the gradient to fit the width of the filled portion of
// the progress bar.
func WithScaledRamp(colorA, colorB string) Option {
return func(m *Model) {
m.setRamp(colorA, colorB, true)
}
}
// WithSoildFill sets the progress to use a solid fill with the given color.
func WithSolidFill(color string) Option {
return func(m *Model) {
m.FullColor = color
m.useRamp = false
}
}
2020-11-04 16:42:44 +03:00
// WithoutPercentage hides the numeric percentage.
func WithoutPercentage() Option {
return func(m *Model) {
m.ShowPercentage = false
}
}
// WithWidth sets the initial width of the progress bar. Note that you can also
// set the width via the Width property, which can come in handy if you're
// waiting for a tea.WindowSizeMsg.
func WithWidth(w int) Option {
return func(m *Model) {
m.Width = w
}
}
// Model stores values we'll use when rendering the progress bar.
type Model struct {
2020-11-04 16:42:44 +03:00
// Total width of the progress bar, including percentage, if set.
2020-11-05 14:41:53 +03:00
Width int
2020-11-04 16:42:44 +03:00
// "Filled" sections of the progress bar
Full rune
FullColor string
2020-11-04 16:42:44 +03:00
// "Empty" sections of progress bar
Empty rune
EmptyColor string
2020-11-04 16:42:44 +03:00
// Settings for rendering the numeric percentage
ShowPercentage bool
PercentFormat string // a fmt string for a float
2020-11-18 22:27:41 +03:00
useRamp bool
rampColorA colorful.Color
rampColorB colorful.Color
// When true, we scale the gradient to fit the width of the filled section
// of the progress bar. When false, the width of the gradient will be set
// to the full width of the progress bar.
scaleRamp bool
2020-11-04 16:42:44 +03:00
}
// NewModel returns a model with default values.
func NewModel(opts ...Option) *Model {
m := &Model{
Width: 40,
Full: '█',
FullColor: "#7571F9",
Empty: '░',
EmptyColor: "#606060",
ShowPercentage: true,
PercentFormat: " %3.0f%%",
2020-11-04 16:42:44 +03:00
}
for _, opt := range opts {
opt(m)
}
return m
2020-11-04 16:42:44 +03:00
}
// view renders the progress bar as a given percentage.
2020-11-18 22:27:41 +03:00
func (m Model) View(percent float64) string {
b := strings.Builder{}
if m.ShowPercentage {
2020-11-18 22:27:41 +03:00
s := fmt.Sprintf(m.PercentFormat, percent*100)
w := ansi.PrintableRuneWidth(s)
m.bar(&b, percent, w)
b.WriteString(s)
} else {
m.bar(&b, percent, 0)
2020-11-18 22:27:41 +03:00
}
return b.String()
2020-11-18 22:27:41 +03:00
}
func (m Model) bar(b *strings.Builder, percent float64, textWidth int) {
var (
tw = m.Width - textWidth // total width
fw = int(float64(tw) * percent) // filled width
p float64
)
if m.useRamp {
// Gradient fill
for i := 0; i < fw; i++ {
if m.scaleRamp {
p = float64(i) / float64(fw)
} else {
p = float64(i) / float64(tw)
}
c := m.rampColorA.BlendLuv(m.rampColorB, p).Hex()
b.WriteString(termenv.
String(string(m.Full)).
Foreground(color(c)).
String(),
)
2020-11-04 16:42:44 +03:00
}
} else {
// Solid fill
s := termenv.String(string(m.Full)).Foreground(color(m.FullColor)).String()
b.WriteString(strings.Repeat(s, fw))
2020-11-04 16:42:44 +03:00
}
// Empty fill
e := termenv.String(string(m.Empty)).Foreground(color(m.EmptyColor)).String()
b.WriteString(strings.Repeat(e, tw-fw))
2020-11-04 16:42:44 +03:00
}
func (m *Model) setRamp(colorA, colorB string, scaled bool) {
a, _ := colorful.Hex(colorA)
b, _ := colorful.Hex(colorB)
m.useRamp = true
m.scaleRamp = scaled
m.rampColorA = a
m.rampColorB = b
}