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
|
||||
|
||||
- The Button widget.
|
||||
- A function that draws text vertically.
|
||||
- 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
|
||||
|
14
README.md
14
README.md
@ -8,7 +8,7 @@
|
||||
|
||||
# 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.
|
||||
The feature set is inspired by the
|
||||
@ -62,6 +62,18 @@ Project documentation is available in the [doc](doc/) directory.
|
||||
|
||||
## 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
|
||||
|
||||
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"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash"
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/container"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminal/termbox"
|
||||
"github.com/mum4k/termdash/terminalapi"
|
||||
"github.com/mum4k/termdash/widgets/barchart"
|
||||
"github.com/mum4k/termdash/widgets/button"
|
||||
"github.com/mum4k/termdash/widgets/donut"
|
||||
"github.com/mum4k/termdash/widgets/gauge"
|
||||
"github.com/mum4k/termdash/widgets/linechart"
|
||||
@ -128,10 +131,34 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sineLC, err := newSines(ctx)
|
||||
leftB, rightB, sineLC, err := newSines(ctx)
|
||||
if err != nil {
|
||||
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{
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
@ -148,12 +175,7 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
|
||||
container.BorderTitleAlignRight(),
|
||||
container.PlaceWidget(don),
|
||||
),
|
||||
container.Bottom(
|
||||
container.Border(draw.LineStyleLight),
|
||||
container.BorderTitle("Multiple series"),
|
||||
container.BorderTitleAlignRight(),
|
||||
container.PlaceWidget(sineLC),
|
||||
),
|
||||
container.Bottom(lcAndButtons...),
|
||||
container.SplitPercent(30),
|
||||
),
|
||||
),
|
||||
@ -422,23 +444,47 @@ func newBarChart(ctx context.Context) (*barchart.BarChart, error) {
|
||||
return bc, nil
|
||||
}
|
||||
|
||||
// newSines returns a line chart that displays multiple sine series.
|
||||
func newSines(ctx context.Context) (*linechart.LineChart, error) {
|
||||
// distance is a thread-safe int value used by the newSince method.
|
||||
// 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
|
||||
for i := 0; i < 200; i++ {
|
||||
v := math.Sin(float64(i) / 100 * math.Pi)
|
||||
inputs = append(inputs, v)
|
||||
}
|
||||
|
||||
lc, err := linechart.New(
|
||||
sineLc, err := linechart.New(
|
||||
linechart.AxesCellOpts(cell.FgColor(cell.ColorRed)),
|
||||
linechart.YLabelCellOpts(cell.FgColor(cell.ColorGreen)),
|
||||
linechart.XLabelCellOpts(cell.FgColor(cell.ColorGreen)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
step1 := 0
|
||||
secondDist := &distance{v: 100}
|
||||
go periodic(ctx, redrawInterval/3, func() error {
|
||||
step1 = (step1 + 1) % len(inputs)
|
||||
if err := lc.Series("first", rotateFloats(inputs, step1),
|
||||
@ -447,10 +493,30 @@ func newSines(ctx context.Context) (*linechart.LineChart, error) {
|
||||
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, 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.
|
||||
|
@ -96,6 +96,7 @@ func New(text string, cFn CallbackFn, opts ...Option) (*Button, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Vars to be replaced from tests.
|
||||
var (
|
||||
// Runes to use in cells that contain the button.
|
||||
// Changed from tests to provide readable test failures.
|
||||
@ -103,6 +104,9 @@ var (
|
||||
// Runes to use in cells that contain the shadow.
|
||||
// Changed from tests to provide readable test failures.
|
||||
shadowRune = ' '
|
||||
|
||||
// timeSince is a function that calculates duration since some time.
|
||||
timeSince = time.Since
|
||||
)
|
||||
|
||||
// Draw draws the Button widget onto the canvas.
|
||||
@ -112,7 +116,7 @@ func (b *Button) Draw(cvs *canvas.Canvas) error {
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if b.keyTriggerTime != nil {
|
||||
since := time.Since(*b.keyTriggerTime)
|
||||
since := timeSince(*b.keyTriggerTime)
|
||||
if since > b.opts.keyUpDelay {
|
||||
b.state = button.Up
|
||||
}
|
||||
|
@ -71,10 +71,16 @@ func TestButton(t *testing.T) {
|
||||
opts []Option
|
||||
events []terminalapi.Event
|
||||
canvas image.Rectangle
|
||||
|
||||
// timeSince is used to replace time.Since for tests, leave nil to use
|
||||
// the original.
|
||||
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",
|
||||
@ -108,6 +114,13 @@ func TestButton(t *testing.T) {
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
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",
|
||||
callback: &callbackTracker{},
|
||||
@ -259,23 +272,246 @@ func TestButton(t *testing.T) {
|
||||
},
|
||||
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
|
||||
// Draws button down by key + trigger.
|
||||
// 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.
|
||||
// Button.
|
||||
testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 'x', cell.BgColor(cell.ColorNumber(117)))
|
||||
|
||||
// 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",
|
||||
callback: &callbackTracker{},
|
||||
@ -372,6 +608,12 @@ func TestButton(t *testing.T) {
|
||||
shadowRune = 's'
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.timeSince != nil {
|
||||
timeSince = tc.timeSince
|
||||
} else {
|
||||
timeSince = time.Since
|
||||
}
|
||||
|
||||
gotCallback := tc.callback
|
||||
var cFn CallbackFn
|
||||
if gotCallback == nil {
|
||||
@ -402,17 +644,39 @@ func TestButton(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, ev := range tc.events {
|
||||
for i, ev := range tc.events {
|
||||
switch e := ev.(type) {
|
||||
case *terminalapi.Mouse:
|
||||
if err := b.Mouse(e); err != nil {
|
||||
err := b.Mouse(e)
|
||||
// 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:
|
||||
if err := b.Keyboard(e); err != nil {
|
||||
err := b.Keyboard(e)
|
||||
// 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:
|
||||
t.Fatalf("unsupported event type: %T", ev)
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/mum4k/termdash"
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/container"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminal/termbox"
|
||||
@ -69,6 +70,7 @@ func main() {
|
||||
segmentdisplay.NewChunk(fmt.Sprintf("%d", val)),
|
||||
})
|
||||
},
|
||||
button.FillColor(cell.ColorNumber(220)),
|
||||
button.GlobalKey('s'),
|
||||
)
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user