mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-27 13:48:49 +08:00
Skeleton for function that draws lines.
- tests. - testdraw wrapper. - basic functionality that just draws lines, not crossings yet.
This commit is contained in:
parent
31f3862524
commit
1bc6a7ccd4
179
draw/hv_line.go
Normal file
179
draw/hv_line.go
Normal file
@ -0,0 +1,179 @@
|
||||
package draw
|
||||
|
||||
// hv_line.go contains code that draws horizontal and vertical lines.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
)
|
||||
|
||||
// HVLineOption is used to provide options to HVLine().
|
||||
type HVLineOption interface {
|
||||
// set sets the provided option.
|
||||
set(*hVLineOptions)
|
||||
}
|
||||
|
||||
// hVLineOptions stores the provided options.
|
||||
type hVLineOptions struct {
|
||||
cellOpts []cell.Option
|
||||
lineStyle LineStyle
|
||||
}
|
||||
|
||||
// hVLineOption implements HVLineOption.
|
||||
type hVLineOption func(*hVLineOptions)
|
||||
|
||||
// set implements HVLineOption.set.
|
||||
func (o hVLineOption) set(opts *hVLineOptions) {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// DefaultHVLineStyle is the default value for the HVLineStyle option.
|
||||
const DefaultHVLineStyle = LineStyleLight
|
||||
|
||||
// HVLineStyle sets the style of the line.
|
||||
// Defaults to DefaultHVLineStyle.
|
||||
func HVLineStyle(ls LineStyle) HVLineOption {
|
||||
return hVLineOption(func(opts *hVLineOptions) {
|
||||
opts.lineStyle = ls
|
||||
})
|
||||
}
|
||||
|
||||
// HVLineCellOpts sets options on the cells that contain the line.
|
||||
// Where two lines cross, the cell representing the crossing point inherits
|
||||
// options set on the line that was drawn last.
|
||||
func HVLineCellOpts(cOpts ...cell.Option) HVLineOption {
|
||||
return hVLineOption(func(opts *hVLineOptions) {
|
||||
opts.cellOpts = cOpts
|
||||
})
|
||||
}
|
||||
|
||||
// HVLine represents one horizontal or vertical line.
|
||||
type HVLine struct {
|
||||
start image.Point
|
||||
end image.Point
|
||||
}
|
||||
|
||||
// HVLines draws horizontal or vertical lines. Handles drawing of the correct
|
||||
// characters for locations where any two lines cross (e.g. a corner, a T shape
|
||||
// a cross). Each line must be at least one cell long. Both start and end
|
||||
// must be on the same horizontal (same X coordinate) or same vertical (same Y
|
||||
// coordinate) line.
|
||||
func HVLines(c *canvas.Canvas, lines []HVLine, opts ...HVLineOption) error {
|
||||
for _, l := range lines {
|
||||
line, err := newHVLine(c, l.start, l.end, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case line.horizontal():
|
||||
for curX := line.start.X; ; curX++ {
|
||||
cur := image.Point{curX, line.start.Y}
|
||||
if _, err := c.SetCell(cur, line.mainPart, line.opts.cellOpts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if curX == line.end.X {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case line.vertical():
|
||||
for curY := line.start.Y; ; curY++ {
|
||||
cur := image.Point{line.start.X, curY}
|
||||
if _, err := c.SetCell(cur, line.mainPart, line.opts.cellOpts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if curY == line.end.Y {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hVLine represents a line that will be drawn on the canvas.
|
||||
type hVLine struct {
|
||||
// start is the starting point of the line.
|
||||
start image.Point
|
||||
|
||||
// end is the ending point of the line.
|
||||
end image.Point
|
||||
|
||||
// parts are characters that represent parts of the line of the style
|
||||
// chosen in the options.
|
||||
parts map[linePart]rune
|
||||
|
||||
// mainPart is either parts[vLine] or parts[hLine] depending on whether
|
||||
// this is horizontal or vertical line.
|
||||
mainPart rune
|
||||
|
||||
// opts are the options provided in a call to HVLine().
|
||||
opts *hVLineOptions
|
||||
}
|
||||
|
||||
// newHVLine creates a new hVLine instance.
|
||||
// Swaps start and end iof necessary, so that horizontal drawing is always left
|
||||
// to right and vertical is always top down.
|
||||
func newHVLine(c *canvas.Canvas, start, end image.Point, opts ...HVLineOption) (*hVLine, error) {
|
||||
if ar := c.Area(); !start.In(ar) || !end.In(ar) {
|
||||
return nil, fmt.Errorf("both the start%v and the end%v must be in the canvas area: %v", start, end, ar)
|
||||
}
|
||||
|
||||
opt := &hVLineOptions{
|
||||
lineStyle: DefaultHVLineStyle,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o.set(opt)
|
||||
}
|
||||
|
||||
parts, err := lineParts(opt.lineStyle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mainPart rune
|
||||
switch {
|
||||
case start.X != end.X && start.Y != end.Y:
|
||||
return nil, fmt.Errorf("can only draw horizontal (same X coordinates) or vertical (same Y coordinates), got start:%v end:%v", start, end)
|
||||
|
||||
case start.X == end.X && start.Y == end.Y:
|
||||
return nil, fmt.Errorf("the line must at least one cell long, got start%v, end%v", start, end)
|
||||
|
||||
case start.X == end.X:
|
||||
mainPart = parts[vLine]
|
||||
if start.Y > end.Y {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
case start.Y == end.Y:
|
||||
mainPart = parts[hLine]
|
||||
if start.X > end.X {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &hVLine{
|
||||
start: start,
|
||||
end: end,
|
||||
parts: parts,
|
||||
mainPart: mainPart,
|
||||
opts: opt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// horizontal determines if this is a horizontal line.
|
||||
func (hvl *hVLine) horizontal() bool {
|
||||
return hvl.mainPart == hvl.parts[hLine]
|
||||
}
|
||||
|
||||
// vertical determines if this is a vertical line.
|
||||
func (hvl *hVLine) vertical() bool {
|
||||
return hvl.mainPart == hvl.parts[vLine]
|
||||
}
|
659
draw/hv_line_test.go
Normal file
659
draw/hv_line_test.go
Normal file
@ -0,0 +1,659 @@
|
||||
package draw
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/canvas/testcanvas"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/terminal/faketerm"
|
||||
)
|
||||
|
||||
func TestHVLines(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
canvas image.Rectangle // Size of the canvas for the test.
|
||||
lines []HVLine
|
||||
opts []HVLineOption
|
||||
want func(size image.Point) *faketerm.Terminal
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "fails when line isn't horizontal or vertical",
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{1, 1},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails when start isn't in the canvas",
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{2, 0},
|
||||
end: image.Point{0, 0},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails when end isn't in the canvas",
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails when the line has zero length",
|
||||
canvas: image.Rect(0, 0, 1, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 0},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "draws single horizontal line",
|
||||
canvas: image.Rect(0, 0, 3, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "respects line style set explicitly",
|
||||
canvas: image.Rect(0, 0, 3, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
},
|
||||
opts: []HVLineOption{
|
||||
HVLineStyle(LineStyleLight),
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "respects cell options",
|
||||
canvas: image.Rect(0, 0, 3, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
},
|
||||
opts: []HVLineOption{
|
||||
HVLineCellOpts(
|
||||
cell.FgColor(cell.ColorYellow),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
),
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine],
|
||||
cell.FgColor(cell.ColorYellow),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
)
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine],
|
||||
cell.FgColor(cell.ColorYellow),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
)
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine],
|
||||
cell.FgColor(cell.ColorYellow),
|
||||
cell.BgColor(cell.ColorBlue),
|
||||
)
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws single horizontal line, supplied in reverse direction",
|
||||
canvas: image.Rect(0, 0, 3, 1),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{1, 0},
|
||||
end: image.Point{0, 0},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws single vertical line",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{1, 0},
|
||||
end: image.Point{1, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws single vertical line, supplied in reverse direction",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{1, 1},
|
||||
end: image.Point{1, 0},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "parallel horizontal lines don't affect each other",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 1},
|
||||
end: image.Point{2, 1},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "parallel vertical lines don't affect each other",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{1, 0},
|
||||
end: image.Point{1, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "perpendicular lines that don't cross don't affect each other",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{1, 1},
|
||||
end: image.Point{2, 1},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws top left corner",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[topLeftCorner])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws top right corner",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{2, 0},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[topRightCorner])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws bottom left corner",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 2},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[bottomLeftCorner])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws bottom right corner",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{2, 0},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 2},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[bottomRightCorner])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws T horizontal and up",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 2},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{1, 0},
|
||||
end: image.Point{1, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hAndUp])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[hLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws T horizontal and down",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
{
|
||||
start: image.Point{1, 0},
|
||||
end: image.Point{1, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hAndDown])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws T vertical and left",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 1},
|
||||
end: image.Point{2, 1},
|
||||
},
|
||||
{
|
||||
start: image.Point{2, 0},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vAndLeft])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws T vertical and right",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 1},
|
||||
end: image.Point{2, 1},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vAndRight])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws a cross",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
{
|
||||
start: image.Point{0, 1},
|
||||
end: image.Point{2, 1},
|
||||
},
|
||||
{
|
||||
start: image.Point{1, 0},
|
||||
end: image.Point{1, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[hLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vAndH])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "draws multiple crossings",
|
||||
canvas: image.Rect(0, 0, 3, 3),
|
||||
lines: []HVLine{
|
||||
// Three horizontal lines.
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{2, 0},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 1},
|
||||
end: image.Point{2, 1},
|
||||
},
|
||||
{
|
||||
start: image.Point{0, 2},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
// Three vertical lines.
|
||||
{
|
||||
start: image.Point{0, 0},
|
||||
end: image.Point{0, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{1, 0},
|
||||
end: image.Point{1, 2},
|
||||
},
|
||||
{
|
||||
start: image.Point{2, 0},
|
||||
end: image.Point{2, 2},
|
||||
},
|
||||
},
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
parts := lineStyleChars[LineStyleLight]
|
||||
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[topLeftCorner])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hAndDown])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[topRightCorner])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vAndRight])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vAndH])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vAndLeft])
|
||||
|
||||
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[bottomLeftCorner])
|
||||
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hAndUp])
|
||||
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[bottomRightCorner])
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c, err := canvas.New(tc.canvas)
|
||||
if err != nil {
|
||||
t.Fatalf("canvas.New => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = HVLines(c, tc.lines, tc.opts...)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("HVLines => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
if diff := faketerm.Diff(tc.want(c.Size()), got); diff != "" {
|
||||
t.Errorf("HVLines => %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -14,12 +14,17 @@
|
||||
|
||||
package draw
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// line_style.go contains the Unicode characters used for drawing lines of
|
||||
// different styles.
|
||||
|
||||
// lineStyleChars maps the line styles to the corresponding component characters.
|
||||
// Source: http://en.wikipedia.org/wiki/Box-drawing_character.
|
||||
var lineStyleChars = map[LineStyle]map[linePart]rune{
|
||||
LineStyleLight: {
|
||||
hLine: '─',
|
||||
@ -28,9 +33,28 @@ var lineStyleChars = map[LineStyle]map[linePart]rune{
|
||||
topRightCorner: '┐',
|
||||
bottomLeftCorner: '└',
|
||||
bottomRightCorner: '┘',
|
||||
hAndUp: '┴',
|
||||
hAndDown: '┬',
|
||||
vAndLeft: '┤',
|
||||
vAndRight: '├',
|
||||
vAndH: '┼',
|
||||
},
|
||||
}
|
||||
|
||||
// init verifies that all line parts are half-width runes (occupy only one
|
||||
// cell).
|
||||
func init() {
|
||||
for ls, parts := range lineStyleChars {
|
||||
for part, r := range parts {
|
||||
if got := runewidth.RuneWidth(r); got > 1 {
|
||||
panic(fmt.Errorf("line style %v line part %v is a rune %c with width %v, all parts must be half-width runes (width of one)", ls, part, r, got))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mum4k): Check inside init() that all of these are half-width runes.
|
||||
|
||||
// lineParts returns the line component characters for the provided line style.
|
||||
func lineParts(ls LineStyle) (map[linePart]rune, error) {
|
||||
parts, ok := lineStyleChars[ls]
|
||||
@ -80,6 +104,11 @@ var linePartNames = map[linePart]string{
|
||||
topRightCorner: "linePartTopRightCorner",
|
||||
bottomLeftCorner: "linePartBottomLeftCorner",
|
||||
bottomRightCorner: "linePartBottomRightCorner",
|
||||
hAndUp: "linePartHAndUp",
|
||||
hAndDown: "linePartHAndDown",
|
||||
vAndLeft: "linePartVAndLeft",
|
||||
vAndRight: "linePartVAndRight",
|
||||
vAndH: "linePartVAndH",
|
||||
}
|
||||
|
||||
const (
|
||||
@ -89,4 +118,9 @@ const (
|
||||
topRightCorner
|
||||
bottomLeftCorner
|
||||
bottomRightCorner
|
||||
hAndUp
|
||||
hAndDown
|
||||
vAndLeft
|
||||
vAndRight
|
||||
vAndH
|
||||
)
|
||||
|
@ -43,3 +43,10 @@ func MustRectangle(c *canvas.Canvas, r image.Rectangle, opts ...draw.RectangleOp
|
||||
panic(fmt.Sprintf("draw.Rectangle => unexpected error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// MustHVLines draws the vertical / horizontal lines or panics.
|
||||
func MustHVLines(c *canvas.Canvas, lines []draw.HVLine, opts ...draw.HVLineOption) {
|
||||
if err := draw.HVLines(c, lines, opts...); err != nil {
|
||||
panic(fmt.Sprintf("draw.HVLines => unexpected error: %v", err))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user