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  }