Files
terminal/tui/border.go
T

162 lines
4.0 KiB
Go

package tui
import (
"github.com/lixenwraith/color"
"github.com/lixenwraith/terminal"
)
// LineType specifies box drawing character style
type LineType uint8
const (
LineSingle LineType = iota // ┌─┐│└┘
LineDouble // ╔═╗║╚╝
LineRounded // ╭─╮│╰╯
LineHeavy // ┏━┓┃┗┛
LineNone // spaces (invisible border with padding)
)
// boxChars contains box drawing character sets indexed by LineType
var boxChars = [...][6]rune{
LineSingle: {'┌', '─', '┐', '│', '└', '┘'},
LineDouble: {'╔', '═', '╗', '║', '╚', '╝'},
LineRounded: {'╭', '─', '╮', '│', '╰', '╯'},
LineHeavy: {'┏', '━', '┓', '┃', '┗', '┛'},
LineNone: {' ', ' ', ' ', ' ', ' ', ' '},
}
const (
boxTL = 0 // top-left
boxH = 1 // horizontal
boxTR = 2 // top-right
boxV = 3 // vertical
boxBL = 4 // bottom-left
boxBR = 5 // bottom-right
)
// --- Box Rendering ---
// Box draws border around region edge
func (r Region) Box(line LineType, fg color.RGB) {
if r.W < 2 || r.H < 2 {
return
}
if line >= LineType(len(boxChars)) {
line = LineSingle
}
chars := boxChars[line]
bg := color.RGB{} // Transparent (use existing bg)
// Corners
r.Cell(0, 0, chars[boxTL], fg, bg, terminal.AttrNone)
r.Cell(r.W-1, 0, chars[boxTR], fg, bg, terminal.AttrNone)
r.Cell(0, r.H-1, chars[boxBL], fg, bg, terminal.AttrNone)
r.Cell(r.W-1, r.H-1, chars[boxBR], fg, bg, terminal.AttrNone)
// Horizontal edges
for x := 1; x < r.W-1; x++ {
r.Cell(x, 0, chars[boxH], fg, bg, terminal.AttrNone)
r.Cell(x, r.H-1, chars[boxH], fg, bg, terminal.AttrNone)
}
// Vertical edges
for y := 1; y < r.H-1; y++ {
r.Cell(0, y, chars[boxV], fg, bg, terminal.AttrNone)
r.Cell(r.W-1, y, chars[boxV], fg, bg, terminal.AttrNone)
}
}
// BoxFilled draws border and fills interior with background
func (r Region) BoxFilled(line LineType, fg, bg color.RGB) {
// Fill interior first
for y := 1; y < r.H-1; y++ {
for x := 1; x < r.W-1; x++ {
r.Cell(x, y, ' ', fg, bg, terminal.AttrNone)
}
}
// Draw border on top
r.Box(line, fg)
}
// --- Line rendering ---
// HLine draws horizontal line across region width at row y
func (r Region) HLine(y int, line LineType, fg color.RGB) {
if y < 0 || y >= r.H {
return
}
if line >= LineType(len(boxChars)) {
line = LineSingle
}
ch := boxChars[line][boxH]
for x := 0; x < r.W; x++ {
r.Cell(x, y, ch, fg, color.RGB{}, terminal.AttrNone)
}
}
// VLine draws vertical line across region height at column x
func (r Region) VLine(x int, line LineType, fg color.RGB) {
if x < 0 || x >= r.W {
return
}
if line >= LineType(len(boxChars)) {
line = LineSingle
}
ch := boxChars[line][boxV]
for y := 0; y < r.H; y++ {
r.Cell(x, y, ch, fg, color.RGB{}, terminal.AttrNone)
}
}
// Divider draws horizontal line with optional centered label
func (r Region) Divider(y int, label string, line LineType, fg color.RGB) {
if y < 0 || y >= r.H {
return
}
if line >= LineType(len(boxChars)) {
line = LineSingle
}
hChar := boxChars[line][boxH]
// Fill with horizontal line
for x := 0; x < r.W; x++ {
r.Cell(x, y, hChar, fg, color.RGB{}, terminal.AttrNone)
}
// Center label if provided
if label != "" && r.W > 4 {
text := " " + label + " "
textLen := RuneLen(text)
if textLen > r.W-2 {
text = Truncate(text, r.W-2)
textLen = RuneLen(text)
}
startX := (r.W - textLen) / 2
for i, ch := range text {
r.Cell(startX+i, y, ch, fg, color.RGB{}, terminal.AttrBold)
}
}
}
// --- Card rendering ---
// Card draws titled border and returns inner content region
func (r Region) Card(title string, line LineType, fg color.RGB) Region {
r.Box(line, fg)
if title != "" && r.W > 4 {
maxTitleLen := r.W - 4
displayTitle := title
if RuneLen(displayTitle) > maxTitleLen {
displayTitle = Truncate(displayTitle, maxTitleLen)
}
titleX := (r.W - RuneLen(displayTitle) - 2) / 2
r.Text(titleX, 0, " "+displayTitle+" ", fg, color.RGB{}, terminal.AttrBold)
}
return r.Inset(1)
}