go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/cli/progress/renderer.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package progress 5 6 import ( 7 "errors" 8 "fmt" 9 "strings" 10 11 "github.com/lucasb-eyer/go-colorful" 12 "github.com/muesli/ansi" 13 "github.com/muesli/termenv" 14 "go.mondoo.com/cnquery/cli/theme/colors" 15 ) 16 17 var color func(string) termenv.Color = colors.Profile.Color 18 19 // renderer stores values we'll use when rendering the progress bar. 20 type renderer struct { 21 // Total width of the progress bar, including percentage, if set. 22 Width int 23 24 // "Filled" sections of the progress bar 25 Full rune 26 FullColor string 27 28 // "Empty" sections of progress bar 29 Empty rune 30 EmptyColor string 31 32 // Settings for rendering the numeric percentage 33 ShowPercentage bool 34 PercentFormat string // a fmt string for a float 35 PercentageStyle *termenv.Style 36 37 useRamp bool 38 rampColorA colorful.Color 39 rampColorB colorful.Color 40 41 // When true, we scale the gradient to fit the width of the filled section 42 // of the progress bar. When false, the width of the gradient will be set 43 // to the full width of the progress bar. 44 scaleRamp bool 45 } 46 47 // newRenderer returns a model with default values. 48 func newRenderer() (*renderer, error) { 49 m := &renderer{ 50 Width: 40, 51 Full: '█', 52 FullColor: "#7571F9", 53 Empty: '░', 54 EmptyColor: "#606060", 55 ShowPercentage: true, 56 PercentFormat: " %3.0f%%", 57 } 58 59 if err := m.setRamp("#5A56E0", "#EE6FF8", true); err != nil { 60 return nil, errors.New("default color setup failed, please report this issue!") 61 } 62 63 return m, nil 64 } 65 66 // View renders the progress bar as a given percentage. 67 func (m *renderer) View(percent float32) string { 68 b := strings.Builder{} 69 if m.ShowPercentage { 70 percentage := fmt.Sprintf(m.PercentFormat, percent*100) 71 if m.PercentageStyle != nil { 72 percentage = m.PercentageStyle.Styled(percentage) 73 } 74 m.bar(&b, percent, ansi.PrintableRuneWidth(percentage)) 75 b.WriteString(percentage) 76 } else { 77 m.bar(&b, percent, 0) 78 } 79 return b.String() 80 } 81 82 func (m *renderer) bar(b *strings.Builder, percent float32, textWidth int) { 83 var ( 84 tw = m.Width - textWidth // total width 85 fw = int(float32(tw) * percent) // filled width 86 p float64 87 ) 88 89 if m.useRamp { 90 // Gradient fill 91 for i := 0; i < fw; i++ { 92 if m.scaleRamp { 93 p = float64(i) / float64(fw) 94 } else { 95 p = float64(i) / float64(tw) 96 } 97 c := m.rampColorA.BlendLuv(m.rampColorB, p).Hex() 98 b.WriteString(termenv. 99 String(string(m.Full)). 100 Foreground(color(c)). 101 String(), 102 ) 103 } 104 } else { 105 // Solid fill 106 s := termenv.String(string(m.Full)).Foreground(color(m.FullColor)).String() 107 b.WriteString(strings.Repeat(s, fw)) 108 } 109 110 // Empty fill 111 e := termenv.String(string(m.Empty)).Foreground(color(m.EmptyColor)).String() 112 b.WriteString(strings.Repeat(e, tw-fw)) 113 } 114 115 func (m *renderer) setRamp(colorA, colorB string, scaled bool) error { 116 a, err := colorful.Hex(colorA) 117 if err != nil { 118 return err 119 } 120 121 b, err := colorful.Hex(colorB) 122 if err != nil { 123 return err 124 } 125 126 m.useRamp = true 127 m.scaleRamp = scaled 128 m.rampColorA = a 129 m.rampColorB = b 130 return nil 131 }