package mpb import ( "io" "github.com/acarl005/stripansi" "github.com/mattn/go-runewidth" "github.com/vbauerster/mpb/v8/decor" "github.com/vbauerster/mpb/v8/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 frames []*component onComplete *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, stat decor.Statistics) (err error) { width := internal.CheckRequestedWidth(stat.RequestedWidth, stat.AvailableWidth) // don't count brackets as progress width -= (s.components[iLbound].width + s.components[iRbound].width) if width < 0 { return nil } _, err = w.Write(s.components[iLbound].bytes) if err != nil { return err } if width == 0 { _, err = w.Write(s.components[iRbound].bytes) return err } 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.Completed { 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 { filling, padding = padding, filling } err = flush(w, filling, padding) if err != nil { return err } _, err = w.Write(s.components[iRbound].bytes) return err } func flush(w io.Writer, filling, padding [][]byte) error { for i := len(filling) - 1; i >= 0; i-- { _, err := w.Write(filling[i]) if err != nil { return err } } for i := 0; i < len(padding); i++ { _, err := w.Write(padding[i]) if err != nil { return err } } return nil }