package mpb import ( "io" "github.com/acarl005/stripansi" "github.com/mattn/go-runewidth" "github.com/vbauerster/mpb/v7/decor" "github.com/vbauerster/mpb/v7/internal" ) const ( iLbound = iota iRbound iFiller iRefiller iPadding components ) // BarStyleComposer interface. type BarStyleComposer interface { BarFillerBuilder Lbound(string) BarStyleComposer Rbound(string) BarStyleComposer Filler(string) BarStyleComposer Refiller(string) BarStyleComposer Padding(string) BarStyleComposer TipOnComplete(string) BarStyleComposer Tip(frames ...string) BarStyleComposer Reverse() BarStyleComposer } type bFiller struct { rev bool components [components]*component tip struct { count uint onComplete *component frames []*component } } type component struct { width int bytes []byte } type barStyle struct { lbound string rbound string filler string refiller string padding string tipOnComplete string tipFrames []string rev bool } // BarStyle constructs default bar style which can be altered via // BarStyleComposer interface. func BarStyle() BarStyleComposer { return &barStyle{ lbound: "[", rbound: "]", filler: "=", refiller: "+", padding: "-", tipFrames: []string{">"}, } } func (s *barStyle) Lbound(bound string) BarStyleComposer { s.lbound = bound return s } func (s *barStyle) Rbound(bound string) BarStyleComposer { s.rbound = bound return s } func (s *barStyle) Filler(filler string) BarStyleComposer { s.filler = filler return s } func (s *barStyle) Refiller(refiller string) BarStyleComposer { s.refiller = refiller return s } func (s *barStyle) Padding(padding string) BarStyleComposer { s.padding = padding return s } func (s *barStyle) TipOnComplete(tip string) BarStyleComposer { s.tipOnComplete = tip return s } func (s *barStyle) Tip(frames ...string) BarStyleComposer { if len(frames) != 0 { s.tipFrames = append(s.tipFrames[:0], frames...) } return s } func (s *barStyle) Reverse() BarStyleComposer { s.rev = true return s } func (s *barStyle) Build() BarFiller { bf := &bFiller{rev: s.rev} bf.components[iLbound] = &component{ width: runewidth.StringWidth(stripansi.Strip(s.lbound)), bytes: []byte(s.lbound), } bf.components[iRbound] = &component{ width: runewidth.StringWidth(stripansi.Strip(s.rbound)), bytes: []byte(s.rbound), } bf.components[iFiller] = &component{ width: runewidth.StringWidth(stripansi.Strip(s.filler)), bytes: []byte(s.filler), } bf.components[iRefiller] = &component{ width: runewidth.StringWidth(stripansi.Strip(s.refiller)), bytes: []byte(s.refiller), } bf.components[iPadding] = &component{ width: runewidth.StringWidth(stripansi.Strip(s.padding)), bytes: []byte(s.padding), } bf.tip.onComplete = &component{ width: runewidth.StringWidth(stripansi.Strip(s.tipOnComplete)), bytes: []byte(s.tipOnComplete), } bf.tip.frames = make([]*component, len(s.tipFrames)) for i, t := range s.tipFrames { bf.tip.frames[i] = &component{ width: runewidth.StringWidth(stripansi.Strip(t)), bytes: []byte(t), } } return bf } func (s *bFiller) Fill(w io.Writer, width int, stat decor.Statistics) { width = internal.CheckRequestedWidth(width, stat.AvailableWidth) brackets := s.components[iLbound].width + s.components[iRbound].width // don't count brackets as progress width -= brackets if width < 0 { return } mustWrite(w, s.components[iLbound].bytes) defer mustWrite(w, s.components[iRbound].bytes) if width == 0 { return } var filling [][]byte var padding [][]byte var tip *component var filled int var refWidth int curWidth := int(internal.PercentageRound(stat.Total, stat.Current, uint(width))) if stat.Current >= stat.Total { tip = s.tip.onComplete } else { tip = s.tip.frames[s.tip.count%uint(len(s.tip.frames))] } if curWidth > 0 { filling = append(filling, tip.bytes) filled += tip.width s.tip.count++ } if stat.Refill > 0 { refWidth = int(internal.PercentageRound(stat.Total, stat.Refill, uint(width))) curWidth -= refWidth refWidth += curWidth } for filled < curWidth { if curWidth-filled >= s.components[iFiller].width { filling = append(filling, s.components[iFiller].bytes) if s.components[iFiller].width == 0 { break } filled += s.components[iFiller].width } else { filling = append(filling, []byte("…")) filled++ } } for filled < refWidth { if refWidth-filled >= s.components[iRefiller].width { filling = append(filling, s.components[iRefiller].bytes) if s.components[iRefiller].width == 0 { break } filled += s.components[iRefiller].width } else { filling = append(filling, []byte("…")) filled++ } } padWidth := width - filled for padWidth > 0 { if padWidth >= s.components[iPadding].width { padding = append(padding, s.components[iPadding].bytes) if s.components[iPadding].width == 0 { break } padWidth -= s.components[iPadding].width } else { padding = append(padding, []byte("…")) padWidth-- } } if s.rev { flush(w, padding, filling) } else { flush(w, filling, padding) } } func flush(w io.Writer, filling, padding [][]byte) { for i := len(filling) - 1; i >= 0; i-- { mustWrite(w, filling[i]) } for i := 0; i < len(padding); i++ { mustWrite(w, padding[i]) } } func mustWrite(w io.Writer, p []byte) { _, err := w.Write(p) if err != nil { panic(err) } }