border.go

 1package layout
 2
 3import (
 4	"fmt"
 5	"strings"
 6
 7	"github.com/charmbracelet/lipgloss"
 8	"github.com/kujtimiihoxha/termai/internal/tui/styles"
 9)
10
11type BorderPosition int
12
13const (
14	TopLeftBorder BorderPosition = iota
15	TopMiddleBorder
16	TopRightBorder
17	BottomLeftBorder
18	BottomMiddleBorder
19	BottomRightBorder
20)
21
22var (
23	ActiveBorder          = styles.Blue
24	InactivePreviewBorder = styles.Grey
25)
26
27func Borderize(content string, active bool, embeddedText map[BorderPosition]string) string {
28	if embeddedText == nil {
29		embeddedText = make(map[BorderPosition]string)
30	}
31	var (
32		thickness = map[bool]lipgloss.Border{
33			true:  lipgloss.Border(lipgloss.ThickBorder()),
34			false: lipgloss.Border(lipgloss.NormalBorder()),
35		}
36		color = map[bool]lipgloss.TerminalColor{
37			true:  ActiveBorder,
38			false: InactivePreviewBorder,
39		}
40		border = thickness[active]
41		style  = lipgloss.NewStyle().Foreground(color[active])
42		width  = lipgloss.Width(content)
43	)
44
45	encloseInSquareBrackets := func(text string) string {
46		if text != "" {
47			return fmt.Sprintf("%s%s%s",
48				style.Render(border.TopRight),
49				text,
50				style.Render(border.TopLeft),
51			)
52		}
53		return text
54	}
55	buildHorizontalBorder := func(leftText, middleText, rightText, leftCorner, inbetween, rightCorner string) string {
56		leftText = encloseInSquareBrackets(leftText)
57		middleText = encloseInSquareBrackets(middleText)
58		rightText = encloseInSquareBrackets(rightText)
59		// Calculate length of border between embedded texts
60		remaining := max(0, width-lipgloss.Width(leftText)-lipgloss.Width(middleText)-lipgloss.Width(rightText))
61		leftBorderLen := max(0, (width/2)-lipgloss.Width(leftText)-(lipgloss.Width(middleText)/2))
62		rightBorderLen := max(0, remaining-leftBorderLen)
63		// Then construct border string
64		s := leftText +
65			style.Render(strings.Repeat(inbetween, leftBorderLen)) +
66			middleText +
67			style.Render(strings.Repeat(inbetween, rightBorderLen)) +
68			rightText
69		// Make it fit in the space available between the two corners.
70		s = lipgloss.NewStyle().
71			Inline(true).
72			MaxWidth(width).
73			Render(s)
74		// Add the corners
75		return style.Render(leftCorner) + s + style.Render(rightCorner)
76	}
77	// Stack top border, content and horizontal borders, and bottom border.
78	return strings.Join([]string{
79		buildHorizontalBorder(
80			embeddedText[TopLeftBorder],
81			embeddedText[TopMiddleBorder],
82			embeddedText[TopRightBorder],
83			border.TopLeft,
84			border.Top,
85			border.TopRight,
86		),
87		lipgloss.NewStyle().
88			BorderForeground(color[active]).
89			Border(border, false, true, false, true).Render(content),
90		buildHorizontalBorder(
91			embeddedText[BottomLeftBorder],
92			embeddedText[BottomMiddleBorder],
93			embeddedText[BottomRightBorder],
94			border.BottomLeft,
95			border.Bottom,
96			border.BottomRight,
97		),
98	}, "\n")
99}