1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-28 13:48:51 +08:00
termdash/draw/hv_line.go
Jakub Sobon 1bc6a7ccd4
Skeleton for function that draws lines.
- tests.
- testdraw wrapper.
- basic functionality that just draws lines, not crossings yet.
2018-06-24 21:49:30 -04:00

180 lines
4.5 KiB
Go

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]
}