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

- tests. - testdraw wrapper. - basic functionality that just draws lines, not crossings yet.
180 lines
4.5 KiB
Go
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]
|
|
}
|