diff --git a/_examples/reverseBar/main.go b/_examples/reverseBar/main.go new file mode 100644 index 0000000..f48b39a --- /dev/null +++ b/_examples/reverseBar/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v4" + "github.com/vbauerster/mpb/v4/decor" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func main() { + var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually + p := mpb.New(mpb.WithWaitGroup(&wg)) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + // reverse Bar#1 + mpb.BarOptOnCond(mpb.BarReverse(), func() bool { return i == 1 }), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + // simulating some work + go func() { + defer wg.Done() + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + start := time.Now() + time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) + // ewma based decorators require work duration measurement + bar.IncrBy(1, time.Since(start)) + } + }() + } + // Waiting for passed &wg and for all bars to complete and flush + p.Wait() +} diff --git a/bar_filler.go b/bar_filler.go index d26f4a9..f38665a 100644 --- a/bar_filler.go +++ b/bar_filler.go @@ -1,7 +1,6 @@ package mpb import ( - "bytes" "io" "unicode/utf8" @@ -15,14 +14,16 @@ rTip rEmpty rRight + rRevTip rRefill ) -var defaultBarStyle = "[=>-]+" +var defaultBarStyle = "[=>-]<+" type barFiller struct { - format [][]byte - rup int + format [][]byte + refillCount int + reverse bool } func newDefaultBarFiller() Filler { @@ -35,7 +36,7 @@ func (s *barFiller) setStyle(style string) { if !utf8.ValidString(style) { - style = defaultBarStyle + return } src := make([][]byte, 0, utf8.RuneCountInString(style)) for _, r := range style { @@ -44,41 +45,68 @@ copy(s.format, src) } +func (s *barFiller) setReverse() { + s.reverse = true +} + func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { - - b := s.format[rLeft] // don't count rLeft and rRight [brackets] width -= 2 - if width < 2 { - return - } else if width == 2 { - w.Write(append(b, s.format[rRight]...)) return } - cwidth := internal.Percentage(stat.Total, stat.Current, int64(width)) - - if s.rup > 0 { - rwidth := internal.Percentage(stat.Total, int64(s.rup), int64(width)) - b = append(b, bytes.Repeat(s.format[rRefill], int(rwidth))...) - rest := cwidth - rwidth - b = append(b, bytes.Repeat(s.format[rFill], int(rest))...) - } else { - b = append(b, bytes.Repeat(s.format[rFill], int(cwidth))...) + w.Write(s.format[rLeft]) + if width == 2 { + w.Write(s.format[rRight]) + return } - if cwidth < int64(width) && cwidth > 0 { - _, size := utf8.DecodeLastRune(b) - b = append(b[:len(b)-size], s.format[rTip]...) + bb := make([][]byte, width) + + cwidth := int(internal.Percentage(stat.Total, stat.Current, int64(width))) + + for i := 0; i < cwidth; i++ { + bb[i] = s.format[rFill] } - rest := int64(width) - cwidth - b = append(b, bytes.Repeat(s.format[rEmpty], int(rest))...) - w.Write(append(b, s.format[rRight]...)) + if s.refillCount > 0 { + var rwidth int + if s.refillCount > cwidth { + rwidth = cwidth + } else { + rwidth = int(internal.Percentage(stat.Total, int64(s.refillCount), int64(width))) + } + for i := 0; i < rwidth; i++ { + bb[i] = s.format[rRefill] + } + } + + if cwidth < width && cwidth > 0 { + if s.reverse { + bb[cwidth-1] = s.format[rRevTip] + } else { + bb[cwidth-1] = s.format[rTip] + } + } + + for i := cwidth; i < width; i++ { + bb[i] = s.format[rEmpty] + } + + if s.reverse { + for i, j := 0, len(bb)-1; i < j; i, j = i+1, j-1 { + bb[i], bb[j] = bb[j], bb[i] + } + } + + for i := 0; i < width; i++ { + w.Write(bb[i]) + } + w.Write(s.format[rRight]) } -func (s *barFiller) SetRefill(upto int) { - s.rup = upto +func (s *barFiller) SetRefill(count int) { + s.refillCount = count } diff --git a/bar_option.go b/bar_option.go index 35f7e44..86382b1 100644 --- a/bar_option.go +++ b/bar_option.go @@ -103,8 +103,23 @@ } } -// BarStyle sets custom bar style. -// Effective when Filler type is bar. +// BarStyle sets custom bar style, default one is "[=>-]<+". +// +// '[' left bracket rune +// +// '=' fill rune +// +// '>' tip rune +// +// '-' empty rune +// +// ']' right bracket rune +// +// '<' reverse tip rune, used when BarReverse option is set +// +// '+' refill rune, used when *Bar.SetRefill(int) is called +// +// It's ok to provide first five runes only, for example mpb.BarStyle("╢▌▌░╟") func BarStyle(style string) BarOption { chk := func(filler Filler) (interface{}, bool) { if style == "" { @@ -115,6 +130,18 @@ } cb := func(t interface{}) { t.(*barFiller).setStyle(style) + } + return MakeFillerTypeSpecificBarOption(chk, cb) +} + +// BarReverse reverse mode, bar will progress from right to left. +func BarReverse() BarOption { + chk := func(filler Filler) (interface{}, bool) { + t, ok := filler.(*barFiller) + return t, ok + } + cb := func(t interface{}) { + t.(*barFiller).setReverse() } return MakeFillerTypeSpecificBarOption(chk, cb) }