mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Implementation of container and its tests.
Including a diff utility for unit tests.
This commit is contained in:
parent
6b592b7d34
commit
3a3531d7e1
47
area/area.go
47
area/area.go
@ -21,3 +21,50 @@ func FromSize(size image.Point) (image.Rectangle, error) {
|
||||
}
|
||||
return image.Rect(0, 0, size.X, size.Y), nil
|
||||
}
|
||||
|
||||
// HSplit returns two new areas created by splitting the provided area in the
|
||||
// middle along the horizontal axis. Can return zero size areas.
|
||||
func HSplit(area image.Rectangle) (image.Rectangle, image.Rectangle) {
|
||||
height := area.Dy() / 2
|
||||
if height == 0 {
|
||||
return image.ZR, image.ZR
|
||||
}
|
||||
return image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+height),
|
||||
image.Rect(area.Min.X, area.Min.Y+height, area.Max.X, area.Max.Y)
|
||||
}
|
||||
|
||||
// VSplit returns two new areas created by splitting the provided area in the
|
||||
// middle along the vertical axis. Can return zero size areas.
|
||||
func VSplit(area image.Rectangle) (image.Rectangle, image.Rectangle) {
|
||||
width := area.Dx() / 2
|
||||
if width == 0 {
|
||||
return image.ZR, image.ZR
|
||||
}
|
||||
return image.Rect(area.Min.X, area.Min.Y, area.Min.X+width, area.Max.Y),
|
||||
image.Rect(area.Min.X+width, area.Min.Y, area.Max.X, area.Max.Y)
|
||||
}
|
||||
|
||||
// abs returns the absolute value of x.
|
||||
func abs(x int) int {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ExcludeBorder returns a new area created by subtracting a border around the
|
||||
// provided area. Can return a zero area.
|
||||
func ExcludeBorder(area image.Rectangle) image.Rectangle {
|
||||
// If the area dimensions are smaller than this, subtracting a point for the
|
||||
// border on each of its sides results in a zero area.
|
||||
const minDim = 3
|
||||
if area.Dx() < minDim || area.Dy() < minDim {
|
||||
return image.ZR
|
||||
}
|
||||
return image.Rect(
|
||||
abs(area.Min.X+1),
|
||||
abs(area.Min.Y+1),
|
||||
abs(area.Max.X-1),
|
||||
abs(area.Max.Y-1),
|
||||
)
|
||||
}
|
||||
|
@ -102,3 +102,128 @@ func TestFromSize(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHSplit(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
area image.Rectangle
|
||||
want1 image.Rectangle
|
||||
want2 image.Rectangle
|
||||
}{
|
||||
{
|
||||
desc: "zero area to begin with",
|
||||
area: image.ZR,
|
||||
want1: image.ZR,
|
||||
want2: image.ZR,
|
||||
},
|
||||
{
|
||||
desc: "splitting results in zero height area",
|
||||
area: image.Rect(1, 1, 2, 2),
|
||||
want1: image.ZR,
|
||||
want2: image.ZR,
|
||||
},
|
||||
{
|
||||
desc: "splits area with even height",
|
||||
area: image.Rect(1, 1, 3, 3),
|
||||
want1: image.Rect(1, 1, 3, 2),
|
||||
want2: image.Rect(1, 2, 3, 3),
|
||||
},
|
||||
{
|
||||
desc: "splits area with odd height",
|
||||
area: image.Rect(1, 1, 4, 4),
|
||||
want1: image.Rect(1, 1, 4, 2),
|
||||
want2: image.Rect(1, 2, 4, 4),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got1, got2 := HSplit(tc.area)
|
||||
if diff := pretty.Compare(tc.want1, got1); diff != "" {
|
||||
t.Errorf("HSplit => first value unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
if diff := pretty.Compare(tc.want2, got2); diff != "" {
|
||||
t.Errorf("HSplit => second value unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVSplit(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
area image.Rectangle
|
||||
want1 image.Rectangle
|
||||
want2 image.Rectangle
|
||||
}{
|
||||
{
|
||||
desc: "zero area to begin with",
|
||||
area: image.ZR,
|
||||
want1: image.ZR,
|
||||
want2: image.ZR,
|
||||
},
|
||||
{
|
||||
desc: "splitting results in zero width area",
|
||||
area: image.Rect(1, 1, 2, 2),
|
||||
want1: image.ZR,
|
||||
want2: image.ZR,
|
||||
},
|
||||
{
|
||||
desc: "splits area with even width",
|
||||
area: image.Rect(1, 1, 3, 3),
|
||||
want1: image.Rect(1, 1, 2, 3),
|
||||
want2: image.Rect(2, 1, 3, 3),
|
||||
},
|
||||
{
|
||||
desc: "splits area with odd width",
|
||||
area: image.Rect(1, 1, 4, 4),
|
||||
want1: image.Rect(1, 1, 2, 4),
|
||||
want2: image.Rect(2, 1, 4, 4),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got1, got2 := VSplit(tc.area)
|
||||
if diff := pretty.Compare(tc.want1, got1); diff != "" {
|
||||
t.Errorf("VSplit => first value unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
if diff := pretty.Compare(tc.want2, got2); diff != "" {
|
||||
t.Errorf("VSplit => second value unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExcludeBorder(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
area image.Rectangle
|
||||
want image.Rectangle
|
||||
}{
|
||||
{
|
||||
desc: "zero area to begin with",
|
||||
area: image.ZR,
|
||||
want: image.ZR,
|
||||
},
|
||||
{
|
||||
desc: "excluding results in zero area",
|
||||
area: image.Rect(1, 1, 2, 2),
|
||||
want: image.ZR,
|
||||
},
|
||||
{
|
||||
desc: "excludes border",
|
||||
area: image.Rect(1, 1, 4, 5),
|
||||
want: image.Rect(2, 2, 3, 4),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := ExcludeBorder(tc.area)
|
||||
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||
t.Errorf("ExcludeBorder => unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,12 @@ canvases assigned to the placed widgets.
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/area"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminalapi"
|
||||
)
|
||||
|
||||
@ -34,6 +37,12 @@ type Container struct {
|
||||
opts *options
|
||||
}
|
||||
|
||||
// String represents the container metadata in a human readable format.
|
||||
// Implements fmt.Stringer.
|
||||
func (c *Container) String() string {
|
||||
return fmt.Sprintf("Container@%p{parent:%p, first:%p, second:%p, area:%+v}", c, c.parent, c.first, c.second, c.area)
|
||||
}
|
||||
|
||||
// New returns a new root container that will use the provided terminal and
|
||||
// applies the provided options.
|
||||
func New(t terminalapi.Terminal, opts ...Option) *Container {
|
||||
@ -51,10 +60,26 @@ func New(t terminalapi.Terminal, opts ...Option) *Container {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the parent container of this container.
|
||||
// Returns nil if this container is the root of the container tree.
|
||||
// newChild creates a new child container of the given parent.
|
||||
func newChild(parent *Container, area image.Rectangle, opts ...Option) *Container {
|
||||
o := &options{}
|
||||
for _, opt := range opts {
|
||||
opt.set(o)
|
||||
}
|
||||
|
||||
return &Container{
|
||||
parent: parent,
|
||||
term: parent.term,
|
||||
area: area,
|
||||
opts: o,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the parent container of this container and applies the provided
|
||||
// options to the parent container. Returns nil if this container is the root
|
||||
// of the container tree.
|
||||
func (c *Container) Parent(opts ...Option) *Container {
|
||||
if c == nil || c.parent == nil {
|
||||
if c.parent == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -65,6 +90,30 @@ func (c *Container) Parent(opts ...Option) *Container {
|
||||
return p
|
||||
}
|
||||
|
||||
// hasBorder determines if this container has a border.
|
||||
func (c *Container) hasBorder() bool {
|
||||
return c.opts.border != draw.LineStyleNone
|
||||
}
|
||||
|
||||
// usable returns the usable area in this container.
|
||||
// This depends on whether the container has a border, etc.
|
||||
func (c *Container) usable() image.Rectangle {
|
||||
if c.hasBorder() {
|
||||
return area.ExcludeBorder(c.area)
|
||||
} else {
|
||||
return c.area
|
||||
}
|
||||
}
|
||||
|
||||
// split splits the container's usable area into child areas.
|
||||
func (c *Container) split() (image.Rectangle, image.Rectangle) {
|
||||
if ar := c.usable(); c.opts.split == splitTypeHorizontal {
|
||||
return area.HSplit(ar)
|
||||
} else {
|
||||
return area.VSplit(ar)
|
||||
}
|
||||
}
|
||||
|
||||
// First returns the first sub container of this container.
|
||||
// This is the left sub container when using SplitVertical() or the top sub
|
||||
// container when using SplitHorizontal().
|
||||
@ -73,7 +122,7 @@ func (c *Container) Parent(opts ...Option) *Container {
|
||||
// Returns nil if this container contains a widget, containers with widgets
|
||||
// cannot have sub containers.
|
||||
func (c *Container) First(opts ...Option) *Container {
|
||||
if c == nil || c.opts.widget != nil {
|
||||
if c.opts.widget != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -84,8 +133,8 @@ func (c *Container) First(opts ...Option) *Container {
|
||||
return child
|
||||
}
|
||||
|
||||
c.first = New(c.term, opts...)
|
||||
c.first.parent = c
|
||||
ar, _ := c.split()
|
||||
c.first = newChild(c, ar, opts...)
|
||||
return c.first
|
||||
}
|
||||
|
||||
@ -97,7 +146,7 @@ func (c *Container) First(opts ...Option) *Container {
|
||||
// Returns nil if this container contains a widget, containers with widgets
|
||||
// cannot have sub containers.
|
||||
func (c *Container) Second(opts ...Option) *Container {
|
||||
if c == nil || c.opts.widget != nil {
|
||||
if c.opts.widget != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -108,13 +157,68 @@ func (c *Container) Second(opts ...Option) *Container {
|
||||
return child
|
||||
}
|
||||
|
||||
c.second = New(c.term, opts...)
|
||||
c.second.parent = c
|
||||
_, ar := c.split()
|
||||
c.second = newChild(c, ar, opts...)
|
||||
return c.second
|
||||
}
|
||||
|
||||
// Draw requests all widgets in this and all sub containers to draw on their
|
||||
// respective canvases.
|
||||
func (c *Container) Draw() error {
|
||||
return errors.New("unimplemented")
|
||||
// Root returns the root container and applies the provided options to the root
|
||||
// container.
|
||||
func (c *Container) Root(opts ...Option) *Container {
|
||||
for p := c.Parent(); p != nil; p = c.Parent() {
|
||||
c = p
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt.set(c.opts)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// draw draws this container and its widget.
|
||||
// TODO(mum4k): Draw the widget.
|
||||
func (c *Container) draw() error {
|
||||
// TODO(mum4k): Should be verified against the min size reported by the
|
||||
// widget.
|
||||
if us := c.usable(); us.Dx() < 1 || us.Dy() < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cvs, err := canvas.New(c.area)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.hasBorder() {
|
||||
ar, err := area.FromSize(cvs.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := draw.Box(cvs, ar, c.opts.border); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return cvs.Apply(c.term)
|
||||
}
|
||||
|
||||
// Draw draws this container and all of its sub containers.
|
||||
func (c *Container) Draw() error {
|
||||
// TODO(mum4k): Handle resize or split to area too small.
|
||||
// TODO(mum4k): Propagate error.
|
||||
// TODO(mum4k): Don't require .Root() at the end.
|
||||
drawTree(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawTree implements pre-order BST walk through the containers and draws each
|
||||
// visited container.
|
||||
func drawTree(c *Container) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
if err := c.draw(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
drawTree(c.first)
|
||||
drawTree(c.second)
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminal/faketerm"
|
||||
)
|
||||
|
||||
// Example demonstrates how to use the Container API.
|
||||
func Example() {
|
||||
New( // Create the root container.
|
||||
@ -13,7 +24,272 @@ func Example() {
|
||||
).Parent().Second( // Right side on the top.
|
||||
HorizontalAlignRight(),
|
||||
PlaceWidget( /* widget = */ nil),
|
||||
).Parent().Parent().Second( // Bottom half of the terminal.
|
||||
).Root().Second( // Bottom half of the terminal.
|
||||
PlaceWidget( /* widget = */ nil),
|
||||
)
|
||||
).Root()
|
||||
}
|
||||
|
||||
func TestParentAndRoot(t *testing.T) {
|
||||
ft := faketerm.MustNew(image.Point{1, 1})
|
||||
tests := []struct {
|
||||
desc string
|
||||
container *Container
|
||||
// Arg is the container defined above.
|
||||
want func(c *Container) *Container
|
||||
}{
|
||||
{
|
||||
desc: "root container has no parent",
|
||||
container: New(ft),
|
||||
want: func(c *Container) *Container {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "returns the parent",
|
||||
container: New(ft).First(),
|
||||
want: func(c *Container) *Container {
|
||||
return c.Root()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if got := tc.container.Parent(); got != tc.want(tc.container) {
|
||||
t.Errorf("Parent => unexpected container\n got: %v\n want: %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mustCanvas returns a new canvas or panics.
|
||||
func mustCanvas(area image.Rectangle) *canvas.Canvas {
|
||||
cvs, err := canvas.New(area)
|
||||
if err != nil {
|
||||
log.Fatalf("canvas.New => unexpected error: %v", err)
|
||||
}
|
||||
return cvs
|
||||
}
|
||||
|
||||
// mustBox draws box on the canvas or panics.
|
||||
func mustBox(c *canvas.Canvas, box image.Rectangle, ls draw.LineStyle, opts ...cell.Option) {
|
||||
if err := draw.Box(c, box, ls, opts...); err != nil {
|
||||
log.Fatalf("draw.Box => unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// mustApply applies the canvas on the terminal or panics.
|
||||
func mustApply(c *canvas.Canvas, t *faketerm.Terminal) {
|
||||
if err := c.Apply(t); err != nil {
|
||||
log.Fatalf("canvas.Apply => unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDraw(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
termSize image.Point
|
||||
container func(ft *faketerm.Terminal) *Container
|
||||
want func(size image.Point) *faketerm.Terminal
|
||||
wantErr bool
|
||||
}{
|
||||
// {
|
||||
// desc: "empty container",
|
||||
// termSize: image.Point{10, 10},
|
||||
// container: func(ft *faketerm.Terminal) *Container {
|
||||
// return New(ft)
|
||||
// },
|
||||
// want: func(size image.Point) *faketerm.Terminal {
|
||||
// return faketerm.MustNew(size)
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// desc: "container with a border",
|
||||
// termSize: image.Point{10, 10},
|
||||
// container: func(ft *faketerm.Terminal) *Container {
|
||||
// return New(
|
||||
// ft,
|
||||
// Border(draw.LineStyleLight),
|
||||
// )
|
||||
// },
|
||||
// want: func(size image.Point) *faketerm.Terminal {
|
||||
// ft := faketerm.MustNew(size)
|
||||
// cvs := mustCanvas(image.Rect(0, 0, 10, 10))
|
||||
// mustBox(cvs, image.Rect(0, 0, 10, 10), draw.LineStyleLight)
|
||||
// mustApply(cvs, ft)
|
||||
// return ft
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// desc: "horizontal split, children have borders",
|
||||
// termSize: image.Point{10, 10},
|
||||
// container: func(ft *faketerm.Terminal) *Container {
|
||||
// return New(
|
||||
// ft,
|
||||
// SplitHorizontal(),
|
||||
// ).First(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root().Second(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root()
|
||||
// },
|
||||
// want: func(size image.Point) *faketerm.Terminal {
|
||||
// ft := faketerm.MustNew(size)
|
||||
// cvs := mustCanvas(image.Rect(0, 0, 10, 10))
|
||||
// mustBox(cvs, image.Rect(0, 0, 10, 5), draw.LineStyleLight)
|
||||
// mustBox(cvs, image.Rect(0, 5, 10, 10), draw.LineStyleLight)
|
||||
// mustApply(cvs, ft)
|
||||
// return ft
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// desc: "horizontal split, parent and children have borders",
|
||||
// termSize: image.Point{10, 10},
|
||||
// container: func(ft *faketerm.Terminal) *Container {
|
||||
// return New(
|
||||
// ft,
|
||||
// SplitHorizontal(),
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).First(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root().Second(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root()
|
||||
// },
|
||||
// want: func(size image.Point) *faketerm.Terminal {
|
||||
// ft := faketerm.MustNew(size)
|
||||
// cvs := mustCanvas(image.Rect(0, 0, 10, 10))
|
||||
// mustBox(cvs, image.Rect(0, 0, 10, 10), draw.LineStyleLight)
|
||||
// mustBox(cvs, image.Rect(1, 1, 9, 5), draw.LineStyleLight)
|
||||
// mustBox(cvs, image.Rect(1, 5, 9, 9), draw.LineStyleLight)
|
||||
// mustApply(cvs, ft)
|
||||
// return ft
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// desc: "vertical split, children have borders",
|
||||
// termSize: image.Point{10, 10},
|
||||
// container: func(ft *faketerm.Terminal) *Container {
|
||||
// return New(
|
||||
// ft,
|
||||
// SplitVertical(),
|
||||
// ).First(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root().Second(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root()
|
||||
// },
|
||||
// want: func(size image.Point) *faketerm.Terminal {
|
||||
// ft := faketerm.MustNew(size)
|
||||
// cvs := mustCanvas(image.Rect(0, 0, 10, 10))
|
||||
// mustBox(cvs, image.Rect(0, 0, 5, 10), draw.LineStyleLight)
|
||||
// mustBox(cvs, image.Rect(5, 0, 10, 10), draw.LineStyleLight)
|
||||
// mustApply(cvs, ft)
|
||||
// return ft
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// desc: "vertical split, parent and children have borders",
|
||||
// termSize: image.Point{10, 10},
|
||||
// container: func(ft *faketerm.Terminal) *Container {
|
||||
// return New(
|
||||
// ft,
|
||||
// SplitVertical(),
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).First(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root().Second(
|
||||
// Border(draw.LineStyleLight),
|
||||
// ).Root()
|
||||
// },
|
||||
// want: func(size image.Point) *faketerm.Terminal {
|
||||
// ft := faketerm.MustNew(size)
|
||||
// cvs := mustCanvas(image.Rect(0, 0, 10, 10))
|
||||
// mustBox(cvs, image.Rect(0, 0, 10, 10), draw.LineStyleLight)
|
||||
// mustBox(cvs, image.Rect(1, 1, 5, 9), draw.LineStyleLight)
|
||||
// mustBox(cvs, image.Rect(5, 1, 9, 9), draw.LineStyleLight)
|
||||
// mustApply(cvs, ft)
|
||||
// return ft
|
||||
// },
|
||||
// },
|
||||
{
|
||||
desc: "multi level split",
|
||||
termSize: image.Point{10, 11},
|
||||
container: func(ft *faketerm.Terminal) *Container {
|
||||
return New(
|
||||
ft,
|
||||
SplitVertical(),
|
||||
).First(
|
||||
SplitHorizontal(),
|
||||
).First(
|
||||
Border(draw.LineStyleLight),
|
||||
).Parent().Second(
|
||||
SplitHorizontal(),
|
||||
).First(
|
||||
Border(draw.LineStyleLight),
|
||||
).Parent().Second(
|
||||
Border(draw.LineStyleLight),
|
||||
).Root().Second(
|
||||
Border(draw.LineStyleLight),
|
||||
).Root()
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
cvs := mustCanvas(image.Rect(0, 0, 10, 11))
|
||||
mustBox(cvs, image.Rect(0, 0, 5, 5), draw.LineStyleLight)
|
||||
mustBox(cvs, image.Rect(0, 5, 5, 8), draw.LineStyleLight)
|
||||
mustBox(cvs, image.Rect(0, 8, 5, 11), draw.LineStyleLight)
|
||||
mustBox(cvs, image.Rect(5, 0, 10, 11), draw.LineStyleLight)
|
||||
mustApply(cvs, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "container height too low",
|
||||
termSize: image.Point{4, 7},
|
||||
container: func(ft *faketerm.Terminal) *Container {
|
||||
return New(
|
||||
ft,
|
||||
SplitHorizontal(),
|
||||
).First(
|
||||
Border(draw.LineStyleLight),
|
||||
).Parent().Second(
|
||||
SplitHorizontal(),
|
||||
).First(
|
||||
Border(draw.LineStyleLight),
|
||||
).Parent().Second(
|
||||
Border(draw.LineStyleLight),
|
||||
).Root()
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
cvs := mustCanvas(image.Rect(0, 0, 4, 7))
|
||||
mustBox(cvs, image.Rect(0, 0, 4, 3), draw.LineStyleLight)
|
||||
mustApply(cvs, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got, err := faketerm.New(tc.termSize)
|
||||
if err != nil {
|
||||
t.Fatalf("faketerm.New => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = tc.container(got).Draw()
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("Draw => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if diff := faketerm.Diff(tc.want(tc.termSize), got); diff != "" {
|
||||
t.Errorf("Draw => %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ var lineStyleNames = map[LineStyle]string{
|
||||
}
|
||||
|
||||
const (
|
||||
lineStyleUnknown LineStyle = iota
|
||||
LineStyleNone LineStyle = iota
|
||||
LineStyleLight
|
||||
)
|
||||
|
||||
|
44
experimental/boxes.go
Normal file
44
experimental/boxes.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Binary boxes just creates containers with borders.
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash/container"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminal/termbox"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t, err := termbox.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer t.Close()
|
||||
|
||||
c := container.New(
|
||||
t,
|
||||
container.SplitVertical(),
|
||||
).First(
|
||||
container.SplitHorizontal(),
|
||||
).First(
|
||||
container.Border(draw.LineStyleLight),
|
||||
).Parent().Second(
|
||||
container.SplitHorizontal(),
|
||||
).First(
|
||||
container.Border(draw.LineStyleLight),
|
||||
).Parent().Second(
|
||||
container.Border(draw.LineStyleLight),
|
||||
).Root().Second(
|
||||
container.Border(draw.LineStyleLight),
|
||||
).Root()
|
||||
|
||||
if err := c.Draw(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := t.Flush(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
40
terminal/faketerm/diff.go
Normal file
40
terminal/faketerm/diff.go
Normal file
@ -0,0 +1,40 @@
|
||||
package faketerm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// diff.go provides functions that highlight differences between fake terminals.
|
||||
|
||||
// Diff compares the two terminals, returning an empty string if there is not
|
||||
// difference. If a difference is found, returns a human readable description
|
||||
// of the differences.
|
||||
func Diff(want, got *Terminal) string {
|
||||
if reflect.DeepEqual(want, got) {
|
||||
return ""
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
b.WriteString("found differences between the two fake terminals.\n")
|
||||
b.WriteString(" got:\n")
|
||||
b.WriteString(got.String())
|
||||
b.WriteString(" want:\n")
|
||||
b.WriteString(want.String())
|
||||
b.WriteString(" diff (unexpected cells highlighted with rune '࿃'):\n")
|
||||
|
||||
size := got.Size()
|
||||
for row := 0; row < size.Y; row++ {
|
||||
for col := 0; col < size.X; col++ {
|
||||
r := got.BackBuffer()[col][row].Rune
|
||||
if r != want.BackBuffer()[col][row].Rune {
|
||||
r = '࿃'
|
||||
} else if r == 0 {
|
||||
r = ' '
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
return b.String()
|
||||
}
|
@ -51,13 +51,22 @@ func New(size image.Point, opts ...Option) (*Terminal, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// MustNew is like New, but panics on all errors.
|
||||
func MustNew(size image.Point, opts ...Option) *Terminal {
|
||||
ft, err := New(size, opts...)
|
||||
if err != nil {
|
||||
log.Fatalf("New => unexpected error: %v", err)
|
||||
}
|
||||
return ft
|
||||
}
|
||||
|
||||
// BackBuffer returns the back buffer of the fake terminal.
|
||||
func (t *Terminal) BackBuffer() cell.Buffer {
|
||||
return t.buffer
|
||||
}
|
||||
|
||||
// String prints out the buffer into a string.
|
||||
// TODO(mum4k): Support printing of options.
|
||||
// This includes the cell runes only, cell options are ignored.
|
||||
// Implements fmt.Stringer.
|
||||
func (t *Terminal) String() string {
|
||||
size := t.Size()
|
||||
|
Loading…
x
Reference in New Issue
Block a user