1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-25 13:48:50 +08:00

Merge branch 'devel' into 243-formdemo

This commit is contained in:
Jakub Sobon 2020-12-30 00:27:32 -05:00
commit d30bc47245
3 changed files with 153 additions and 29 deletions

View File

@ -60,6 +60,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
main states (up, focused and up, down).
- the `button` widget allows specifying separate fill color values for each of
its main states (up, focused and up, down).
- the `button` widget now has a method `SetCallback` that allows updating the
callback function on an existing `button` instance.
#### Updates to the `textinput` widget

View File

@ -99,6 +99,8 @@ type Button struct {
// New returns a new Button that will display the provided text.
// Each press of the button will invoke the callback function.
// The callback function can be nil in which case pressing the button is a
// no-op.
func New(text string, cFn CallbackFn, opts ...Option) (*Button, error) {
return NewFromChunks([]*TextChunk{NewChunk(text)}, cFn, opts...)
}
@ -106,10 +108,6 @@ func New(text string, cFn CallbackFn, opts ...Option) (*Button, error) {
// NewFromChunks is like New, but allows specifying write options for
// individual chunks of text displayed in the button.
func NewFromChunks(chunks []*TextChunk, cFn CallbackFn, opts ...Option) (*Button, error) {
if cFn == nil {
return nil, errors.New("the CallbackFn argument cannot be nil")
}
if len(chunks) == 0 {
return nil, errors.New("at least one text chunk must be specified")
}
@ -154,6 +152,13 @@ func NewFromChunks(chunks []*TextChunk, cFn CallbackFn, opts ...Option) (*Button
}, nil
}
// SetCallback replaces the callback function of the button with the one provided.
func (b *Button) SetCallback(cFn CallbackFn) {
b.mu.Lock()
defer b.mu.Unlock()
b.callback = cFn
}
// Vars to be replaced from tests.
var (
// Runes to use in cells that contain the button.
@ -281,10 +286,12 @@ func (b *Button) keyActivated(k *terminalapi.Keyboard, meta *widgetapi.EventMeta
// Implements widgetapi.Widget.Keyboard.
func (b *Button) Keyboard(k *terminalapi.Keyboard, meta *widgetapi.EventMeta) error {
if b.keyActivated(k, meta) {
// Mutex must be released when calling the callback.
// Users might call container methods from the callback like the
// Container.Update, see #205.
return b.callback()
if b.callback != nil {
// Mutex must be released when calling the callback.
// Users might call container methods from the callback like the
// Container.Update, see #205.
return b.callback()
}
}
return nil
}
@ -307,10 +314,12 @@ func (b *Button) mouseActivated(m *terminalapi.Mouse) bool {
// Implements widgetapi.Widget.Mouse.
func (b *Button) Mouse(m *terminalapi.Mouse, meta *widgetapi.EventMeta) error {
if b.mouseActivated(m) {
// Mutex must be released when calling the callback.
// Users might call container methods from the callback like the
// Container.Update, see #205.
return b.callback()
if b.callback != nil {
// Mutex must be released when calling the callback.
// Users might call container methods from the callback like the
// Container.Update, see #205.
return b.callback()
}
}
return nil
}

View File

@ -45,6 +45,10 @@ type callbackTracker struct {
// count is the number of times the callback was called.
count int
// useSetCallback when set to true instructs the test to set the callback
// via button.SetCallback instead of button.New or button.NewFromChunks.
useSetCallback bool
// mu protects the tracker.
mu sync.Mutex
}
@ -93,13 +97,6 @@ func TestButton(t *testing.T) {
wantDrawErr bool
wantCallbackErr bool
}{
{
desc: "New fails with nil callback",
canvas: image.Rect(0, 0, 1, 1),
text: "hello",
meta: &widgetapi.Meta{Focused: false},
wantNewErr: true,
},
{
desc: "New fails with negative keyUpDelay",
callback: &callbackTracker{},
@ -168,15 +165,6 @@ func TestButton(t *testing.T) {
meta: &widgetapi.Meta{Focused: false},
wantNewErr: true,
},
{
desc: "NewFromChunks fails with nil callback",
textChunks: []*TextChunk{
NewChunk("text"),
},
canvas: image.Rect(0, 0, 1, 1),
meta: &widgetapi.Meta{Focused: false},
wantNewErr: true,
},
{
desc: "NewFromChunks fails with negative keyUpDelay",
textChunks: []*TextChunk{
@ -411,7 +399,44 @@ func TestButton(t *testing.T) {
wantCallback: &callbackTracker{},
},
{
desc: "mouse triggered the callback",
desc: "mouse triggered a button with nil callback",
callback: nil,
text: "hello",
canvas: image.Rect(0, 0, 8, 4),
meta: &widgetapi.Meta{Focused: false},
events: []*event{
{
ev: &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonLeft},
meta: &widgetapi.EventMeta{},
},
{
ev: &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease},
meta: &widgetapi.EventMeta{},
},
},
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
},
},
{
desc: "mouse triggered a callback set via the constructor",
callback: &callbackTracker{},
text: "hello",
canvas: image.Rect(0, 0, 8, 4),
@ -451,6 +476,50 @@ func TestButton(t *testing.T) {
count: 1,
},
},
{
desc: "mouse triggered a callback set via SetCallback",
callback: &callbackTracker{
useSetCallback: true,
},
text: "hello",
canvas: image.Rect(0, 0, 8, 4),
meta: &widgetapi.Meta{Focused: false},
events: []*event{
{
ev: &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonLeft},
meta: &widgetapi.EventMeta{},
},
{
ev: &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease},
meta: &widgetapi.EventMeta{},
},
},
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,
useSetCallback: true,
},
},
{
desc: "draws button in down state due to a keyboard event, callback triggered",
callback: &callbackTracker{},
@ -674,6 +743,42 @@ func TestButton(t *testing.T) {
},
wantCallback: &callbackTracker{},
},
{
desc: "keyboard event triggers a button with nil callback",
callback: nil,
text: "hello",
opts: []Option{
Key(keyboard.KeyEnter),
},
timeSince: func(time.Time) time.Duration {
return 200 * time.Millisecond
},
canvas: image.Rect(0, 0, 8, 4),
meta: &widgetapi.Meta{Focused: false},
events: []*event{
{
ev: &terminalapi.Keyboard{Key: keyboard.KeyEnter},
meta: &widgetapi.EventMeta{Focused: true},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
cvs := testcanvas.MustNew(ft.Area())
// 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
},
},
{
desc: "keyboard event triggers the button, trigger time didn't expire so button is down",
callback: &callbackTracker{},
@ -1373,6 +1478,10 @@ func TestButton(t *testing.T) {
var cFn CallbackFn
if gotCallback == nil {
cFn = nil
} else if gotCallback.useSetCallback {
// Set an no-op callback via the constructor.
// It will be updated to the real one via SetCallback.
cFn = func() error { return nil }
} else {
cFn = gotCallback.callback
}
@ -1402,6 +1511,10 @@ func TestButton(t *testing.T) {
btn = b
}
if gotCallback != nil && gotCallback.useSetCallback {
btn.SetCallback(gotCallback.callback)
}
{
// Draw once which initializes the mouse state machine with the current canvas area.
c, err := canvas.New(tc.canvas)