mirror of https://github.com/mum4k/termdash.git
Gauge widget now supports full-width runes.
This commit is contained in:
parent
8a1bb34b05
commit
945b48f4a3
|
@ -108,7 +108,7 @@ func main() {
|
|||
go playGauge(ctx, noProgress, 5, 250*time.Millisecond, playTypePercent)
|
||||
withLabel := gauge.New(
|
||||
gauge.Height(3),
|
||||
gauge.TextLabel("with text label and no border"),
|
||||
gauge.TextLabel("你好,世界! text label and no border"),
|
||||
gauge.Color(cell.ColorRed),
|
||||
gauge.FilledTextColor(cell.ColorBlack),
|
||||
gauge.EmptyTextColor(cell.ColorYellow),
|
||||
|
|
|
@ -21,8 +21,8 @@ import (
|
|||
"fmt"
|
||||
"image"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/area"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
|
@ -30,7 +30,6 @@ import (
|
|||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
"golang.org/x/exp/utf8string"
|
||||
)
|
||||
|
||||
// progressType indicates how was the current progress provided by the caller.
|
||||
|
@ -184,64 +183,62 @@ func (g *Gauge) gaugeText() string {
|
|||
}
|
||||
|
||||
// drawText draws the text enumerating the progress and the text label.
|
||||
func (g *Gauge) drawText(cvs *canvas.Canvas) error {
|
||||
func (g *Gauge) drawText(cvs *canvas.Canvas, progress image.Rectangle) error {
|
||||
text := g.gaugeText()
|
||||
if text == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ar := g.usable(cvs)
|
||||
textStart, err := align.Text(ar, text, g.opts.hTextAlign, g.opts.vTextAlign)
|
||||
trimmed, err := draw.TrimText(text, ar.Dx(), draw.OverrunModeThreeDot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
textEndX := textStart.X + utf8.RuneCountInString(text)
|
||||
if textEndX >= ar.Max.X { // The text will be trimmed.
|
||||
textEndX = ar.Max.X - 1
|
||||
|
||||
cur, err := align.Text(ar, trimmed, g.opts.hTextAlign, g.opts.vTextAlign)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gaugeEndX := g.width(ar)
|
||||
|
||||
switch {
|
||||
case gaugeEndX < textStart.X:
|
||||
// The text entirely falls outside of the drawn gauge.
|
||||
return draw.Text(cvs, text, textStart,
|
||||
draw.TextOverrunMode(draw.OverrunModeThreeDot),
|
||||
draw.TextCellOpts(cell.FgColor(g.opts.emptyTextColor)),
|
||||
draw.TextMaxX(ar.Max.X),
|
||||
)
|
||||
|
||||
case gaugeEndX >= textEndX:
|
||||
// The text entirely falls inside of the drawn gauge.
|
||||
return draw.Text(cvs, text, textStart,
|
||||
draw.TextOverrunMode(draw.OverrunModeThreeDot),
|
||||
draw.TextCellOpts(cell.FgColor(g.opts.filledTextColor)),
|
||||
draw.TextMaxX(ar.Max.X),
|
||||
)
|
||||
|
||||
default:
|
||||
// Part of the text falls inside of the drawn gauge and part outside.
|
||||
utfText := utf8string.NewString(text)
|
||||
insideCount := ar.Min.X + gaugeEndX - textStart.X
|
||||
insideText := utfText.Slice(0, insideCount)
|
||||
outsideText := utfText.Slice(insideCount, utfText.RuneCount())
|
||||
|
||||
if err := draw.Text(cvs, insideText, textStart,
|
||||
draw.TextOverrunMode(draw.OverrunModeTrim),
|
||||
draw.TextCellOpts(cell.FgColor(g.opts.filledTextColor)),
|
||||
); err != nil {
|
||||
return err
|
||||
for _, r := range trimmed {
|
||||
if !cur.In(ar) {
|
||||
break
|
||||
}
|
||||
|
||||
outsideStart := image.Point{textStart.X + insideCount, textStart.Y}
|
||||
if outsideStart.In(ar) {
|
||||
if err := draw.Text(cvs, outsideText, outsideStart,
|
||||
draw.TextOverrunMode(draw.OverrunModeThreeDot),
|
||||
draw.TextCellOpts(cell.FgColor(g.opts.emptyTextColor)),
|
||||
draw.TextMaxX(ar.Max.X),
|
||||
next := image.Point{cur.X + 1, cur.Y}
|
||||
rw := runewidth.RuneWidth(r)
|
||||
// If the current rune is full-width and only one of its cells falls
|
||||
// within the filled area of the gauge, extend the gauge by one cell to
|
||||
// fully cover the full-width rune.
|
||||
if rw == 2 && next.In(ar) && cur.In(progress) && !next.In(progress) {
|
||||
fixup := image.Rect(
|
||||
next.X,
|
||||
ar.Min.Y,
|
||||
next.X+1,
|
||||
ar.Max.Y,
|
||||
)
|
||||
if err := draw.Rectangle(cvs, fixup,
|
||||
draw.RectChar(g.opts.gaugeChar),
|
||||
draw.RectCellOpts(cell.BgColor(g.opts.color)),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var cellOpts []cell.Option
|
||||
if cur.In(progress) {
|
||||
cellOpts = append(cellOpts, cell.FgColor(g.opts.filledTextColor))
|
||||
} else {
|
||||
cellOpts = append(cellOpts, cell.FgColor(g.opts.emptyTextColor))
|
||||
}
|
||||
|
||||
cells, err := cvs.SetCell(cur, r, cellOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cur = image.Point{cur.X + cells, cur.Y}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -278,7 +275,7 @@ func (g *Gauge) Draw(cvs *canvas.Canvas) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return g.drawText(cvs)
|
||||
return g.drawText(cvs, progress)
|
||||
}
|
||||
|
||||
// Keyboard input isn't supported on the Gauge widget.
|
||||
|
|
|
@ -368,7 +368,7 @@ func TestGauge(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
desc: "gauge with text label",
|
||||
desc: "gauge with text label, half-width runes",
|
||||
gauge: New(
|
||||
Char('o'),
|
||||
HideTextProgress(),
|
||||
|
@ -391,6 +391,84 @@ func TestGauge(t *testing.T) {
|
|||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "gauge with text label, full-width runes",
|
||||
gauge: New(
|
||||
Char('o'),
|
||||
HideTextProgress(),
|
||||
TextLabel("你好"),
|
||||
),
|
||||
percent: &percentCall{p: 100},
|
||||
canvas: image.Rect(0, 0, 10, 3),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 0, 10, 3),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
|
||||
)
|
||||
testdraw.MustText(c, "(你好)", image.Point{2, 1},
|
||||
draw.TextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "gauge with text label, full-width runes, gauge falls on rune boundary",
|
||||
gauge: New(
|
||||
Char('o'),
|
||||
HideTextProgress(),
|
||||
TextLabel("你好"),
|
||||
),
|
||||
percent: &percentCall{p: 50},
|
||||
canvas: image.Rect(0, 0, 10, 3),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 0, 5, 3),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
|
||||
)
|
||||
testdraw.MustText(c, "(你", image.Point{2, 1},
|
||||
draw.TextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
)
|
||||
testdraw.MustText(c, "好)", image.Point{5, 1},
|
||||
draw.TextCellOpts(cell.FgColor(cell.ColorDefault)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "gauge with text label, full-width runes, gauge extended to cover full rune",
|
||||
gauge: New(
|
||||
Char('o'),
|
||||
HideTextProgress(),
|
||||
TextLabel("你好"),
|
||||
),
|
||||
percent: &percentCall{p: 40},
|
||||
canvas: image.Rect(0, 0, 10, 3),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 0, 5, 3),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(cell.ColorGreen)),
|
||||
)
|
||||
testdraw.MustText(c, "(你", image.Point{2, 1},
|
||||
draw.TextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
)
|
||||
testdraw.MustText(c, "好)", image.Point{5, 1},
|
||||
draw.TextCellOpts(cell.FgColor(cell.ColorDefault)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "gauge with progress text and text label",
|
||||
gauge: New(
|
||||
|
|
|
@ -49,6 +49,7 @@ type options struct {
|
|||
// newOptions returns options with the default values set.
|
||||
func newOptions() *options {
|
||||
return &options{
|
||||
gaugeChar: DefaultChar,
|
||||
hTextAlign: DefaultHorizontalTextAlign,
|
||||
vTextAlign: DefaultVerticalTextAlign,
|
||||
color: DefaultColor,
|
||||
|
|
Loading…
Reference in New Issue