vbauerster / mpb

multi progress bar for Go cli applications
The Unlicense
2.29k stars 123 forks source link

Add Marquee decorator for long message scrolling #126

Open DeathKing opened 1 year ago

DeathKing commented 1 year ago

2023-03-22-231235

Add Marquee decorator that scrolls text from right to left, it is useful when displaying long messages. The codes are not carefully written, modifications are welcomed.

vbauerster commented 1 year ago

Implementation assumes that (string width = bytes width), therefore it's going to fail with non Latin strings. Try it for example with Россия великая страна! string to see what I mean.

DeathKing commented 1 year ago

@vbauerster Da, I know what you mean, I noticed that issue too. After opened this PR and some investigation of mpb source code, I found the go-runewidth library that you used may be the solution.

I implement this feature in my project like this (https://github.com/DeathKing/pico/blob/master/cmd/pdf2image/bar.go#L14-L28):

func Marquee(textGetter func() string, ws uint, wcc ...decor.WC) decor.Decorator {
    var count uint
    f := func(s decor.Statistics) string {
        text := textGetter()
        runes := []rune(text)

        msg := string(runes[int(count)%len(runes):])
        count++

        return runewidth.FillRight(
            runewidth.Truncate(msg, int(ws), ""),
            int(ws))
    }
    return decor.Any(f, wcc...)
}

https://github.com/vbauerster/mpb/assets/895809/95bf73d0-7628-49b2-881a-18d78ed0d7dd

For this specific implementation, the API requires a getter function to obtain the text for display, which may not suitable for most case, a string parameter should be used instead as I suppose.

I'd like to know your opinion about this feature.

RikaCelery commented 12 months ago

Try this?🤔 It works well for me.

func Marquee(t string, ws int, divider string, wcc ...decor.WC) decor.Decorator {
    var count int
    var f = func(s decor.Statistics) string {
        length := runewidth.StringWidth(t + divider)
        if runewidth.StringWidth(t) < ws {
            return runewidth.FillRight(t, ws)
        }
        text := t + divider
        var msg string

        if count-ws > length {
            msg = TruncateLeft(Truncate(text, count+ws), ws)
        } else {
            msg = TruncateLeft(Truncate(text, count+ws), count)
        }
        if count+ws > length {
            msg += Truncate(text, count+ws-length)
        }
        count++
        if count+ws >= len(t)+len(divider)-1 {
            count = 0
        }
        return runewidth.FillRight(msg, ws)
    }
    return decor.Any(f, wcc...)
}

func Truncate(s string, size int) string {
    return runewidth.Truncate(s, size, "")
}
func TruncateLeft(s string, size int) string {
    return runewidth.TruncateLeft(s, size, "")
}