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

Allowing container.Option to report an error.

This commit is contained in:
Jakub Sobon 2019-01-14 00:08:20 -05:00
parent d39112bbad
commit 964d676e31
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
12 changed files with 181 additions and 101 deletions

View File

@ -64,7 +64,7 @@ func (c *Container) String() string {
// New returns a new root container that will use the provided terminal and // New returns a new root container that will use the provided terminal and
// applies the provided options. // applies the provided options.
func New(t terminalapi.Terminal, opts ...Option) *Container { func New(t terminalapi.Terminal, opts ...Option) (*Container, error) {
size := t.Size() size := t.Size()
root := &Container{ root := &Container{
term: t, term: t,
@ -75,8 +75,10 @@ func New(t terminalapi.Terminal, opts ...Option) *Container {
// Initially the root is focused. // Initially the root is focused.
root.focusTracker = newFocusTracker(root) root.focusTracker = newFocusTracker(root)
applyOptions(root, opts...) if err := applyOptions(root, opts...); err != nil {
return root return nil, err
}
return root, nil
} }
// newChild creates a new child container of the given parent. // newChild creates a new child container of the given parent.

View File

@ -33,7 +33,7 @@ import (
// Example demonstrates how to use the Container API. // Example demonstrates how to use the Container API.
func Example() { func Example() {
New( if _, err := New(
/* terminal = */ nil, /* terminal = */ nil,
SplitVertical( SplitVertical(
Left( Left(
@ -58,20 +58,23 @@ func Example() {
PlaceWidget(fakewidget.New(widgetapi.Options{})), PlaceWidget(fakewidget.New(widgetapi.Options{})),
), ),
), ),
) ); err != nil {
panic(err)
}
} }
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
termSize image.Point termSize image.Point
container func(ft *faketerm.Terminal) *Container container func(ft *faketerm.Terminal) (*Container, error)
wantContainerErr bool
want func(size image.Point) *faketerm.Terminal want func(size image.Point) *faketerm.Terminal
}{ }{
{ {
desc: "empty container", desc: "empty container",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New(ft) return New(ft)
}, },
want: func(size image.Point) *faketerm.Terminal { want: func(size image.Point) *faketerm.Terminal {
@ -81,7 +84,7 @@ func TestNew(t *testing.T) {
{ {
desc: "container with a border", desc: "container with a border",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -102,7 +105,7 @@ func TestNew(t *testing.T) {
{ {
desc: "horizontal split, children have borders", desc: "horizontal split, children have borders",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
SplitHorizontal( SplitHorizontal(
@ -127,7 +130,7 @@ func TestNew(t *testing.T) {
{ {
desc: "horizontal split, parent and children have borders", desc: "horizontal split, parent and children have borders",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -158,7 +161,7 @@ func TestNew(t *testing.T) {
{ {
desc: "vertical split, children have borders", desc: "vertical split, children have borders",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
SplitVertical( SplitVertical(
@ -183,7 +186,7 @@ func TestNew(t *testing.T) {
{ {
desc: "vertical split, parent and children have borders", desc: "vertical split, parent and children have borders",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -214,7 +217,7 @@ func TestNew(t *testing.T) {
{ {
desc: "multi level split", desc: "multi level split",
termSize: image.Point{10, 16}, termSize: image.Point{10, 16},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
SplitVertical( SplitVertical(
@ -255,7 +258,7 @@ func TestNew(t *testing.T) {
{ {
desc: "inherits border and focused color", desc: "inherits border and focused color",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -296,7 +299,7 @@ func TestNew(t *testing.T) {
{ {
desc: "splitting a container removes the widget", desc: "splitting a container removes the widget",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -328,7 +331,7 @@ func TestNew(t *testing.T) {
{ {
desc: "placing a widget removes container split", desc: "placing a widget removes container split",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
SplitVertical( SplitVertical(
@ -360,7 +363,14 @@ func TestNew(t *testing.T) {
t.Fatalf("faketerm.New => unexpected error: %v", err) t.Fatalf("faketerm.New => unexpected error: %v", err)
} }
if err := tc.container(got).Draw(); err != nil { cont, err := tc.container(got)
if (err != nil) != tc.wantContainerErr {
t.Errorf("tc.container => unexpected error:%v, wantErr:%v", err, tc.wantContainerErr)
}
if err != nil {
return
}
if err := cont.Draw(); err != nil {
t.Fatalf("Draw => unexpected error: %v", err) t.Fatalf("Draw => unexpected error: %v", err)
} }
@ -376,7 +386,7 @@ func TestKeyboard(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
termSize image.Point termSize image.Point
container func(ft *faketerm.Terminal) *Container container func(ft *faketerm.Terminal) (*Container, error)
events []terminalapi.Event events []terminalapi.Event
want func(size image.Point) *faketerm.Terminal want func(size image.Point) *faketerm.Terminal
wantErr bool wantErr bool
@ -384,7 +394,7 @@ func TestKeyboard(t *testing.T) {
{ {
desc: "event not forwarded if container has no widget", desc: "event not forwarded if container has no widget",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New(ft) return New(ft)
}, },
events: []terminalapi.Event{ events: []terminalapi.Event{
@ -397,7 +407,7 @@ func TestKeyboard(t *testing.T) {
{ {
desc: "event forwarded to focused container only", desc: "event forwarded to focused container only",
termSize: image.Point{40, 20}, termSize: image.Point{40, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
SplitVertical( SplitVertical(
@ -450,7 +460,7 @@ func TestKeyboard(t *testing.T) {
{ {
desc: "event not forwarded if the widget didn't request it", desc: "event not forwarded if the widget didn't request it",
termSize: image.Point{40, 20}, termSize: image.Point{40, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: false})), PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: false})),
@ -473,7 +483,7 @@ func TestKeyboard(t *testing.T) {
{ {
desc: "widget returns an error when processing the event", desc: "widget returns an error when processing the event",
termSize: image.Point{40, 20}, termSize: image.Point{40, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: true})), PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: true})),
@ -503,7 +513,10 @@ func TestKeyboard(t *testing.T) {
t.Fatalf("faketerm.New => unexpected error: %v", err) t.Fatalf("faketerm.New => unexpected error: %v", err)
} }
c := tc.container(got) c, err := tc.container(got)
if err != nil {
t.Fatalf("tc.container => unexpected error: %v", err)
}
for _, ev := range tc.events { for _, ev := range tc.events {
switch e := ev.(type) { switch e := ev.(type) {
case *terminalapi.Mouse: case *terminalapi.Mouse:
@ -537,7 +550,7 @@ func TestMouse(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
termSize image.Point termSize image.Point
container func(ft *faketerm.Terminal) *Container container func(ft *faketerm.Terminal) (*Container, error)
events []terminalapi.Event events []terminalapi.Event
want func(size image.Point) *faketerm.Terminal want func(size image.Point) *faketerm.Terminal
wantErr bool wantErr bool
@ -545,7 +558,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "mouse click outside of the terminal is ignored", desc: "mouse click outside of the terminal is ignored",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: true})), PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: true})),
@ -569,7 +582,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "event not forwarded if container has no widget", desc: "event not forwarded if container has no widget",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New(ft) return New(ft)
}, },
events: []terminalapi.Event{ events: []terminalapi.Event{
@ -583,7 +596,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "event forwarded to container at that point", desc: "event forwarded to container at that point",
termSize: image.Point{50, 20}, termSize: image.Point{50, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
SplitVertical( SplitVertical(
@ -636,7 +649,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "event not forwarded if the widget didn't request it", desc: "event not forwarded if the widget didn't request it",
termSize: image.Point{20, 20}, termSize: image.Point{20, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: false})), PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: false})),
@ -659,7 +672,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "event not forwarded if it falls on the container's border", desc: "event not forwarded if it falls on the container's border",
termSize: image.Point{20, 20}, termSize: image.Point{20, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -693,7 +706,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "event not forwarded if it falls outside of widget's canvas", desc: "event not forwarded if it falls outside of widget's canvas",
termSize: image.Point{20, 20}, termSize: image.Point{20, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget( PlaceWidget(
@ -723,7 +736,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "mouse poisition adjusted relative to widget's canvas, vertical offset", desc: "mouse poisition adjusted relative to widget's canvas, vertical offset",
termSize: image.Point{20, 20}, termSize: image.Point{20, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget( PlaceWidget(
@ -754,7 +767,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "mouse poisition adjusted relative to widget's canvas, horizontal offset", desc: "mouse poisition adjusted relative to widget's canvas, horizontal offset",
termSize: image.Point{30, 20}, termSize: image.Point{30, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget( PlaceWidget(
@ -785,7 +798,7 @@ func TestMouse(t *testing.T) {
{ {
desc: "widget returns an error when processing the event", desc: "widget returns an error when processing the event",
termSize: image.Point{40, 20}, termSize: image.Point{40, 20},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: true})), PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: true})),
@ -815,7 +828,10 @@ func TestMouse(t *testing.T) {
t.Fatalf("faketerm.New => unexpected error: %v", err) t.Fatalf("faketerm.New => unexpected error: %v", err)
} }
c := tc.container(got) c, err := tc.container(got)
if err != nil {
t.Fatalf("tc.container => unexpected error: %v", err)
}
for _, ev := range tc.events { for _, ev := range tc.events {
switch e := ev.(type) { switch e := ev.(type) {
case *terminalapi.Mouse: case *terminalapi.Mouse:

View File

@ -32,14 +32,14 @@ func TestDrawWidget(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
termSize image.Point termSize image.Point
container func(ft *faketerm.Terminal) *Container container func(ft *faketerm.Terminal) (*Container, error)
want func(size image.Point) *faketerm.Terminal want func(size image.Point) *faketerm.Terminal
wantErr bool wantErr bool
}{ }{
{ {
desc: "draws widget with container border", desc: "draws widget with container border",
termSize: image.Point{9, 5}, termSize: image.Point{9, 5},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -66,7 +66,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "draws widget with container border and title aligned on the left", desc: "draws widget with container border and title aligned on the left",
termSize: image.Point{9, 5}, termSize: image.Point{9, 5},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -100,7 +100,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "draws widget with container border and title aligned in the center", desc: "draws widget with container border and title aligned in the center",
termSize: image.Point{9, 5}, termSize: image.Point{9, 5},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -135,7 +135,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "draws widget with container border and title aligned on the right", desc: "draws widget with container border and title aligned on the right",
termSize: image.Point{9, 5}, termSize: image.Point{9, 5},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -170,7 +170,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "draws widget with container border and title that is trimmed", desc: "draws widget with container border and title that is trimmed",
termSize: image.Point{9, 5}, termSize: image.Point{9, 5},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -205,7 +205,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "draws widget without container border", desc: "draws widget without container border",
termSize: image.Point{9, 5}, termSize: image.Point{9, 5},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget(fakewidget.New(widgetapi.Options{})), PlaceWidget(fakewidget.New(widgetapi.Options{})),
@ -225,7 +225,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "widget.Draw returns an error", desc: "widget.Draw returns an error",
termSize: image.Point{5, 5}, // Too small for the widget's box. termSize: image.Point{5, 5}, // Too small for the widget's box.
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -240,7 +240,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "container with border and no space isn't drawn", desc: "container with border and no space isn't drawn",
termSize: image.Point{1, 1}, termSize: image.Point{1, 1},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -257,7 +257,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "container without the requested space for its widget isn't drawn", desc: "container without the requested space for its widget isn't drawn",
termSize: image.Point{1, 1}, termSize: image.Point{1, 1},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
PlaceWidget(fakewidget.New(widgetapi.Options{ PlaceWidget(fakewidget.New(widgetapi.Options{
@ -276,7 +276,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "widget's canvas is limited to the requested maximum size", desc: "widget's canvas is limited to the requested maximum size",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -308,7 +308,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "widget's canvas is limited to the requested maximum width", desc: "widget's canvas is limited to the requested maximum width",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -340,7 +340,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "widget's canvas is limited to the requested maximum height", desc: "widget's canvas is limited to the requested maximum height",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -372,7 +372,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "widget gets the requested aspect ratio", desc: "widget gets the requested aspect ratio",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -404,7 +404,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "widget's canvas is limited to the requested maximum size and ratio", desc: "widget's canvas is limited to the requested maximum size and ratio",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -436,7 +436,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "horizontal left align for the widget", desc: "horizontal left align for the widget",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -467,7 +467,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "horizontal center align for the widget", desc: "horizontal center align for the widget",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -498,7 +498,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "horizontal right align for the widget", desc: "horizontal right align for the widget",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -529,7 +529,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "vertical top align for the widget", desc: "vertical top align for the widget",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -560,7 +560,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "vertical middle align for the widget", desc: "vertical middle align for the widget",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -591,7 +591,7 @@ func TestDrawWidget(t *testing.T) {
{ {
desc: "vertical bottom align for the widget", desc: "vertical bottom align for the widget",
termSize: image.Point{22, 22}, termSize: image.Point{22, 22},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -624,8 +624,11 @@ func TestDrawWidget(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
got := faketerm.MustNew(tc.termSize) got := faketerm.MustNew(tc.termSize)
c := tc.container(got) c, err := tc.container(got)
err := c.Draw() if err != nil {
t.Fatalf("tc.container => unexpected error: %v", err)
}
err = c.Draw()
if (err != nil) != tc.wantErr { if (err != nil) != tc.wantErr {
t.Errorf("Draw => unexpected error: %v, wantErr: %v", err, tc.wantErr) t.Errorf("Draw => unexpected error: %v, wantErr: %v", err, tc.wantErr)
} }
@ -647,7 +650,7 @@ func TestDrawHandlesTerminalResize(t *testing.T) {
t.Errorf("faketerm.New => unexpected error: %v", err) t.Errorf("faketerm.New => unexpected error: %v", err)
} }
cont := New( cont, err := New(
got, got,
SplitVertical( SplitVertical(
Left( Left(
@ -672,6 +675,9 @@ func TestDrawHandlesTerminalResize(t *testing.T) {
), ),
), ),
) )
if err != nil {
t.Fatalf("New => unexpected error: %v", err)
}
// The following tests aren't hermetic, they all access the same container // The following tests aren't hermetic, they all access the same container
// and fake terminal in order to retain state between resizes. // and fake terminal in order to retain state between resizes.

View File

@ -37,13 +37,13 @@ func TestPointCont(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
termSize image.Point termSize image.Point
container func(ft *faketerm.Terminal) *Container container func(ft *faketerm.Terminal) (*Container, error)
cases []pointCase cases []pointCase
}{ }{
{ {
desc: "single container, no border", desc: "single container, no border",
termSize: image.Point{3, 3}, termSize: image.Point{3, 3},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
BorderColor(cell.ColorBlue), BorderColor(cell.ColorBlue),
@ -90,7 +90,7 @@ func TestPointCont(t *testing.T) {
{ {
desc: "single container, border", desc: "single container, border",
termSize: image.Point{3, 3}, termSize: image.Point{3, 3},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -113,7 +113,7 @@ func TestPointCont(t *testing.T) {
{ {
desc: "split containers, parent has no border", desc: "split containers, parent has no border",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
BorderColor(cell.ColorBlack), BorderColor(cell.ColorBlack),
@ -160,7 +160,7 @@ func TestPointCont(t *testing.T) {
{ {
desc: "split containers, parent has border", desc: "split containers, parent has border",
termSize: image.Point{10, 10}, termSize: image.Point{10, 10},
container: func(ft *faketerm.Terminal) *Container { container: func(ft *faketerm.Terminal) (*Container, error) {
return New( return New(
ft, ft,
Border(draw.LineStyleLight), Border(draw.LineStyleLight),
@ -229,7 +229,10 @@ func TestPointCont(t *testing.T) {
t.Fatalf("faketerm.New => unexpected error: %v", err) t.Fatalf("faketerm.New => unexpected error: %v", err)
} }
cont := tc.container(ft) cont, err := tc.container(ft)
if err != nil {
t.Fatalf("tc.container => unexpected error: %v", err)
}
for _, pc := range tc.cases { for _, pc := range tc.cases {
gotCont := pointCont(cont, pc.point) gotCont := pointCont(cont, pc.point)
if (gotCont == nil) != pc.wantNil { if (gotCont == nil) != pc.wantNil {
@ -382,13 +385,16 @@ func TestFocusTrackerMouse(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
root := New( root, err := New(
ft, ft,
SplitVertical( SplitVertical(
Left(), Left(),
Right(), Right(),
), ),
) )
if err != nil {
t.Fatalf("New => unexpected error: %v", err)
}
for _, ev := range tc.events { for _, ev := range tc.events {
root.Mouse(ev) root.Mouse(ev)

View File

@ -24,16 +24,19 @@ import (
) )
// applyOptions applies the options to the container. // applyOptions applies the options to the container.
func applyOptions(c *Container, opts ...Option) { func applyOptions(c *Container, opts ...Option) error {
for _, opt := range opts { for _, opt := range opts {
opt.set(c) if err := opt.set(c); err != nil {
return err
} }
} }
return nil
}
// Option is used to provide options to a container. // Option is used to provide options to a container.
type Option interface { type Option interface {
// set sets the provided option. // set sets the provided option.
set(*Container) set(*Container) error
} }
// options stores the options provided to the container. // options stores the options provided to the container.
@ -85,22 +88,24 @@ func newOptions(parent *options) *options {
} }
// option implements Option. // option implements Option.
type option func(*Container) type option func(*Container) error
// set implements Option.set. // set implements Option.set.
func (o option) set(c *Container) { func (o option) set(c *Container) error {
o(c) return o(c)
} }
// SplitVertical splits the container along the vertical axis into two sub // SplitVertical splits the container along the vertical axis into two sub
// containers. The use of this option removes any widget placed at this // containers. The use of this option removes any widget placed at this
// container, containers with sub containers cannot contain widgets. // container, containers with sub containers cannot contain widgets.
func SplitVertical(l LeftOption, r RightOption) Option { func SplitVertical(l LeftOption, r RightOption) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.split = splitTypeVertical c.opts.split = splitTypeVertical
c.opts.widget = nil c.opts.widget = nil
applyOptions(c.createFirst(), l.lOpts()...) if err := applyOptions(c.createFirst(), l.lOpts()...); err != nil {
applyOptions(c.createSecond(), r.rOpts()...) return err
}
return applyOptions(c.createSecond(), r.rOpts()...)
}) })
} }
@ -108,11 +113,13 @@ func SplitVertical(l LeftOption, r RightOption) Option {
// containers. The use of this option removes any widget placed at this // containers. The use of this option removes any widget placed at this
// container, containers with sub containers cannot contain widgets. // container, containers with sub containers cannot contain widgets.
func SplitHorizontal(t TopOption, b BottomOption) Option { func SplitHorizontal(t TopOption, b BottomOption) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.split = splitTypeHorizontal c.opts.split = splitTypeHorizontal
c.opts.widget = nil c.opts.widget = nil
applyOptions(c.createFirst(), t.tOpts()...) if err := applyOptions(c.createFirst(), t.tOpts()...); err != nil {
applyOptions(c.createSecond(), b.bOpts()...) return err
}
return applyOptions(c.createSecond(), b.bOpts()...)
}) })
} }
@ -120,10 +127,11 @@ func SplitHorizontal(t TopOption, b BottomOption) Option {
// The use of this option removes any sub containers. Containers with sub // The use of this option removes any sub containers. Containers with sub
// containers cannot have widgets. // containers cannot have widgets.
func PlaceWidget(w widgetapi.Widget) Option { func PlaceWidget(w widgetapi.Widget) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.widget = w c.opts.widget = w
c.first = nil c.first = nil
c.second = nil c.second = nil
return nil
}) })
} }
@ -131,8 +139,9 @@ func PlaceWidget(w widgetapi.Widget) Option {
// container. Has no effect if the container contains no widget. // container. Has no effect if the container contains no widget.
// Defaults alignment in the center. // Defaults alignment in the center.
func AlignHorizontal(h align.Horizontal) Option { func AlignHorizontal(h align.Horizontal) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.hAlign = h c.opts.hAlign = h
return nil
}) })
} }
@ -140,51 +149,58 @@ func AlignHorizontal(h align.Horizontal) Option {
// Has no effect if the container contains no widget. // Has no effect if the container contains no widget.
// Defaults to alignment in the middle. // Defaults to alignment in the middle.
func AlignVertical(v align.Vertical) Option { func AlignVertical(v align.Vertical) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.vAlign = v c.opts.vAlign = v
return nil
}) })
} }
// Border configures the container to have a border of the specified style. // Border configures the container to have a border of the specified style.
func Border(ls draw.LineStyle) Option { func Border(ls draw.LineStyle) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.border = ls c.opts.border = ls
return nil
}) })
} }
// BorderTitle sets a text title within the border. // BorderTitle sets a text title within the border.
func BorderTitle(title string) Option { func BorderTitle(title string) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.borderTitle = title c.opts.borderTitle = title
return nil
}) })
} }
// BorderTitleAlignLeft aligns the border title on the left. // BorderTitleAlignLeft aligns the border title on the left.
func BorderTitleAlignLeft() Option { func BorderTitleAlignLeft() Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.borderTitleHAlign = align.HorizontalLeft c.opts.borderTitleHAlign = align.HorizontalLeft
return nil
}) })
} }
// BorderTitleAlignCenter aligns the border title in the center. // BorderTitleAlignCenter aligns the border title in the center.
func BorderTitleAlignCenter() Option { func BorderTitleAlignCenter() Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.borderTitleHAlign = align.HorizontalCenter c.opts.borderTitleHAlign = align.HorizontalCenter
return nil
}) })
} }
// BorderTitleAlignRight aligns the border title on the right. // BorderTitleAlignRight aligns the border title on the right.
func BorderTitleAlignRight() Option { func BorderTitleAlignRight() Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.borderTitleHAlign = align.HorizontalRight c.opts.borderTitleHAlign = align.HorizontalRight
return nil
}) })
} }
// BorderColor sets the color of the border around the container. // BorderColor sets the color of the border around the container.
// This option is inherited to sub containers created by container splits. // This option is inherited to sub containers created by container splits.
func BorderColor(color cell.Color) Option { func BorderColor(color cell.Color) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.inherited.borderColor = color c.opts.inherited.borderColor = color
return nil
}) })
} }
@ -192,8 +208,9 @@ func BorderColor(color cell.Color) Option {
// keyboard focus. // keyboard focus.
// This option is inherited to sub containers created by container splits. // This option is inherited to sub containers created by container splits.
func FocusedColor(color cell.Color) Option { func FocusedColor(color cell.Color) Option {
return option(func(c *Container) { return option(func(c *Container) error {
c.opts.inherited.focusedColor = color c.opts.inherited.focusedColor = color
return nil
}) })
} }

View File

@ -30,7 +30,7 @@ func TestRoot(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("faketerm.New => unexpected error: %v", err) t.Fatalf("faketerm.New => unexpected error: %v", err)
} }
want := New( want, err := New(
ft, ft,
SplitHorizontal( SplitHorizontal(
Top( Top(
@ -42,6 +42,9 @@ func TestRoot(t *testing.T) {
Bottom(), Bottom(),
), ),
) )
if err != nil {
t.Fatalf("New => unexpected error: %v", err)
}
if got := rootCont(want); got != want { if got := rootCont(want); got != want {
t.Errorf("rootCont(root) => got %p, want %p", got, want) t.Errorf("rootCont(root) => got %p, want %p", got, want)
@ -58,7 +61,7 @@ func TestTraversal(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("faketerm.New => unexpected error: %v", err) t.Fatalf("faketerm.New => unexpected error: %v", err)
} }
cont := New( cont, err := New(
ft, ft,
BorderColor(cell.ColorBlack), BorderColor(cell.ColorBlack),
SplitVertical( SplitVertical(
@ -86,6 +89,9 @@ func TestTraversal(t *testing.T) {
), ),
), ),
) )
if err != nil {
t.Fatalf("New => unexpected error: %v", err)
}
tests := []struct { tests := []struct {
desc string desc string

View File

@ -50,7 +50,7 @@ func Example() {
} }
// Create the container with two fake widgets. // Create the container with two fake widgets.
c := container.New( c, err := container.New(
t, t,
container.SplitVertical( container.SplitVertical(
container.Left( container.Left(
@ -61,6 +61,9 @@ func Example() {
), ),
), ),
) )
if err != nil {
panic(err)
}
// Termdash runs until the context expires. // Termdash runs until the context expires.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@ -86,10 +89,13 @@ func Example_triggered() {
} }
// Create the container with a widget. // Create the container with a widget.
c := container.New( c, err := container.New(
t, t,
container.PlaceWidget(fakewidget.New(wOpts)), container.PlaceWidget(fakewidget.New(wOpts)),
) )
if err != nil {
panic(err)
}
// Create the controller and disable periodic redraw. // Create the controller and disable periodic redraw.
ctrl, err := NewController(t, c) ctrl, err := NewController(t, c)
@ -332,13 +338,16 @@ func TestRun(t *testing.T) {
t.Fatalf("faketerm.New => unexpected error: %v", err) t.Fatalf("faketerm.New => unexpected error: %v", err)
} }
cont := container.New( cont, err := container.New(
got, got,
container.PlaceWidget(fakewidget.New(widgetapi.Options{ container.PlaceWidget(fakewidget.New(widgetapi.Options{
WantKeyboard: true, WantKeyboard: true,
WantMouse: true, WantMouse: true,
})), })),
) )
if err != nil {
t.Fatalf("container.New => unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond)
err = Run(ctx, got, cont, tc.opts...) err = Run(ctx, got, cont, tc.opts...)
@ -511,10 +520,13 @@ func TestController(t *testing.T) {
WantKeyboard: true, WantKeyboard: true,
WantMouse: true, WantMouse: true,
}) })
cont := container.New( cont, err := container.New(
got, got,
container.PlaceWidget(mi), container.PlaceWidget(mi),
) )
if err != nil {
t.Fatalf("container.New => unexpected error: %v", err)
}
ctrl, err := NewController(got, cont, tc.opts...) ctrl, err := NewController(got, cont, tc.opts...)
if (err != nil) != tc.wantErr { if (err != nil) != tc.wantErr {

View File

@ -93,12 +93,15 @@ func main() {
) )
go playBarChart(ctx, bc, 1*time.Second) go playBarChart(ctx, bc, 1*time.Second)
c := container.New( c, err := container.New(
t, t,
container.Border(draw.LineStyleLight), container.Border(draw.LineStyleLight),
container.BorderTitle("PRESS Q TO QUIT"), container.BorderTitle("PRESS Q TO QUIT"),
container.PlaceWidget(bc), container.PlaceWidget(bc),
) )
if err != nil {
panic(err)
}
quitter := func(k *terminalapi.Keyboard) { quitter := func(k *terminalapi.Keyboard) {
if k.Key == 'q' || k.Key == 'Q' { if k.Key == 'q' || k.Key == 'Q' {

View File

@ -115,7 +115,7 @@ func main() {
) )
go playGauge(ctx, withLabel, 3, 500*time.Millisecond, playTypePercent) go playGauge(ctx, withLabel, 3, 500*time.Millisecond, playTypePercent)
c := container.New( c, err := container.New(
t, t,
container.SplitVertical( container.SplitVertical(
container.Left( container.Left(
@ -147,6 +147,9 @@ func main() {
), ),
), ),
) )
if err != nil {
panic(err)
}
quitter := func(k *terminalapi.Keyboard) { quitter := func(k *terminalapi.Keyboard) {
if k.Key == 'q' || k.Key == 'Q' { if k.Key == 'q' || k.Key == 'Q' {

View File

@ -88,12 +88,15 @@ func main() {
linechart.XLabelCellOpts(cell.FgColor(cell.ColorCyan)), linechart.XLabelCellOpts(cell.FgColor(cell.ColorCyan)),
) )
go playLineChart(ctx, lc, redrawInterval/3) go playLineChart(ctx, lc, redrawInterval/3)
c := container.New( c, err := container.New(
t, t,
container.Border(draw.LineStyleLight), container.Border(draw.LineStyleLight),
container.BorderTitle("PRESS Q TO QUIT"), container.BorderTitle("PRESS Q TO QUIT"),
container.PlaceWidget(lc), container.PlaceWidget(lc),
) )
if err != nil {
panic(err)
}
quitter := func(k *terminalapi.Keyboard) { quitter := func(k *terminalapi.Keyboard) {
if k.Key == 'q' || k.Key == 'Q' { if k.Key == 'q' || k.Key == 'Q' {

View File

@ -75,7 +75,7 @@ func main() {
) )
go playSparkLine(ctx, yellow, 1*time.Second) go playSparkLine(ctx, yellow, 1*time.Second)
c := container.New( c, err := container.New(
t, t,
container.Border(draw.LineStyleLight), container.Border(draw.LineStyleLight),
container.BorderTitle("PRESS Q TO QUIT"), container.BorderTitle("PRESS Q TO QUIT"),
@ -103,6 +103,9 @@ func main() {
), ),
), ),
) )
if err != nil {
panic(err)
}
quitter := func(k *terminalapi.Keyboard) { quitter := func(k *terminalapi.Keyboard) {
if k.Key == 'q' || k.Key == 'Q' { if k.Key == 'q' || k.Key == 'Q' {

View File

@ -106,7 +106,7 @@ func main() {
} }
go writeLines(ctx, rolled, 1*time.Second) go writeLines(ctx, rolled, 1*time.Second)
c := container.New( c, err := container.New(
t, t,
container.Border(draw.LineStyleLight), container.Border(draw.LineStyleLight),
container.BorderTitle("PRESS Q TO QUIT"), container.BorderTitle("PRESS Q TO QUIT"),
@ -148,6 +148,9 @@ func main() {
), ),
), ),
) )
if err != nil {
panic(err)
}
quitter := func(k *terminalapi.Keyboard) { quitter := func(k *terminalapi.Keyboard) {
if k.Key == 'q' || k.Key == 'Q' { if k.Key == 'q' || k.Key == 'Q' {