mirror of https://github.com/mum4k/termdash.git
Rest of the Donut widget files.
This commit is contained in:
parent
cd48efc885
commit
7f133bf611
|
@ -58,3 +58,10 @@ func MustBrailleLine(bc *braille.Canvas, start, end image.Point, opts ...draw.Br
|
|||
panic(fmt.Sprintf("draw.BrailleLine => unexpected error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// MustBrailleCircle draws the braille circle or panics.
|
||||
func MustBrailleCircle(bc *braille.Canvas, mid image.Point, radius int, opts ...draw.BrailleCircleOption) {
|
||||
if err := draw.BrailleCircle(bc, mid, radius, opts...); err != nil {
|
||||
panic(fmt.Sprintf("draw.BrailleCircle => unexpected error: %v", err))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,12 @@ import (
|
|||
"image"
|
||||
"sync"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/canvas/braille"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/numbers"
|
||||
"github.com/mum4k/termdash/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
@ -135,12 +139,92 @@ func (d *Donut) Percent(p int, opts ...Option) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// progressText returns the textual representation of the current progress.
|
||||
func (d *Donut) progressText() string {
|
||||
switch d.pt {
|
||||
case progressTypePercent:
|
||||
return fmt.Sprintf("%d%%", d.current)
|
||||
case progressTypeAbsolute:
|
||||
return fmt.Sprintf("%d/%d", d.current, d.total)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// holeRadius calculates the radius of the "hole" in the donut.
|
||||
// Returns zero if no hole should be drawn.
|
||||
func (d *Donut) holeRadius(donutRadius int) int {
|
||||
r := int(numbers.Round(float64(donutRadius) / 100 * float64(d.opts.donutHolePercent)))
|
||||
if r < 2 { // Smallest possible circle radius.
|
||||
return 0
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// drawText draws the text label showing the progress.
|
||||
// The text is only drawn if the radius of the donut "hole" is large enough to
|
||||
// accommodate it.
|
||||
func (d *Donut) drawText(cvs *canvas.Canvas, mid image.Point, holeR int) error {
|
||||
cells, first := availableCells(mid, holeR)
|
||||
t := d.progressText()
|
||||
needCells := runewidth.StringWidth(t)
|
||||
if cells < needCells {
|
||||
return nil
|
||||
}
|
||||
|
||||
ar := image.Rect(first.X, first.Y, first.X+cells+2, first.Y+1)
|
||||
start, err := align.Text(ar, t, align.HorizontalCenter, align.VerticalMiddle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("align.Text => %v", err)
|
||||
}
|
||||
if err := draw.Text(cvs, t, start, draw.TextMaxX(start.X+needCells), draw.TextCellOpts(d.opts.textCellOpts...)); err != nil {
|
||||
return fmt.Errorf("draw.Text => %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Draw draws the Donut widget onto the canvas.
|
||||
// Implements widgetapi.Widget.Draw.
|
||||
func (d *Donut) Draw(cvs *canvas.Canvas) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
bc, err := braille.New(cvs.Area())
|
||||
if err != nil {
|
||||
return fmt.Errorf("braille.New => %v", err)
|
||||
}
|
||||
|
||||
startA, endA := startEndAngles(d.current, d.total, d.opts.startAngle, d.opts.direction)
|
||||
if startA == endA {
|
||||
// No progress recorded, so nothing to do.
|
||||
return nil
|
||||
}
|
||||
|
||||
mid, r := midAndRadius(bc.Area())
|
||||
if err := draw.BrailleCircle(bc, mid, r,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleArcOnly(startA, endA),
|
||||
draw.BrailleCircleCellOpts(d.opts.cellOpts...),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to draw the outer circle: %v", err)
|
||||
}
|
||||
|
||||
holeR := d.holeRadius(r)
|
||||
if holeR != 0 {
|
||||
if err := draw.BrailleCircle(bc, mid, holeR,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to draw the outer circle: %v", err)
|
||||
}
|
||||
}
|
||||
if err := bc.CopyTo(cvs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !d.opts.hideTextProgress {
|
||||
return d.drawText(cvs, mid, holeR)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -161,10 +245,8 @@ func (d *Donut) Options() widgetapi.Options {
|
|||
// This is adjusted for the inequality of the braille canvas.
|
||||
Ratio: image.Point{braille.RowMult, braille.ColMult},
|
||||
|
||||
// The smallest circle that "looks" like a circle on the canvas needs
|
||||
// to have a radius of two. We need at least three columns and two rows
|
||||
// of cells to display it.
|
||||
MinimumSize: image.Point{3, 2},
|
||||
// The smallest circle that "looks" like a circle on the canvas.
|
||||
MinimumSize: image.Point{3, 3},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
}
|
||||
|
|
|
@ -20,7 +20,13 @@ import (
|
|||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/canvas/braille/testbraille"
|
||||
"github.com/mum4k/termdash/canvas/testcanvas"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/draw/testdraw"
|
||||
"github.com/mum4k/termdash/terminal/faketerm"
|
||||
"github.com/mum4k/termdash/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
||||
|
@ -31,9 +37,99 @@ func TestDonut(t *testing.T) {
|
|||
update func(*Donut) error // update gets called before drawing of the widget.
|
||||
canvas image.Rectangle
|
||||
want func(size image.Point) *faketerm.Terminal
|
||||
wantNewErr bool
|
||||
wantUpdateErr bool // whether to expect an error on a call to the update function
|
||||
wantDrawErr bool
|
||||
}{
|
||||
{
|
||||
desc: "New fails on negative donut hole percent",
|
||||
opts: []Option{
|
||||
HolePercent(-1),
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
wantNewErr: true,
|
||||
},
|
||||
{
|
||||
desc: "New fails on too large donut hole percent",
|
||||
opts: []Option{
|
||||
HolePercent(101),
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
wantNewErr: true,
|
||||
},
|
||||
{
|
||||
desc: "New fails on too small start angle",
|
||||
opts: []Option{
|
||||
StartAngle(-1),
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
wantNewErr: true,
|
||||
},
|
||||
{
|
||||
desc: "New fails on too large start angle",
|
||||
opts: []Option{
|
||||
StartAngle(360),
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
wantNewErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Percent fails on too small start angle",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, StartAngle(-1))
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Percent fails on negative percent",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(-1)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Percent fails on value too large",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(101)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Absolute fails on too small start angle",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(100, 100, StartAngle(-1))
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Absolute fails on done to small",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(-1, 100)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Absolute fails on total to small",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(0, 0)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Absolute fails on done greater than total",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(2, 1)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "draws empty for no data points",
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
|
@ -41,13 +137,455 @@ func TestDonut(t *testing.T) {
|
|||
return faketerm.MustNew(size)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fails when canvas too small to draw a circle",
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
wantDrawErr: true,
|
||||
},
|
||||
{
|
||||
desc: "smallest valid donut, 100% progress",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100)
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{2, 5}, 2, draw.BrailleCircleFilled())
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "New sets donut options",
|
||||
opts: []Option{
|
||||
CellOpts(
|
||||
cell.FgColor(cell.ColorRed),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
),
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100)
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{2, 5}, 2,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleCellOpts(
|
||||
cell.FgColor(cell.ColorRed),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
),
|
||||
)
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Percent sets donut options",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100,
|
||||
CellOpts(
|
||||
cell.FgColor(cell.ColorRed),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
),
|
||||
)
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{2, 5}, 2,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleCellOpts(
|
||||
cell.FgColor(cell.ColorRed),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
),
|
||||
)
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Absolute sets donut options",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(100, 100,
|
||||
CellOpts(
|
||||
cell.FgColor(cell.ColorRed),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
),
|
||||
)
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{2, 5}, 2,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleCellOpts(
|
||||
cell.FgColor(cell.ColorRed),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
),
|
||||
)
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "smallest valid donut, 100 absolute",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(100, 100)
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{2, 5}, 2, draw.BrailleCircleFilled())
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "smallest valid donut with a hole",
|
||||
canvas: image.Rect(0, 0, 6, 6),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100)
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 2,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws a larger hole",
|
||||
canvas: image.Rect(0, 0, 6, 6),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, HolePercent(50))
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 3,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "hole as large as donut",
|
||||
canvas: image.Rect(0, 0, 6, 6),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, HolePercent(100), HideTextProgress())
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
bc := testbraille.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
|
||||
testbraille.MustApply(bc, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays 100% progress",
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, HolePercent(80))
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "100%", image.Point{2, 3})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "sets text cell options",
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, HolePercent(80), TextCellOpts(
|
||||
cell.FgColor(cell.ColorGreen),
|
||||
cell.BgColor(cell.ColorYellow),
|
||||
))
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "100%", image.Point{2, 3}, draw.TextCellOpts(
|
||||
cell.FgColor(cell.ColorGreen),
|
||||
cell.BgColor(cell.ColorYellow),
|
||||
))
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "shows text again when hidden previously",
|
||||
opts: []Option{
|
||||
HideTextProgress(),
|
||||
},
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, HolePercent(80), ShowTextProgress())
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "100%", image.Point{2, 3})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "hides text when requested",
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, HolePercent(80), HideTextProgress())
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "hides text when hole is too small",
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(100, HolePercent(50))
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 3,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays 1% progress",
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(1, HolePercent(80))
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleArcOnly(89, 90),
|
||||
)
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "1%", image.Point{3, 3})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays 25% progress, clockwise",
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(25, HolePercent(80), Clockwise())
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleArcOnly(0, 90),
|
||||
)
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "25%", image.Point{2, 3})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays 25% progress, counter-clockwise",
|
||||
canvas: image.Rect(0, 0, 7, 7),
|
||||
update: func(d *Donut) error {
|
||||
return d.Percent(25, HolePercent(80), CounterClockwise())
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleArcOnly(90, 180),
|
||||
)
|
||||
testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "25%", image.Point{2, 3})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays 10/10 absolute progress",
|
||||
canvas: image.Rect(0, 0, 8, 8),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(10, 10, HolePercent(80))
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{8, 17}, 7, draw.BrailleCircleFilled())
|
||||
testdraw.MustBrailleCircle(bc, image.Point{8, 17}, 6,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "10/10", image.Point{2, 4})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays 1/10 absolute progress",
|
||||
canvas: image.Rect(0, 0, 8, 8),
|
||||
update: func(d *Donut) error {
|
||||
return d.Absolute(1, 10, HolePercent(80))
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
bc := testbraille.MustNew(c.Area())
|
||||
|
||||
testdraw.MustBrailleCircle(bc, image.Point{8, 17}, 7,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleArcOnly(54, 90),
|
||||
)
|
||||
testdraw.MustBrailleCircle(bc, image.Point{8, 17}, 6,
|
||||
draw.BrailleCircleFilled(),
|
||||
draw.BrailleCircleClearPixels(),
|
||||
)
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testdraw.MustText(c, "1/10", image.Point{2, 4})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
d, err := New(tc.opts...)
|
||||
if (err != nil) != tc.wantNewErr {
|
||||
t.Errorf("New => unexpected error: %v, wantNewErr: %v", err, tc.wantNewErr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("New => unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := canvas.New(tc.canvas)
|
||||
|
@ -97,6 +635,26 @@ func TestDonut(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestKeyboard(t *testing.T) {
|
||||
d, err := New()
|
||||
if err != nil {
|
||||
t.Fatalf("New => unexpected error: %v", err)
|
||||
}
|
||||
if err := d.Keyboard(&terminalapi.Keyboard{}); err == nil {
|
||||
t.Errorf("Keyboard => got nil err, wanted one")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMouse(t *testing.T) {
|
||||
d, err := New()
|
||||
if err != nil {
|
||||
t.Fatalf("New => unexpected error: %v", err)
|
||||
}
|
||||
if err := d.Mouse(&terminalapi.Mouse{}); err == nil {
|
||||
t.Errorf("Mouse => got nil err, wanted one")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
d, err := New()
|
||||
if err != nil {
|
||||
|
@ -106,7 +664,7 @@ func TestOptions(t *testing.T) {
|
|||
got := d.Options()
|
||||
want := widgetapi.Options{
|
||||
Ratio: image.Point{4, 2},
|
||||
MinimumSize: image.Point{3, 2},
|
||||
MinimumSize: image.Point{3, 3},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/mum4k/termdash"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/container"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminal/termbox"
|
||||
|
@ -41,6 +42,7 @@ const (
|
|||
func playDonut(ctx context.Context, d *donut.Donut, step int, delay time.Duration, pt playType) {
|
||||
progress := 0
|
||||
mult := 1
|
||||
start := 0
|
||||
|
||||
ticker := time.NewTicker(delay)
|
||||
defer ticker.Stop()
|
||||
|
@ -49,20 +51,26 @@ func playDonut(ctx context.Context, d *donut.Donut, step int, delay time.Duratio
|
|||
case <-ticker.C:
|
||||
switch pt {
|
||||
case playTypePercent:
|
||||
if err := d.Percent(progress); err != nil {
|
||||
if err := d.Percent(progress, donut.StartAngle(start)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case playTypeAbsolute:
|
||||
if err := d.Absolute(progress, 100); err != nil {
|
||||
if err := d.Absolute(progress, 100, donut.StartAngle(start)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
//progress = 20
|
||||
//continue
|
||||
|
||||
progress += step * mult
|
||||
if progress > 100 || 100-progress < step {
|
||||
progress = 100
|
||||
} else if progress < 0 || progress < step {
|
||||
progress = 0
|
||||
start += 10
|
||||
if start >= 360 {
|
||||
start = 0
|
||||
}
|
||||
}
|
||||
|
||||
if progress == 100 {
|
||||
|
@ -85,11 +93,19 @@ func main() {
|
|||
defer t.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
d, err := donut.New()
|
||||
d, err := donut.New(
|
||||
donut.HolePercent(35),
|
||||
donut.CellOpts(
|
||||
cell.FgColor(cell.ColorRed),
|
||||
),
|
||||
donut.TextCellOpts(
|
||||
cell.FgColor(cell.ColorBlue),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go playDonut(ctx, d, 10, 500*time.Millisecond, playTypePercent)
|
||||
go playDonut(ctx, d, 1, 25*time.Millisecond, playTypePercent)
|
||||
|
||||
c, err := container.New(
|
||||
t,
|
||||
|
@ -107,7 +123,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
if err := termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter)); err != nil {
|
||||
if err := termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter), termdash.RedrawInterval(25*time.Millisecond)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,13 +41,13 @@ type options struct {
|
|||
donutHolePercent int
|
||||
hideTextProgress bool
|
||||
|
||||
textCellOpts []cell.Option
|
||||
donutCellOpts []cell.Option
|
||||
textCellOpts []cell.Option
|
||||
cellOpts []cell.Option
|
||||
|
||||
// The angle in degrees that represents 0 and 100% of the progress.
|
||||
startAngle int
|
||||
// The direction in which the donut completes as progress increases.
|
||||
// Positive for clockwise, negative for counter-clockwise.
|
||||
// Positive for counter-clockwise, negative for clockwise.
|
||||
direction int
|
||||
}
|
||||
|
||||
|
@ -57,8 +57,8 @@ func (o *options) validate() error {
|
|||
return fmt.Errorf("invalid donut hole percent %d, must be in range %d <= p <= %d", o.donutHolePercent, min, max)
|
||||
}
|
||||
|
||||
if min, max := 0, 360; o.startAngle < min || o.startAngle > max {
|
||||
return fmt.Errorf("invalid start angle %d, must be in range %d <= angle <= %d", o.startAngle, min, max)
|
||||
if min, max := 0, 360; o.startAngle < min || o.startAngle >= max {
|
||||
return fmt.Errorf("invalid start angle %d, must be in range %d <= angle < %d", o.startAngle, min, max)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -67,21 +67,25 @@ func (o *options) validate() error {
|
|||
// newOptions returns options with the default values set.
|
||||
func newOptions() *options {
|
||||
return &options{
|
||||
donutHolePercent: DefaultDonutHolePercent,
|
||||
donutHolePercent: DefaultHolePercent,
|
||||
startAngle: DefaultStartAngle,
|
||||
direction: 1,
|
||||
direction: -1,
|
||||
textCellOpts: []cell.Option{
|
||||
cell.FgColor(cell.ColorDefault),
|
||||
cell.BgColor(cell.ColorDefault),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultDonutHolePercent is the default value for the DonutHolePercent
|
||||
// DefaultHolePercent is the default value for the HolePercent
|
||||
// option.
|
||||
const DefaultDonutHolePercent = 20
|
||||
const DefaultHolePercent = 35
|
||||
|
||||
// DonutHolePercent sets the size of the "hole" inside the donut as a
|
||||
// HolePercent sets the size of the "hole" inside the donut as a
|
||||
// percentage of the donut's radius.
|
||||
// Setting this to zero disables the hole so that the donut will become just a
|
||||
// circle. Valid range is 0 <= p <= 100.
|
||||
func DonutHolePercent(p int) Option {
|
||||
func HolePercent(p int) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.donutHolePercent = p
|
||||
})
|
||||
|
@ -96,7 +100,7 @@ func DonutHolePercent(p int) Option {
|
|||
// The progress is only displayed if there is enough space for it in the middle
|
||||
// of the drawn donut.
|
||||
//
|
||||
// Providing this option also sets DonutHolePercent to its default value.
|
||||
// Providing this option also sets HolePercent to its default value.
|
||||
func ShowTextProgress() Option {
|
||||
return option(func(opts *options) {
|
||||
opts.hideTextProgress = false
|
||||
|
@ -118,10 +122,10 @@ func TextCellOpts(cOpts ...cell.Option) Option {
|
|||
})
|
||||
}
|
||||
|
||||
// DonutCellOpts sets cell options on cells that contain the donut.
|
||||
func DonutCellOpts(cOpts ...cell.Option) Option {
|
||||
// CellOpts sets cell options on cells that contain the donut.
|
||||
func CellOpts(cOpts ...cell.Option) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.donutCellOpts = cOpts
|
||||
opts.cellOpts = cOpts
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -130,7 +134,7 @@ const DefaultStartAngle = 90
|
|||
|
||||
// StartAngle sets the starting angle in degrees, i.e. the point that will
|
||||
// represent both 0% and 100% of progress.
|
||||
// Valid values are in range 0 <= angle <= 360.
|
||||
// Valid values are in range 0 <= angle < 360.
|
||||
// Angles start at the X axis and grow counter-clockwise.
|
||||
func StartAngle(angle int) Option {
|
||||
return option(func(opts *options) {
|
||||
|
@ -142,7 +146,7 @@ func StartAngle(angle int) Option {
|
|||
// direction. This is the default option.
|
||||
func Clockwise() Option {
|
||||
return option(func(opts *options) {
|
||||
opts.direction = 1
|
||||
opts.direction = -1
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -150,6 +154,6 @@ func Clockwise() Option {
|
|||
// direction.
|
||||
func CounterClockwise() Option {
|
||||
return option(func(opts *options) {
|
||||
opts.direction = -1
|
||||
opts.direction = 1
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue