mirror of
https://github.com/Maks1mS/bubbles.git
synced 2024-12-25 07:04:37 +03:00
193 lines
4.5 KiB
Go
193 lines
4.5 KiB
Go
package progress
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/lucasb-eyer/go-colorful"
|
|
"github.com/muesli/reflow/ansi"
|
|
"github.com/muesli/termenv"
|
|
)
|
|
|
|
const defaultWidth = 40
|
|
|
|
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) error
|
|
|
|
// WithDefaultGradient sets a gradient fill with default colors.
|
|
func WithDefaultGradient() Option {
|
|
return WithGradient("#5A56E0", "#EE6FF8")
|
|
}
|
|
|
|
// WithGradient sets a gradient fill blending between two colors.
|
|
func WithGradient(colorA, colorB string) Option {
|
|
return func(m *Model) error {
|
|
return m.setRamp(colorA, colorB, false)
|
|
}
|
|
}
|
|
|
|
// WithDefaultScaledGradient sets a gradient with default colors, and scales the
|
|
// gradient to fit the filled portion of the ramp.
|
|
func WithDefaultScaledGradient() Option {
|
|
return WithScaledGradient("#5A56E0", "#EE6FF8")
|
|
}
|
|
|
|
// WithScaledGradient scales the gradient to fit the width of the filled portion of
|
|
// the progress bar.
|
|
func WithScaledGradient(colorA, colorB string) Option {
|
|
return func(m *Model) error {
|
|
return m.setRamp(colorA, colorB, true)
|
|
}
|
|
}
|
|
|
|
// WithSolidFill sets the progress to use a solid fill with the given color.
|
|
func WithSolidFill(color string) Option {
|
|
return func(m *Model) error {
|
|
m.FullColor = color
|
|
m.useRamp = false
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithoutPercentage hides the numeric percentage.
|
|
func WithoutPercentage() Option {
|
|
return func(m *Model) error {
|
|
m.ShowPercentage = false
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// 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) error {
|
|
m.Width = w
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Model stores values we'll use when rendering the progress bar.
|
|
type Model struct {
|
|
|
|
// Total width of the progress bar, including percentage, if set.
|
|
Width int
|
|
|
|
// "Filled" sections of the progress bar
|
|
Full rune
|
|
FullColor string
|
|
|
|
// "Empty" sections of progress bar
|
|
Empty rune
|
|
EmptyColor string
|
|
|
|
// Settings for rendering the numeric percentage
|
|
ShowPercentage bool
|
|
PercentFormat string // a fmt string for a float
|
|
PercentageStyle *termenv.Style
|
|
|
|
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
|
|
}
|
|
|
|
// 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%%",
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
if err := opt(m); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// View renders the progress bar as a given percentage.
|
|
func (m Model) View(percent float64) string {
|
|
b := strings.Builder{}
|
|
if m.ShowPercentage {
|
|
percentage := fmt.Sprintf(m.PercentFormat, percent*100)
|
|
if m.PercentageStyle != nil {
|
|
percentage = m.PercentageStyle.Styled(percentage)
|
|
}
|
|
m.bar(&b, percent, ansi.PrintableRuneWidth(percentage))
|
|
b.WriteString(percentage)
|
|
} else {
|
|
m.bar(&b, percent, 0)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
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(),
|
|
)
|
|
}
|
|
} else {
|
|
// Solid fill
|
|
s := termenv.String(string(m.Full)).Foreground(color(m.FullColor)).String()
|
|
b.WriteString(strings.Repeat(s, fw))
|
|
}
|
|
|
|
// Empty fill
|
|
e := termenv.String(string(m.Empty)).Foreground(color(m.EmptyColor)).String()
|
|
b.WriteString(strings.Repeat(e, tw-fw))
|
|
}
|
|
|
|
func (m *Model) setRamp(colorA, colorB string, scaled bool) error {
|
|
a, err := colorful.Hex(colorA)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := colorful.Hex(colorB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m.useRamp = true
|
|
m.scaleRamp = scaled
|
|
m.rampColorA = a
|
|
m.rampColorB = b
|
|
return nil
|
|
}
|