summaryrefslogtreecommitdiffhomepage
path: root/internal/tui/layout/border.go
blob: 8fe5c430c6d7adc7ffab02c9f8c94a611f54d58e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package layout

import (
	"fmt"
	"strings"

	"github.com/charmbracelet/lipgloss"
	"github.com/kujtimiihoxha/termai/internal/tui/styles"
)

type BorderPosition int

const (
	TopLeftBorder BorderPosition = iota
	TopMiddleBorder
	TopRightBorder
	BottomLeftBorder
	BottomMiddleBorder
	BottomRightBorder
)

var (
	ActiveBorder          = styles.Blue
	InactivePreviewBorder = styles.Grey
)

type BorderOptions struct {
	Active         bool
	EmbeddedText   map[BorderPosition]string
	ActiveColor    lipgloss.TerminalColor
	InactiveColor  lipgloss.TerminalColor
	ActiveBorder   lipgloss.Border
	InactiveBorder lipgloss.Border
}

func Borderize(content string, opts BorderOptions) string {
	if opts.EmbeddedText == nil {
		opts.EmbeddedText = make(map[BorderPosition]string)
	}
	if opts.ActiveColor == nil {
		opts.ActiveColor = ActiveBorder
	}
	if opts.InactiveColor == nil {
		opts.InactiveColor = InactivePreviewBorder
	}
	if opts.ActiveBorder == (lipgloss.Border{}) {
		opts.ActiveBorder = lipgloss.ThickBorder()
	}
	if opts.InactiveBorder == (lipgloss.Border{}) {
		opts.InactiveBorder = lipgloss.NormalBorder()
	}

	var (
		thickness = map[bool]lipgloss.Border{
			true:  opts.ActiveBorder,
			false: opts.InactiveBorder,
		}
		color = map[bool]lipgloss.TerminalColor{
			true:  opts.ActiveColor,
			false: opts.InactiveColor,
		}
		border = thickness[opts.Active]
		style  = lipgloss.NewStyle().Foreground(color[opts.Active])
		width  = lipgloss.Width(content)
	)

	encloseInSquareBrackets := func(text string) string {
		if text != "" {
			return fmt.Sprintf("%s%s%s",
				style.Render(border.TopRight),
				text,
				style.Render(border.TopLeft),
			)
		}
		return text
	}
	buildHorizontalBorder := func(leftText, middleText, rightText, leftCorner, inbetween, rightCorner string) string {
		leftText = encloseInSquareBrackets(leftText)
		middleText = encloseInSquareBrackets(middleText)
		rightText = encloseInSquareBrackets(rightText)
		// Calculate length of border between embedded texts
		remaining := max(0, width-lipgloss.Width(leftText)-lipgloss.Width(middleText)-lipgloss.Width(rightText))
		leftBorderLen := max(0, (width/2)-lipgloss.Width(leftText)-(lipgloss.Width(middleText)/2))
		rightBorderLen := max(0, remaining-leftBorderLen)
		// Then construct border string
		s := leftText +
			style.Render(strings.Repeat(inbetween, leftBorderLen)) +
			middleText +
			style.Render(strings.Repeat(inbetween, rightBorderLen)) +
			rightText
		// Make it fit in the space available between the two corners.
		s = lipgloss.NewStyle().
			Inline(true).
			MaxWidth(width).
			Render(s)
		// Add the corners
		return style.Render(leftCorner) + s + style.Render(rightCorner)
	}
	// Stack top border, content and horizontal borders, and bottom border.
	return strings.Join([]string{
		buildHorizontalBorder(
			opts.EmbeddedText[TopLeftBorder],
			opts.EmbeddedText[TopMiddleBorder],
			opts.EmbeddedText[TopRightBorder],
			border.TopLeft,
			border.Top,
			border.TopRight,
		),
		lipgloss.NewStyle().
			BorderForeground(color[opts.Active]).
			Border(border, false, true, false, true).Render(content),
		buildHorizontalBorder(
			opts.EmbeddedText[BottomLeftBorder],
			opts.EmbeddedText[BottomMiddleBorder],
			opts.EmbeddedText[BottomRightBorder],
			border.BottomLeft,
			border.Bottom,
			border.BottomRight,
		),
	}, "\n")
}