Container handles resize correctly.

And making container thread unsafe, thread safety will be implemented at
the top.
This commit is contained in:
Jakub Sobon 2018-04-23 00:45:17 +01:00
parent abdbd82b29
commit 2d4d903f87
3 changed files with 161 additions and 15 deletions

View File

@ -24,7 +24,6 @@ package container
import (
"fmt"
"image"
"sync"
"github.com/mum4k/termdash/area"
"github.com/mum4k/termdash/draw"
@ -33,7 +32,7 @@ import (
// Container wraps either sub containers or widgets and positions them on the
// terminal.
// This is thread-safe and must not be copied.
// This is not thread-safe.
type Container struct {
// parent is the parent container, nil if this is the root container.
parent *Container
@ -52,9 +51,6 @@ type Container struct {
// area is the area of the terminal this container has access to.
area image.Rectangle
// mu locks the container while it is being drawn or while it receives input events.
mu sync.Mutex
// opts are the options provided to the container.
opts *options
}
@ -157,9 +153,6 @@ func (c *Container) createSecond() *Container {
// Draw draws this container and all of its sub containers.
func (c *Container) Draw() error {
c.mu.Lock()
defer c.mu.Unlock()
return drawTree(c)
}
@ -167,9 +160,6 @@ func (c *Container) Draw() error {
// Keyboard events are forwarded to the widget in the currently focused
// container, assuming that the widget registered for keyboard events.
func (c *Container) Keyboard(k *terminalapi.Keyboard) error {
c.mu.Lock()
defer c.mu.Unlock()
w := c.focusTracker.active().opts.widget
if w == nil || !w.Options().WantKeyboard {
return nil
@ -186,9 +176,6 @@ func (c *Container) Keyboard(k *terminalapi.Keyboard) error {
// widget. Only mouse events that fall within the widget's canvas are forwarded
// and the coordinates are adjusted relative to the widget's canvas.
func (c *Container) Mouse(m *terminalapi.Mouse) error {
c.mu.Lock()
defer c.mu.Unlock()
c.focusTracker.mouse(m)
target := pointCont(c, m.Position)

View File

@ -30,7 +30,20 @@ import (
// drawTree draws this container and all of its sub containers.
func drawTree(c *Container) error {
var errStr string
preOrder(c, &errStr, visitFunc(func(c *Container) error {
root := rootCont(c)
size := root.term.Size()
root.area = image.Rect(0, 0, size.X, size.Y)
preOrder(root, &errStr, visitFunc(func(c *Container) error {
first, second := c.split()
if c.first != nil {
c.first.area = first
}
if c.second != nil {
c.second.area = second
}
return drawCont(c)
}))
if errStr != "" {

View File

@ -386,3 +386,149 @@ func TestDrawWidget(t *testing.T) {
})
}
}
func TestDrawHandlesTerminalResize(t *testing.T) {
termSize := image.Point{60, 10}
got, err := faketerm.New(termSize)
if err != nil {
t.Errorf("faketerm.New => unexpected error: %v", err)
}
cont := New(
got,
SplitVertical(
Left(
SplitHorizontal(
Top(
PlaceWidget(fakewidget.New(widgetapi.Options{})),
),
Bottom(
PlaceWidget(fakewidget.New(widgetapi.Options{})),
),
),
),
Right(
SplitVertical(
Left(
PlaceWidget(fakewidget.New(widgetapi.Options{})),
),
Right(
PlaceWidget(fakewidget.New(widgetapi.Options{})),
),
),
),
),
)
// The following tests aren't hermetic, they all access the same container
// and fake terminal in order to retain state between resizes.
tests := []struct {
desc string
resize *image.Point // if not nil, the fake terminal will be resized.
want func(size image.Point) *faketerm.Terminal
}{
{
desc: "handles the initial draw request",
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(0, 0, 30, 5)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(0, 5, 30, 10)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(30, 0, 45, 10)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(45, 0, 60, 10)),
widgetapi.Options{},
)
return ft
},
},
{
desc: "increase in size",
resize: &image.Point{80, 10},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(0, 0, 40, 5)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(0, 5, 40, 10)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(40, 0, 60, 10)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(60, 0, 80, 10)),
widgetapi.Options{},
)
return ft
},
},
{
desc: "decrease in size",
resize: &image.Point{50, 10},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(0, 0, 25, 5)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(0, 5, 25, 10)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(25, 0, 37, 10)),
widgetapi.Options{},
)
fakewidget.MustDraw(
ft,
testcanvas.MustNew(image.Rect(37, 0, 50, 10)),
widgetapi.Options{},
)
return ft
},
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
if tc.resize != nil {
if err := got.Resize(*tc.resize); err != nil {
t.Fatalf("Resize => unexpected error: %v", err)
}
}
if err := cont.Draw(); err != nil {
t.Fatalf("Draw => unexpected error: %v", err)
}
if diff := faketerm.Diff(tc.want(got.Size()), got); diff != "" {
t.Errorf("Draw => %v", diff)
}
})
}
}