@@ -13,7 +13,6 @@ import (
1313 "github.com/charmbracelet/harmonica"
1414 "github.com/charmbracelet/lipgloss/v2"
1515 "github.com/charmbracelet/x/ansi"
16- "github.com/lucasb-eyer/go-colorful"
1716)
1817
1918// Internal ID management. Used during animating to assure that frame messages
@@ -34,35 +33,96 @@ const (
3433// Option is used to set options in New. For example:
3534//
3635// progress := New(
37- // WithRamp("#ff0000", "#0000ff"),
36+ // WithBlend(
37+ // lipgloss.Color("#ff0000"),
38+ // lipgloss.Color("#0000ff"),
39+ // ),
3840// WithoutPercentage(),
3941// )
4042type Option func (* Model )
4143
44+ // WithDefaultBlend sets a default blend of colors.
45+ func WithDefaultBlend () Option {
46+ return WithBlend (
47+ lipgloss .Color ("#5A56E0" ),
48+ lipgloss .Color ("#EE6FF8" ),
49+ )
50+ }
51+
4252// WithDefaultGradient sets a gradient fill with default colors.
53+ //
54+ // Deprecated: Use [WithDefaultBlend] instead.
4355func WithDefaultGradient () Option {
44- return WithGradient ( "#5A56E0" , "#EE6FF8" )
56+ return WithDefaultBlend ( )
4557}
4658
47- // WithGradient sets a gradient fill blending between two colors.
48- func WithGradient (colorA , colorB string ) Option {
59+ // WithBlend uses a blend of multiple color stops to fill the progress bar. If the
60+ // blend has only 1 color, it will be treated as a solid fill. If the blend has 0
61+ // colors, it will be treated as a default blend.
62+ func WithBlend (blend ... color.Color ) Option {
63+ if len (blend ) == 1 {
64+ return WithSolidFill (blend [0 ])
65+ }
66+ if len (blend ) == 0 {
67+ return WithDefaultBlend ()
68+ }
4969 return func (m * Model ) {
50- m .setRamp (colorA , colorB , false )
70+ m .setRamp (blend , false )
5171 }
5272}
5373
74+ // WithGradient sets a gradient fill blending between two colors.
75+ //
76+ // Deprecated: Use [WithBlend] instead.
77+ func WithGradient (colorA , colorB string ) Option {
78+ return WithBlend (
79+ lipgloss .Color (colorA ),
80+ lipgloss .Color (colorB ),
81+ )
82+ }
83+
5484// WithDefaultScaledGradient sets a gradient with default colors, and scales the
5585// gradient to fit the filled portion of the ramp.
86+ //
87+ // Deprecated: Use [WithDefaultScaledBlend] instead.
5688func WithDefaultScaledGradient () Option {
57- return WithScaledGradient ("#5A56E0" , "#EE6FF8" )
89+ return WithDefaultScaledBlend ()
90+ }
91+
92+ // WithDefaultScaledBlend sets a default blend of colors, and scales the blend
93+ // to fit the width of the filled portion of the progress bar.
94+ func WithDefaultScaledBlend () Option {
95+ return WithScaledBlend (
96+ lipgloss .Color ("#5A56E0" ),
97+ lipgloss .Color ("#EE6FF8" ),
98+ )
99+ }
100+
101+ // WithScaledBlend scales the blend of colors to fit the width of the filled portion
102+ // of the progress bar. If the blend has only 1 color, it will be treated as a
103+ // solid fill. If the blend has 0 colors, it will be treated as a default scaled
104+ // blend.
105+ func WithScaledBlend (blend ... color.Color ) Option {
106+ if len (blend ) == 1 {
107+ return WithSolidFill (blend [0 ])
108+ }
109+ if len (blend ) == 0 {
110+ return WithDefaultScaledBlend ()
111+ }
112+ return func (m * Model ) {
113+ m .setRamp (blend , true )
114+ }
58115}
59116
60117// WithScaledGradient scales the gradient to fit the width of the filled portion of
61118// the progress bar.
119+ //
120+ // Deprecated: Use [WithScaledBlend] instead.
62121func WithScaledGradient (colorA , colorB string ) Option {
63- return func (m * Model ) {
64- m .setRamp (colorA , colorB , true )
65- }
122+ return WithScaledBlend (
123+ lipgloss .Color (colorA ),
124+ lipgloss .Color (colorB ),
125+ )
66126}
67127
68128// WithSolidFill sets the progress to use a solid fill with the given color.
@@ -73,7 +133,8 @@ func WithSolidFill(color color.Color) Option {
73133 }
74134}
75135
76- // WithFillCharacters sets the characters used to construct the full and empty components of the progress bar.
136+ // WithFillCharacters sets the characters used to construct the full and empty
137+ // components of the progress bar.
77138func WithFillCharacters (full rune , empty rune ) Option {
78139 return func (m * Model ) {
79140 m .Full = full
@@ -93,7 +154,7 @@ func WithoutPercentage() Option {
93154// waiting for a tea.WindowSizeMsg.
94155func WithWidth (w int ) Option {
95156 return func (m * Model ) {
96- m .width = w
157+ m .SetWidth ( w )
97158 }
98159}
99160
@@ -148,9 +209,8 @@ type Model struct {
148209 velocity float64
149210
150211 // Gradient settings
151- useRamp bool
152- rampColorA colorful.Color
153- rampColorB colorful.Color
212+ useRamp bool
213+ blend []color.Color
154214
155215 // When true, we scale the gradient to fit the width of the filled section
156216 // of the progress bar. When false, the width of the gradient will be set
@@ -288,35 +348,31 @@ func (m Model) barView(b *strings.Builder, percent float64, textWidth int) {
288348 var (
289349 tw = max (0 , m .width - textWidth ) // total width
290350 fw = int (math .Round ((float64 (tw ) * percent ))) // filled width
291- p float64
292351 )
293352
294353 fw = max (0 , min (tw , fw ))
295354
296355 if m .useRamp {
297- // Gradient fill
356+ var blend []color.Color
357+ if m .scaleRamp {
358+ blend = lipgloss .Blend1D (fw , m .blend ... )
359+ } else {
360+ blend = lipgloss .Blend1D (tw , m .blend ... )
361+ }
362+ // Blend fill.
298363 for i := range fw {
299- if fw == 1 {
300- // this is up for debate: in a gradient of width=1, should the
301- // single character rendered be the first color, the last color
302- // or exactly 50% in between? I opted for 50%
303- p = 0.5
304- } else if m .scaleRamp {
305- p = float64 (i ) / float64 (fw - 1 )
306- } else {
307- p = float64 (i ) / float64 (tw - 1 )
308- }
309- c := m .rampColorA .BlendLuv (m .rampColorB , p )
310- b .WriteString (lipgloss .NewStyle ().Foreground (c ).Render (string (m .Full )))
364+ b .WriteString (lipgloss .NewStyle ().
365+ Foreground (blend [i ]).
366+ Render (string (m .Full )))
311367 }
312368 } else {
313- // Solid fill
369+ // Solid fill.
314370 b .WriteString (lipgloss .NewStyle ().
315371 Foreground (m .FullColor ).
316372 Render (strings .Repeat (string (m .Full ), fw )))
317373 }
318374
319- // Empty fill
375+ // Empty fill.
320376 n := max (0 , tw - fw )
321377 b .WriteString (lipgloss .NewStyle ().
322378 Foreground (m .EmptyColor ).
@@ -333,20 +389,14 @@ func (m Model) percentageView(percent float64) string {
333389 return percentage
334390}
335391
336- func (m * Model ) setRamp (colorA , colorB string , scaled bool ) {
337- // In the event of an error colors here will default to black. For
338- // usability's sake, and because such an error is only cosmetic, we're
339- // ignoring the error.
340- a , _ := colorful .Hex (colorA )
341- b , _ := colorful .Hex (colorB )
342-
392+ func (m * Model ) setRamp (blend []color.Color , scaled bool ) {
343393 m .useRamp = true
344394 m .scaleRamp = scaled
345- m .rampColorA = a
346- m .rampColorB = b
395+ m .blend = blend
347396}
348397
349- // IsAnimating returns false if the progress bar reached equilibrium and is no longer animating.
398+ // IsAnimating returns false if the progress bar reached equilibrium and is no
399+ // longer animating.
350400func (m * Model ) IsAnimating () bool {
351401 dist := math .Abs (m .percentShown - m .targetPercent )
352402 return ! (dist < 0.001 && m .velocity < 0.01 )
0 commit comments