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