ginger/gim/box.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])
}