158 lines
3.1 KiB
Go
158 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/mediocregopher/ginger/gim/geo"
|
|
"github.com/mediocregopher/ginger/gim/terminal"
|
|
)
|
|
|
|
const (
|
|
boxBorderHoriz = iota
|
|
boxBorderVert
|
|
boxBorderTL
|
|
boxBorderTR
|
|
boxBorderBL
|
|
boxBorderBR
|
|
)
|
|
|
|
var boxDefault = []string{
|
|
"─",
|
|
"│",
|
|
"┌",
|
|
"┐",
|
|
"└",
|
|
"┘",
|
|
}
|
|
|
|
type box struct {
|
|
pos geo.XY
|
|
size geo.XY // if unset, auto-determined
|
|
body string
|
|
|
|
transparent bool
|
|
}
|
|
|
|
func (b box) lines() []string {
|
|
lines := strings.Split(b.body, "\n")
|
|
// if the last line is empty don't include it, it means there was a trailing
|
|
// newline (or the whole string is empty)
|
|
if lines[len(lines)-1] == "" {
|
|
lines = lines[:len(lines)-1]
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func (b box) innerSize() geo.XY {
|
|
if b.size != (geo.XY{}) {
|
|
return b.size
|
|
}
|
|
var size geo.XY
|
|
for _, line := range b.lines() {
|
|
size[1]++
|
|
if l := len(line); l > size[0] {
|
|
size[0] = l
|
|
}
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (b box) rectSize() geo.XY {
|
|
return b.innerSize().Add(geo.XY{2, 2})
|
|
}
|
|
|
|
// edge returns the coordinate of the edge indicated by the given direction (Up,
|
|
// Down, Left, or Right). The coordinate will be for the axis applicable to the
|
|
// direction, so for Left/Right it will be the x coordinate and for Up/Down the
|
|
// y.
|
|
func (b box) rectEdge(dir geo.XY) int {
|
|
size := b.rectSize()
|
|
switch dir {
|
|
case geo.Up:
|
|
return b.pos[1]
|
|
case geo.Down:
|
|
return b.pos[1] + size[1]
|
|
case geo.Left:
|
|
return b.pos[0]
|
|
case geo.Right:
|
|
return b.pos[0] + size[0]
|
|
default:
|
|
panic(fmt.Sprintf("unsupported direction: %#v", dir))
|
|
}
|
|
}
|
|
|
|
func (b box) rectCorner(xDir, yDir geo.XY) geo.XY {
|
|
switch {
|
|
case xDir == geo.Left && yDir == geo.Up:
|
|
return b.pos
|
|
case xDir == geo.Right && yDir == geo.Up:
|
|
size := b.rectSize()
|
|
return b.pos.Add(size.Mul(geo.Right)).Add(geo.XY{-1, 0})
|
|
case xDir == geo.Left && yDir == geo.Down:
|
|
size := b.rectSize()
|
|
return b.pos.Add(size.Mul(geo.Down)).Add(geo.XY{0, -1})
|
|
case xDir == geo.Right && yDir == geo.Down:
|
|
size := b.rectSize()
|
|
return b.pos.Add(size).Add(geo.XY{-1, -1})
|
|
default:
|
|
panic(fmt.Sprintf("unsupported rectCorner args: %v, %v", xDir, yDir))
|
|
}
|
|
}
|
|
|
|
func (b box) draw(term *terminal.Terminal) {
|
|
chars := boxDefault
|
|
pos := b.pos
|
|
size := b.innerSize()
|
|
w, h := size[0], size[1]
|
|
|
|
// draw top line
|
|
term.MoveCursorTo(pos)
|
|
term.Printf(chars[boxBorderTL])
|
|
for i := 0; i < w; i++ {
|
|
term.Printf(chars[boxBorderHoriz])
|
|
}
|
|
term.Printf(chars[boxBorderTR])
|
|
|
|
drawLine := func(line string) {
|
|
pos[1]++
|
|
term.MoveCursorTo(pos)
|
|
term.Printf(chars[boxBorderVert])
|
|
if len(line) > w {
|
|
line = line[:w]
|
|
}
|
|
term.Printf(line)
|
|
if b.transparent {
|
|
term.MoveCursor(geo.XY{w + 1, 0})
|
|
} else {
|
|
term.Printf(strings.Repeat(" ", w-len(line)))
|
|
}
|
|
term.Printf(chars[boxBorderVert])
|
|
}
|
|
|
|
// truncate lines if necessary
|
|
lines := b.lines()
|
|
if len(lines) > h {
|
|
lines = lines[:h]
|
|
}
|
|
|
|
// draw body
|
|
for _, line := range lines {
|
|
drawLine(line)
|
|
}
|
|
|
|
// draw empty lines
|
|
for i := 0; i < h-len(lines); i++ {
|
|
drawLine("")
|
|
}
|
|
|
|
// draw bottom line
|
|
pos[1]++
|
|
term.MoveCursorTo(pos)
|
|
term.Printf(chars[boxBorderBL])
|
|
for i := 0; i < w; i++ {
|
|
term.Printf(chars[boxBorderHoriz])
|
|
}
|
|
term.Printf(chars[boxBorderBR])
|
|
}
|