diff --git a/CHANGELOG.md b/CHANGELOG.md index 13840f9..7f702f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/widgets/button/button.go b/widgets/button/button.go index eb55238..dd13b58 100644 --- a/widgets/button/button.go +++ b/widgets/button/button.go @@ -154,6 +154,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. diff --git a/widgets/button/button_test.go b/widgets/button/button_test.go index 7ab9090..94ed7d5 100644 --- a/widgets/button/button_test.go +++ b/widgets/button/button_test.go @@ -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 } @@ -411,7 +415,7 @@ func TestButton(t *testing.T) { wantCallback: &callbackTracker{}, }, { - desc: "mouse triggered the callback", + desc: "mouse triggered a callback set via the constructor", callback: &callbackTracker{}, text: "hello", canvas: image.Rect(0, 0, 8, 4), @@ -451,6 +455,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{}, @@ -1373,6 +1421,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 +1454,10 @@ func TestButton(t *testing.T) { btn = b } + if 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)