-
Notifications
You must be signed in to change notification settings - Fork 134
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
467 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// Copyright 2019 Google Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Package button implements an interactive widget that can be pressed to | ||
// activate. | ||
package button | ||
|
||
import ( | ||
"errors" | ||
"image" | ||
"sync" | ||
|
||
runewidth "github.com/mattn/go-runewidth" | ||
"github.com/mum4k/termdash/canvas" | ||
"github.com/mum4k/termdash/terminalapi" | ||
"github.com/mum4k/termdash/widgetapi" | ||
) | ||
|
||
// CallbackFn is the function called when the button is pressed. | ||
// The callback function must be non-blocking, ideally just storing a value and | ||
// returning, since event processing blocks the redraws. | ||
// | ||
// The callback function must be thread-safe as the mouse or keyboard events | ||
// that press the button are processed in a separate goroutine. | ||
// | ||
// If the function returns an error, the widget will forward it back to the | ||
// termdash infrastructure which causes a panic, unless the user provided a | ||
// termdash.ErrorHandler. | ||
type CallbackFn func() error | ||
|
||
// Button can be pressed using a mouse click or a configured keyboard key. | ||
// | ||
// Upon each press, the button invokes a callback provided by the user. | ||
// | ||
// Implements widgetapi.Widget. This object is thread-safe. | ||
type Button struct { | ||
// text in the text label displayed in the button. | ||
text string | ||
|
||
// mu protects the widget. | ||
mu sync.Mutex | ||
|
||
// opts are the provided options. | ||
opts *options | ||
} | ||
|
||
// New returns a new Button that will display the provided text. | ||
// Each press of the button will invoke the callback function. | ||
func New(text string, cFn CallbackFn, opts ...Option) (*Button, error) { | ||
opt := newOptions(runewidth.StringWidth(text)) | ||
for _, o := range opts { | ||
o.set(opt) | ||
} | ||
if err := opt.validate(); err != nil { | ||
return nil, err | ||
} | ||
return &Button{ | ||
text: text, | ||
opts: opt, | ||
}, nil | ||
} | ||
|
||
// Draw draws the Button widget onto the canvas. | ||
// Implements widgetapi.Widget.Draw. | ||
func (b *Button) Draw(cvs *canvas.Canvas) error { | ||
b.mu.Lock() | ||
defer b.mu.Unlock() | ||
|
||
return errors.New("unimplemented") | ||
} | ||
|
||
// Keyboard processes keyboard events, acts as a button press on the configured | ||
// Key. | ||
// | ||
// Implements widgetapi.Widget.Keyboard. | ||
func (*Button) Keyboard(k *terminalapi.Keyboard) error { | ||
return errors.New("unimplemented") | ||
} | ||
|
||
// Mouse processes mouse events, acts as a button press if both the press and | ||
// the release happen inside the button. | ||
// | ||
// Implements widgetapi.Widget.Mouse. | ||
func (*Button) Mouse(m *terminalapi.Mouse) error { | ||
return errors.New("the SegmentDisplay widget doesn't support mouse events") | ||
} | ||
|
||
// Options implements widgetapi.Widget.Options. | ||
func (b *Button) Options() widgetapi.Options { | ||
// No need to lock, as the height and width get fixed when New is called. | ||
|
||
height := b.opts.height + 1 // One for the shadow. | ||
return widgetapi.Options{ | ||
MinimumSize: image.Point{b.opts.width, height}, | ||
WantKeyboard: true, | ||
WantMouse: true, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Copyright 2019 Google Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package button | ||
|
||
import ( | ||
"errors" | ||
"image" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/kylelemons/godebug/pretty" | ||
"github.com/mum4k/termdash/canvas" | ||
"github.com/mum4k/termdash/terminal/faketerm" | ||
"github.com/mum4k/termdash/terminalapi" | ||
"github.com/mum4k/termdash/widgetapi" | ||
) | ||
|
||
// callbackTracker tracks whether callback was called. | ||
type callbackTracker struct { | ||
// wantErr when set to true, makes callback return an error. | ||
wantErr bool | ||
|
||
// called asserts whether the callback was called. | ||
called bool | ||
|
||
// count is the number of times the callback was called. | ||
count int | ||
|
||
// mu protects the tracker. | ||
mu sync.Mutex | ||
} | ||
|
||
// callback is the callback function. | ||
func (ct *callbackTracker) callback() error { | ||
ct.mu.Lock() | ||
defer ct.mu.Unlock() | ||
|
||
if ct.wantErr { | ||
return errors.New("ct.wantErr set to true") | ||
} | ||
|
||
ct.count++ | ||
ct.called = true | ||
return nil | ||
} | ||
|
||
func TestButton(t *testing.T) { | ||
tests := []struct { | ||
desc string | ||
text string | ||
opts []Option | ||
events []terminalapi.Event | ||
canvas image.Rectangle | ||
want func(size image.Point) *faketerm.Terminal | ||
wantCallback *callbackTracker | ||
wantNewErr bool | ||
wantDrawErr bool | ||
}{} | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.desc, func(t *testing.T) { | ||
gotCallback := &callbackTracker{} | ||
b, err := New(tc.text, gotCallback.callback, tc.opts...) | ||
if (err != nil) != tc.wantNewErr { | ||
t.Errorf("New => unexpected error: %v, wantNewErr: %v", err, tc.wantNewErr) | ||
} | ||
if err != nil { | ||
return | ||
} | ||
|
||
for _, ev := range tc.events { | ||
switch ev.(type) { | ||
default: | ||
t.Fatalf("unsupported event type: %T", ev) | ||
} | ||
} | ||
|
||
c, err := canvas.New(tc.canvas) | ||
if err != nil { | ||
t.Fatalf("canvas.New => unexpected error: %v", err) | ||
} | ||
|
||
err = b.Draw(c) | ||
if (err != nil) != tc.wantDrawErr { | ||
t.Errorf("Draw => unexpected error: %v, wantDrawErr: %v", err, tc.wantDrawErr) | ||
} | ||
if err != nil { | ||
return | ||
} | ||
|
||
got, err := faketerm.New(c.Size()) | ||
if err != nil { | ||
t.Fatalf("faketerm.New => unexpected error: %v", err) | ||
} | ||
|
||
if err := c.Apply(got); err != nil { | ||
t.Fatalf("Apply => unexpected error: %v", err) | ||
} | ||
|
||
var want *faketerm.Terminal | ||
if tc.want != nil { | ||
want = tc.want(c.Size()) | ||
} else { | ||
want = faketerm.MustNew(c.Size()) | ||
} | ||
|
||
if diff := faketerm.Diff(want, got); diff != "" { | ||
t.Errorf("Draw => %v", diff) | ||
} | ||
|
||
if diff := pretty.Compare(tc.wantCallback, gotCallback); diff != "" { | ||
t.Errorf("CallbackFn => unexpected diff (-want, +got):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestKeyboard(t *testing.T) { | ||
ct := &callbackTracker{} | ||
b, err := New("text", ct.callback) | ||
if err != nil { | ||
t.Fatalf("New => unexpected error: %v", err) | ||
} | ||
if err := b.Keyboard(&terminalapi.Keyboard{}); err == nil { | ||
t.Errorf("Keyboard => got nil err, wanted one") | ||
} | ||
} | ||
|
||
func TestMouse(t *testing.T) { | ||
ct := &callbackTracker{} | ||
b, err := New("text", ct.callback) | ||
if err != nil { | ||
t.Fatalf("New => unexpected error: %v", err) | ||
} | ||
if err := b.Mouse(&terminalapi.Mouse{}); err == nil { | ||
t.Errorf("Mouse => got nil err, wanted one") | ||
} | ||
} | ||
|
||
func TestOptions(t *testing.T) { | ||
ct := &callbackTracker{} | ||
b, err := New("text", ct.callback) | ||
if err != nil { | ||
t.Fatalf("New => unexpected error: %v", err) | ||
} | ||
got := b.Options() | ||
want := widgetapi.Options{ | ||
MinimumSize: image.Point{6, 3}, | ||
WantKeyboard: true, | ||
WantMouse: true, | ||
} | ||
if diff := pretty.Compare(want, got); diff != "" { | ||
t.Errorf("Options => unexpected diff (-want, +got):\n%s", diff) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright 2019 Google Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Binary buttondemo shows the functionality of a button widget. | ||
package main | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/mum4k/termdash" | ||
"github.com/mum4k/termdash/container" | ||
"github.com/mum4k/termdash/draw" | ||
"github.com/mum4k/termdash/terminal/termbox" | ||
"github.com/mum4k/termdash/terminalapi" | ||
) | ||
|
||
func main() { | ||
t, err := termbox.New() | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer t.Close() | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
|
||
c, err := container.New( | ||
t, | ||
container.Border(draw.LineStyleLight), | ||
container.BorderTitle("PRESS Q TO QUIT"), | ||
) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
quitter := func(k *terminalapi.Keyboard) { | ||
if k.Key == 'q' || k.Key == 'Q' { | ||
cancel() | ||
} | ||
} | ||
|
||
if err := termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter), termdash.RedrawInterval(1*time.Second)); err != nil { | ||
panic(err) | ||
} | ||
} |
Oops, something went wrong.