mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Complete test coverage for button and tweaks to the demo.
This commit is contained in:
parent
e9cf1e1af7
commit
b2a1f30fe1
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- The Button widget.
|
||||||
- A function that draws text vertically.
|
- A function that draws text vertically.
|
||||||
- The LineChart widget can display X axis labels in vertical orientation.
|
- The LineChart widget can display X axis labels in vertical orientation.
|
||||||
- The LineChart widget allows the user to specify a custom scale for the Y
|
- The LineChart widget allows the user to specify a custom scale for the Y
|
||||||
|
14
README.md
14
README.md
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
# termdash
|
# termdash
|
||||||
|
|
||||||
[<img src="./images/termdashdemo_0_6_0.gif" alt="termdashdemo" type="image/gif">](termdashdemo/termdashdemo.go)
|
[<img src="./images/termdashdemo_0_7_0.gif" alt="termdashdemo" type="image/gif">](termdashdemo/termdashdemo.go)
|
||||||
|
|
||||||
This project implements a cross-platform customizable terminal based dashboard.
|
This project implements a cross-platform customizable terminal based dashboard.
|
||||||
The feature set is inspired by the
|
The feature set is inspired by the
|
||||||
@ -62,6 +62,18 @@ Project documentation is available in the [doc](doc/) directory.
|
|||||||
|
|
||||||
## Implemented Widgets
|
## Implemented Widgets
|
||||||
|
|
||||||
|
### The Button
|
||||||
|
|
||||||
|
Allows users to interact with the application, each button press runs a callback function.
|
||||||
|
Run the
|
||||||
|
[buttondemo](widgets/button/buttondemo/buttondemo.go).
|
||||||
|
|
||||||
|
```go
|
||||||
|
go run github.com/mum4k/termdash/widgets/button/buttondemo/buttondemo.go
|
||||||
|
```
|
||||||
|
|
||||||
|
[<img src="./images/buttondemo.gif" alt="buttondemo" type="image/gif">](widgets/button/buttondemo/buttondemo.go)
|
||||||
|
|
||||||
### The Gauge
|
### The Gauge
|
||||||
|
|
||||||
Displays the progress of an operation. Run the
|
Displays the progress of an operation. Run the
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 7.0 MiB |
@ -21,15 +21,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mum4k/termdash"
|
"github.com/mum4k/termdash"
|
||||||
|
"github.com/mum4k/termdash/align"
|
||||||
"github.com/mum4k/termdash/cell"
|
"github.com/mum4k/termdash/cell"
|
||||||
"github.com/mum4k/termdash/container"
|
"github.com/mum4k/termdash/container"
|
||||||
"github.com/mum4k/termdash/draw"
|
"github.com/mum4k/termdash/draw"
|
||||||
"github.com/mum4k/termdash/terminal/termbox"
|
"github.com/mum4k/termdash/terminal/termbox"
|
||||||
"github.com/mum4k/termdash/terminalapi"
|
"github.com/mum4k/termdash/terminalapi"
|
||||||
"github.com/mum4k/termdash/widgets/barchart"
|
"github.com/mum4k/termdash/widgets/barchart"
|
||||||
|
"github.com/mum4k/termdash/widgets/button"
|
||||||
"github.com/mum4k/termdash/widgets/donut"
|
"github.com/mum4k/termdash/widgets/donut"
|
||||||
"github.com/mum4k/termdash/widgets/gauge"
|
"github.com/mum4k/termdash/widgets/gauge"
|
||||||
"github.com/mum4k/termdash/widgets/linechart"
|
"github.com/mum4k/termdash/widgets/linechart"
|
||||||
@ -128,10 +131,34 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sineLC, err := newSines(ctx)
|
leftB, rightB, sineLC, err := newSines(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
lcAndButtons := []container.Option{
|
||||||
|
container.SplitHorizontal(
|
||||||
|
container.Top(
|
||||||
|
container.Border(draw.LineStyleLight),
|
||||||
|
container.BorderTitle("Multiple series"),
|
||||||
|
container.BorderTitleAlignRight(),
|
||||||
|
container.PlaceWidget(sineLC),
|
||||||
|
),
|
||||||
|
container.Bottom(
|
||||||
|
container.SplitVertical(
|
||||||
|
container.Left(
|
||||||
|
container.PlaceWidget(leftB),
|
||||||
|
container.AlignHorizontal(align.HorizontalRight),
|
||||||
|
),
|
||||||
|
container.Right(
|
||||||
|
container.PlaceWidget(rightB),
|
||||||
|
container.AlignHorizontal(align.HorizontalLeft),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
container.SplitPercent(80),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
rightSide := []container.Option{
|
rightSide := []container.Option{
|
||||||
container.SplitHorizontal(
|
container.SplitHorizontal(
|
||||||
container.Top(
|
container.Top(
|
||||||
@ -148,12 +175,7 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
|
|||||||
container.BorderTitleAlignRight(),
|
container.BorderTitleAlignRight(),
|
||||||
container.PlaceWidget(don),
|
container.PlaceWidget(don),
|
||||||
),
|
),
|
||||||
container.Bottom(
|
container.Bottom(lcAndButtons...),
|
||||||
container.Border(draw.LineStyleLight),
|
|
||||||
container.BorderTitle("Multiple series"),
|
|
||||||
container.BorderTitleAlignRight(),
|
|
||||||
container.PlaceWidget(sineLC),
|
|
||||||
),
|
|
||||||
container.SplitPercent(30),
|
container.SplitPercent(30),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -422,23 +444,47 @@ func newBarChart(ctx context.Context) (*barchart.BarChart, error) {
|
|||||||
return bc, nil
|
return bc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSines returns a line chart that displays multiple sine series.
|
// distance is a thread-safe int value used by the newSince method.
|
||||||
func newSines(ctx context.Context) (*linechart.LineChart, error) {
|
// Buttons write it and the line chart reads it.
|
||||||
|
type distance struct {
|
||||||
|
v int
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds the provided value to the one stored.
|
||||||
|
func (d *distance) add(v int) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
d.v += v
|
||||||
|
}
|
||||||
|
|
||||||
|
// get returns the current value.
|
||||||
|
func (d *distance) get() int {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
return d.v
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSines returns a line chart that displays multiple sine series and two buttons.
|
||||||
|
// The left button shifts the second series relative to the first series to
|
||||||
|
// the left and the right button shifts it to the right.
|
||||||
|
func newSines(ctx context.Context) (left, right *button.Button, lc *linechart.LineChart, err error) {
|
||||||
var inputs []float64
|
var inputs []float64
|
||||||
for i := 0; i < 200; i++ {
|
for i := 0; i < 200; i++ {
|
||||||
v := math.Sin(float64(i) / 100 * math.Pi)
|
v := math.Sin(float64(i) / 100 * math.Pi)
|
||||||
inputs = append(inputs, v)
|
inputs = append(inputs, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
lc, err := linechart.New(
|
sineLc, err := linechart.New(
|
||||||
linechart.AxesCellOpts(cell.FgColor(cell.ColorRed)),
|
linechart.AxesCellOpts(cell.FgColor(cell.ColorRed)),
|
||||||
linechart.YLabelCellOpts(cell.FgColor(cell.ColorGreen)),
|
linechart.YLabelCellOpts(cell.FgColor(cell.ColorGreen)),
|
||||||
linechart.XLabelCellOpts(cell.FgColor(cell.ColorGreen)),
|
linechart.XLabelCellOpts(cell.FgColor(cell.ColorGreen)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
step1 := 0
|
step1 := 0
|
||||||
|
secondDist := &distance{v: 100}
|
||||||
go periodic(ctx, redrawInterval/3, func() error {
|
go periodic(ctx, redrawInterval/3, func() error {
|
||||||
step1 = (step1 + 1) % len(inputs)
|
step1 = (step1 + 1) % len(inputs)
|
||||||
if err := lc.Series("first", rotateFloats(inputs, step1),
|
if err := lc.Series("first", rotateFloats(inputs, step1),
|
||||||
@ -447,10 +493,30 @@ func newSines(ctx context.Context) (*linechart.LineChart, error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
step2 := (step1 + 100) % len(inputs)
|
step2 := (step1 + secondDist.get()) % len(inputs)
|
||||||
return lc.Series("second", rotateFloats(inputs, step2), linechart.SeriesCellOpts(cell.FgColor(cell.ColorWhite)))
|
return lc.Series("second", rotateFloats(inputs, step2), linechart.SeriesCellOpts(cell.FgColor(cell.ColorWhite)))
|
||||||
})
|
})
|
||||||
return lc, nil
|
|
||||||
|
// diff is the difference a single button press adds or removes to the
|
||||||
|
// second series.
|
||||||
|
const diff = 20
|
||||||
|
leftB, err := button.New("(l)eft", func() error {
|
||||||
|
secondDist.add(diff)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
button.GlobalKey('l'),
|
||||||
|
button.WidthFor("(r)ight"),
|
||||||
|
button.FillColor(cell.ColorNumber(220)),
|
||||||
|
)
|
||||||
|
|
||||||
|
rightB, err := button.New("(r)ight", func() error {
|
||||||
|
secondDist.add(-diff)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
button.GlobalKey('r'),
|
||||||
|
button.FillColor(cell.ColorNumber(196)),
|
||||||
|
)
|
||||||
|
return leftB, rightB, sineLc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// rotateFloats returns a new slice with inputs rotated by step.
|
// rotateFloats returns a new slice with inputs rotated by step.
|
||||||
|
@ -96,6 +96,7 @@ func New(text string, cFn CallbackFn, opts ...Option) (*Button, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vars to be replaced from tests.
|
||||||
var (
|
var (
|
||||||
// Runes to use in cells that contain the button.
|
// Runes to use in cells that contain the button.
|
||||||
// Changed from tests to provide readable test failures.
|
// Changed from tests to provide readable test failures.
|
||||||
@ -103,6 +104,9 @@ var (
|
|||||||
// Runes to use in cells that contain the shadow.
|
// Runes to use in cells that contain the shadow.
|
||||||
// Changed from tests to provide readable test failures.
|
// Changed from tests to provide readable test failures.
|
||||||
shadowRune = ' '
|
shadowRune = ' '
|
||||||
|
|
||||||
|
// timeSince is a function that calculates duration since some time.
|
||||||
|
timeSince = time.Since
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw draws the Button widget onto the canvas.
|
// Draw draws the Button widget onto the canvas.
|
||||||
@ -112,7 +116,7 @@ func (b *Button) Draw(cvs *canvas.Canvas) error {
|
|||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if b.keyTriggerTime != nil {
|
if b.keyTriggerTime != nil {
|
||||||
since := time.Since(*b.keyTriggerTime)
|
since := timeSince(*b.keyTriggerTime)
|
||||||
if since > b.opts.keyUpDelay {
|
if since > b.opts.keyUpDelay {
|
||||||
b.state = button.Up
|
b.state = button.Up
|
||||||
}
|
}
|
||||||
|
@ -65,16 +65,22 @@ func (ct *callbackTracker) callback() error {
|
|||||||
|
|
||||||
func TestButton(t *testing.T) {
|
func TestButton(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
text string
|
text string
|
||||||
callback *callbackTracker
|
callback *callbackTracker
|
||||||
opts []Option
|
opts []Option
|
||||||
events []terminalapi.Event
|
events []terminalapi.Event
|
||||||
canvas image.Rectangle
|
canvas image.Rectangle
|
||||||
want func(size image.Point) *faketerm.Terminal
|
|
||||||
wantCallback *callbackTracker
|
// timeSince is used to replace time.Since for tests, leave nil to use
|
||||||
wantNewErr bool
|
// the original.
|
||||||
wantDrawErr bool
|
timeSince func(time.Time) time.Duration
|
||||||
|
|
||||||
|
want func(size image.Point) *faketerm.Terminal
|
||||||
|
wantCallback *callbackTracker
|
||||||
|
wantNewErr bool
|
||||||
|
wantDrawErr bool
|
||||||
|
wantCallbackErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "New fails with nil callback",
|
desc: "New fails with nil callback",
|
||||||
@ -108,6 +114,13 @@ func TestButton(t *testing.T) {
|
|||||||
canvas: image.Rect(0, 0, 1, 1),
|
canvas: image.Rect(0, 0, 1, 1),
|
||||||
wantNewErr: true,
|
wantNewErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "draw fails on canvas too small",
|
||||||
|
callback: &callbackTracker{},
|
||||||
|
text: "hello",
|
||||||
|
canvas: image.Rect(0, 0, 1, 1),
|
||||||
|
wantDrawErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "draws button in up state",
|
desc: "draws button in up state",
|
||||||
callback: &callbackTracker{},
|
callback: &callbackTracker{},
|
||||||
@ -259,23 +272,246 @@ func TestButton(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantCallback: &callbackTracker{},
|
wantCallback: &callbackTracker{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "keyboard event triggers the button, trigger time didn't expire so button is down",
|
||||||
|
callback: &callbackTracker{},
|
||||||
|
text: "hello",
|
||||||
|
opts: []Option{
|
||||||
|
Key(keyboard.KeyEnter),
|
||||||
|
},
|
||||||
|
timeSince: func(time.Time) time.Duration {
|
||||||
|
return 200 * time.Millisecond
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, 8, 4),
|
||||||
|
events: []terminalapi.Event{
|
||||||
|
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
// Keyboard event ignored when no key configured
|
// Button.
|
||||||
// Draws button down by key + trigger.
|
testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 'x', cell.BgColor(cell.ColorNumber(117)))
|
||||||
// Releases button after key press.
|
|
||||||
// Doesn't release button after key press if before KeyUpDelay.
|
|
||||||
// Ignores unrelated key.
|
|
||||||
// Key works when KeyScopeFocused.
|
|
||||||
// sets custom key
|
|
||||||
// Ignores key outside of the container on KeyScopeFocused.
|
|
||||||
// Accepts key outside of the container on KeyScopeFlobal.
|
|
||||||
// Triggers callback multiple times.
|
|
||||||
// Callback returns an error.
|
|
||||||
// Custom height.
|
|
||||||
// Different width due to text.
|
|
||||||
// Different width due to WidthFor.
|
|
||||||
// Trims text on custom width.
|
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
testdraw.MustText(cvs, "hello", image.Point{2, 2},
|
||||||
|
draw.TextCellOpts(
|
||||||
|
cell.FgColor(cell.ColorBlack),
|
||||||
|
cell.BgColor(cell.ColorNumber(117))),
|
||||||
|
)
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
wantCallback: &callbackTracker{
|
||||||
|
called: true,
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "keyboard event triggers the button, custom trigger time expired so button is up",
|
||||||
|
callback: &callbackTracker{},
|
||||||
|
text: "hello",
|
||||||
|
opts: []Option{
|
||||||
|
Key(keyboard.KeyEnter),
|
||||||
|
KeyUpDelay(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
timeSince: func(time.Time) time.Duration {
|
||||||
|
return 200 * time.Millisecond
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, 8, 4),
|
||||||
|
events: []terminalapi.Event{
|
||||||
|
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
// Shadow.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 's', cell.BgColor(cell.ColorNumber(240)))
|
||||||
|
|
||||||
|
// Button.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(0, 0, 7, 3), 'x', cell.BgColor(cell.ColorNumber(117)))
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
testdraw.MustText(cvs, "hello", image.Point{1, 1},
|
||||||
|
draw.TextCellOpts(
|
||||||
|
cell.FgColor(cell.ColorBlack),
|
||||||
|
cell.BgColor(cell.ColorNumber(117))),
|
||||||
|
)
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
wantCallback: &callbackTracker{
|
||||||
|
called: true,
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "keyboard event triggers the button multiple times",
|
||||||
|
callback: &callbackTracker{},
|
||||||
|
text: "hello",
|
||||||
|
opts: []Option{
|
||||||
|
Key(keyboard.KeyEnter),
|
||||||
|
KeyUpDelay(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
timeSince: func(time.Time) time.Duration {
|
||||||
|
return 200 * time.Millisecond
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, 8, 4),
|
||||||
|
events: []terminalapi.Event{
|
||||||
|
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
|
||||||
|
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
|
||||||
|
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
// Shadow.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 's', cell.BgColor(cell.ColorNumber(240)))
|
||||||
|
|
||||||
|
// Button.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(0, 0, 7, 3), 'x', cell.BgColor(cell.ColorNumber(117)))
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
testdraw.MustText(cvs, "hello", image.Point{1, 1},
|
||||||
|
draw.TextCellOpts(
|
||||||
|
cell.FgColor(cell.ColorBlack),
|
||||||
|
cell.BgColor(cell.ColorNumber(117))),
|
||||||
|
)
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
wantCallback: &callbackTracker{
|
||||||
|
called: true,
|
||||||
|
count: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "mouse event triggers the button multiple times",
|
||||||
|
callback: &callbackTracker{},
|
||||||
|
text: "hello",
|
||||||
|
canvas: image.Rect(0, 0, 8, 4),
|
||||||
|
events: []terminalapi.Event{
|
||||||
|
&terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonLeft},
|
||||||
|
&terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease},
|
||||||
|
&terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonLeft},
|
||||||
|
&terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease},
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
// Shadow.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 's', cell.BgColor(cell.ColorNumber(240)))
|
||||||
|
|
||||||
|
// Button.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(0, 0, 7, 3), 'x', cell.BgColor(cell.ColorNumber(117)))
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
testdraw.MustText(cvs, "hello", image.Point{1, 1},
|
||||||
|
draw.TextCellOpts(
|
||||||
|
cell.FgColor(cell.ColorBlack),
|
||||||
|
cell.BgColor(cell.ColorNumber(117))),
|
||||||
|
)
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
wantCallback: &callbackTracker{
|
||||||
|
called: true,
|
||||||
|
count: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "the callback returns an error after a mouse event",
|
||||||
|
callback: &callbackTracker{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
text: "hello",
|
||||||
|
canvas: image.Rect(0, 0, 8, 4),
|
||||||
|
events: []terminalapi.Event{
|
||||||
|
&terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonLeft},
|
||||||
|
&terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease},
|
||||||
|
},
|
||||||
|
wantCallbackErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "the callback returns an error after a keyboard event",
|
||||||
|
callback: &callbackTracker{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
text: "hello",
|
||||||
|
opts: []Option{
|
||||||
|
Key(keyboard.KeyEnter),
|
||||||
|
KeyUpDelay(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
timeSince: func(time.Time) time.Duration {
|
||||||
|
return 200 * time.Millisecond
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, 8, 4),
|
||||||
|
events: []terminalapi.Event{
|
||||||
|
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
|
||||||
|
},
|
||||||
|
wantCallbackErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "draws button with custom height (infra gives smaller canvas)",
|
||||||
|
callback: &callbackTracker{},
|
||||||
|
text: "hello",
|
||||||
|
canvas: image.Rect(0, 0, 8, 2),
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
// Shadow.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 2), 's', cell.BgColor(cell.ColorNumber(240)))
|
||||||
|
|
||||||
|
// Button.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(0, 0, 7, 1), 'x', cell.BgColor(cell.ColorNumber(117)))
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
testdraw.MustText(cvs, "hello", image.Point{1, 0},
|
||||||
|
draw.TextCellOpts(
|
||||||
|
cell.FgColor(cell.ColorBlack),
|
||||||
|
cell.BgColor(cell.ColorNumber(117))),
|
||||||
|
)
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
wantCallback: &callbackTracker{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "button width adjusts to width (infra gives smaller canvas)",
|
||||||
|
callback: &callbackTracker{},
|
||||||
|
text: "h",
|
||||||
|
canvas: image.Rect(0, 0, 4, 2),
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
// Shadow.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 4, 2), 's', cell.BgColor(cell.ColorNumber(240)))
|
||||||
|
|
||||||
|
// Button.
|
||||||
|
testcanvas.MustSetAreaCells(cvs, image.Rect(0, 0, 3, 1), 'x', cell.BgColor(cell.ColorNumber(117)))
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
testdraw.MustText(cvs, "h", image.Point{1, 0},
|
||||||
|
draw.TextCellOpts(
|
||||||
|
cell.FgColor(cell.ColorBlack),
|
||||||
|
cell.BgColor(cell.ColorNumber(117))),
|
||||||
|
)
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
wantCallback: &callbackTracker{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "sets custom text color",
|
desc: "sets custom text color",
|
||||||
callback: &callbackTracker{},
|
callback: &callbackTracker{},
|
||||||
@ -372,6 +608,12 @@ func TestButton(t *testing.T) {
|
|||||||
shadowRune = 's'
|
shadowRune = 's'
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
if tc.timeSince != nil {
|
||||||
|
timeSince = tc.timeSince
|
||||||
|
} else {
|
||||||
|
timeSince = time.Since
|
||||||
|
}
|
||||||
|
|
||||||
gotCallback := tc.callback
|
gotCallback := tc.callback
|
||||||
var cFn CallbackFn
|
var cFn CallbackFn
|
||||||
if gotCallback == nil {
|
if gotCallback == nil {
|
||||||
@ -402,16 +644,38 @@ func TestButton(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ev := range tc.events {
|
for i, ev := range tc.events {
|
||||||
switch e := ev.(type) {
|
switch e := ev.(type) {
|
||||||
case *terminalapi.Mouse:
|
case *terminalapi.Mouse:
|
||||||
if err := b.Mouse(e); err != nil {
|
err := b.Mouse(e)
|
||||||
t.Fatalf("Mouse => unexpected error: %v", err)
|
// Only the last event in test cases is the one that triggers the callback.
|
||||||
|
if i == len(tc.events)-1 {
|
||||||
|
if (err != nil) != tc.wantCallbackErr {
|
||||||
|
t.Errorf("Mouse => unexpected error: %v, wantCallbackErr: %v", err, tc.wantCallbackErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Mouse => unexpected error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case *terminalapi.Keyboard:
|
case *terminalapi.Keyboard:
|
||||||
if err := b.Keyboard(e); err != nil {
|
err := b.Keyboard(e)
|
||||||
t.Fatalf("Keyboard => unexpected error: %v", err)
|
// Only the last event in test cases is the one that triggers the callback.
|
||||||
|
if i == len(tc.events)-1 {
|
||||||
|
if (err != nil) != tc.wantCallbackErr {
|
||||||
|
t.Errorf("Keyboard => unexpected error: %v, wantCallbackErr: %v", err, tc.wantCallbackErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Keyboard => unexpected error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/mum4k/termdash"
|
"github.com/mum4k/termdash"
|
||||||
"github.com/mum4k/termdash/align"
|
"github.com/mum4k/termdash/align"
|
||||||
|
"github.com/mum4k/termdash/cell"
|
||||||
"github.com/mum4k/termdash/container"
|
"github.com/mum4k/termdash/container"
|
||||||
"github.com/mum4k/termdash/draw"
|
"github.com/mum4k/termdash/draw"
|
||||||
"github.com/mum4k/termdash/terminal/termbox"
|
"github.com/mum4k/termdash/terminal/termbox"
|
||||||
@ -69,6 +70,7 @@ func main() {
|
|||||||
segmentdisplay.NewChunk(fmt.Sprintf("%d", val)),
|
segmentdisplay.NewChunk(fmt.Sprintf("%d", val)),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
button.FillColor(cell.ColorNumber(220)),
|
||||||
button.GlobalKey('s'),
|
button.GlobalKey('s'),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user