@@ -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,94 @@ 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.
5686func WithDefaultScaledGradient () Option {
57- return WithScaledGradient ("#5A56E0" , "#EE6FF8" )
87+ return WithDefaultScaledBlend ()
88+ }
89+
90+ // WithDefaultScaledBlend sets a default blend of colors, and scales the blend
91+ // to fit the width of the filled portion of the progress bar.
92+ func WithDefaultScaledBlend () Option {
93+ return WithScaledBlend (
94+ lipgloss .Color ("#5A56E0" ),
95+ lipgloss .Color ("#EE6FF8" ),
96+ )
97+ }
98+
99+ // WithScaledBlend scales the blend of colors to fit the width of the filled portion
100+ // of the progress bar. If the blend has only 1 color, it will be treated as a
101+ // solid fill. If the blend has 0 colors, it will be treated as a default scaled
102+ // blend.
103+ func WithScaledBlend (blend ... color.Color ) Option {
104+ if len (blend ) == 1 {
105+ return WithSolidFill (blend [0 ])
106+ }
107+ if len (blend ) == 0 {
108+ return WithDefaultScaledBlend ()
109+ }
110+ return func (m * Model ) {
111+ m .setRamp (blend , true )
112+ }
58113}
59114
60115// WithScaledGradient scales the gradient to fit the width of the filled portion of
61116// the progress bar.
117+ //
118+ // Deprecated: Use [WithScaledBlend] instead.
62119func WithScaledGradient (colorA , colorB string ) Option {
63- return func (m * Model ) {
64- m .setRamp (colorA , colorB , true )
65- }
120+ return WithScaledBlend (
121+ lipgloss .Color (colorA ),
122+ lipgloss .Color (colorB ),
123+ )
66124}
67125
68126// WithSolidFill sets the progress to use a solid fill with the given color.
@@ -73,7 +131,8 @@ func WithSolidFill(color color.Color) Option {
73131 }
74132}
75133
76- // WithFillCharacters sets the characters used to construct the full and empty components of the progress bar.
134+ // WithFillCharacters sets the characters used to construct the full and empty
135+ // components of the progress bar.
77136func WithFillCharacters (full rune , empty rune ) Option {
78137 return func (m * Model ) {
79138 m .Full = full
@@ -93,7 +152,7 @@ func WithoutPercentage() Option {
93152// waiting for a tea.WindowSizeMsg.
94153func WithWidth (w int ) Option {
95154 return func (m * Model ) {
96- m .width = w
155+ m .SetWidth ( w )
97156 }
98157}
99158
@@ -148,9 +207,8 @@ type Model struct {
148207 velocity float64
149208
150209 // Gradient settings
151- useRamp bool
152- rampColorA colorful.Color
153- rampColorB colorful.Color
210+ useRamp bool
211+ blend []color.Color
154212
155213 // When true, we scale the gradient to fit the width of the filled section
156214 // of the progress bar. When false, the width of the gradient will be set
@@ -288,35 +346,31 @@ func (m Model) barView(b *strings.Builder, percent float64, textWidth int) {
288346 var (
289347 tw = max (0 , m .width - textWidth ) // total width
290348 fw = int (math .Round ((float64 (tw ) * percent ))) // filled width
291- p float64
292349 )
293350
294351 fw = max (0 , min (tw , fw ))
295352
296353 if m .useRamp {
297- // Gradient fill
354+ var blend []color.Color
355+ if m .scaleRamp {
356+ blend = lipgloss .Blend1D (fw , m .blend ... )
357+ } else {
358+ blend = lipgloss .Blend1D (tw , m .blend ... )
359+ }
360+ // Blend fill.
298361 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 )))
362+ b .WriteString (lipgloss .NewStyle ().
363+ Foreground (blend [i ]).
364+ Render (string (m .Full )))
311365 }
312366 } else {
313- // Solid fill
367+ // Solid fill.
314368 b .WriteString (lipgloss .NewStyle ().
315369 Foreground (m .FullColor ).
316370 Render (strings .Repeat (string (m .Full ), fw )))
317371 }
318372
319- // Empty fill
373+ // Empty fill.
320374 n := max (0 , tw - fw )
321375 b .WriteString (lipgloss .NewStyle ().
322376 Foreground (m .EmptyColor ).
@@ -333,20 +387,14 @@ func (m Model) percentageView(percent float64) string {
333387 return percentage
334388}
335389
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-
390+ func (m * Model ) setRamp (blend []color.Color , scaled bool ) {
343391 m .useRamp = true
344392 m .scaleRamp = scaled
345- m .rampColorA = a
346- m .rampColorB = b
393+ m .blend = blend
347394}
348395
349- // IsAnimating returns false if the progress bar reached equilibrium and is no longer animating.
396+ // IsAnimating returns false if the progress bar reached equilibrium and is no
397+ // longer animating.
350398func (m * Model ) IsAnimating () bool {
351399 dist := math .Abs (m .percentShown - m .targetPercent )
352400 return ! (dist < 0.001 && m .velocity < 0.01 )
0 commit comments