1package lipgloss
2
3import (
4 "math"
5 "strings"
6
7 "github.com/charmbracelet/x/ansi"
8)
9
10// JoinHorizontal is a utility function for horizontally joining two
11// potentially multi-lined strings along a vertical axis. The first argument is
12// the position, with 0 being all the way at the top and 1 being all the way
13// at the bottom.
14//
15// If you just want to align to the top, center or bottom you may as well just
16// use the helper constants Top, Center, and Bottom.
17//
18// Example:
19//
20// blockB := "...\n...\n..."
21// blockA := "...\n...\n...\n...\n..."
22//
23// // Join 20% from the top
24// str := lipgloss.JoinHorizontal(0.2, blockA, blockB)
25//
26// // Join on the top edge
27// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB)
28func JoinHorizontal(pos Position, strs ...string) string {
29 if len(strs) == 0 {
30 return ""
31 }
32 if len(strs) == 1 {
33 return strs[0]
34 }
35
36 var (
37 // Groups of strings broken into multiple lines
38 blocks = make([][]string, len(strs))
39
40 // Max line widths for the above text blocks
41 maxWidths = make([]int, len(strs))
42
43 // Height of the tallest block
44 maxHeight int
45 )
46
47 // Break text blocks into lines and get max widths for each text block
48 for i, str := range strs {
49 blocks[i], maxWidths[i] = getLines(str)
50 if len(blocks[i]) > maxHeight {
51 maxHeight = len(blocks[i])
52 }
53 }
54
55 // Add extra lines to make each side the same height
56 for i := range blocks {
57 if len(blocks[i]) >= maxHeight {
58 continue
59 }
60
61 extraLines := make([]string, maxHeight-len(blocks[i]))
62
63 switch pos { //nolint:exhaustive
64 case Top:
65 blocks[i] = append(blocks[i], extraLines...)
66
67 case Bottom:
68 blocks[i] = append(extraLines, blocks[i]...)
69
70 default: // Somewhere in the middle
71 n := len(extraLines)
72 split := int(math.Round(float64(n) * pos.value()))
73 top := n - split
74 bottom := n - top
75
76 blocks[i] = append(extraLines[top:], blocks[i]...)
77 blocks[i] = append(blocks[i], extraLines[bottom:]...)
78 }
79 }
80
81 // Merge lines
82 var b strings.Builder
83 for i := range blocks[0] { // remember, all blocks have the same number of members now
84 for j, block := range blocks {
85 b.WriteString(block[i])
86
87 // Also make lines the same length
88 b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.StringWidth(block[i])))
89 }
90 if i < len(blocks[0])-1 {
91 b.WriteRune('\n')
92 }
93 }
94
95 return b.String()
96}
97
98// JoinVertical is a utility function for vertically joining two potentially
99// multi-lined strings along a horizontal axis. The first argument is the
100// position, with 0 being all the way to the left and 1 being all the way to
101// the right.
102//
103// If you just want to align to the left, right or center you may as well just
104// use the helper constants Left, Center, and Right.
105//
106// Example:
107//
108// blockB := "...\n...\n..."
109// blockA := "...\n...\n...\n...\n..."
110//
111// // Join 20% from the top
112// str := lipgloss.JoinVertical(0.2, blockA, blockB)
113//
114// // Join on the right edge
115// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB)
116func JoinVertical(pos Position, strs ...string) string {
117 if len(strs) == 0 {
118 return ""
119 }
120 if len(strs) == 1 {
121 return strs[0]
122 }
123
124 var (
125 blocks = make([][]string, len(strs))
126 maxWidth int
127 )
128
129 for i := range strs {
130 var w int
131 blocks[i], w = getLines(strs[i])
132 if w > maxWidth {
133 maxWidth = w
134 }
135 }
136
137 var b strings.Builder
138 for i, block := range blocks {
139 for j, line := range block {
140 w := maxWidth - ansi.StringWidth(line)
141
142 switch pos { //nolint:exhaustive
143 case Left:
144 b.WriteString(line)
145 b.WriteString(strings.Repeat(" ", w))
146
147 case Right:
148 b.WriteString(strings.Repeat(" ", w))
149 b.WriteString(line)
150
151 default: // Somewhere in the middle
152 if w < 1 {
153 b.WriteString(line)
154 break
155 }
156
157 split := int(math.Round(float64(w) * pos.value()))
158 right := w - split
159 left := w - right
160
161 b.WriteString(strings.Repeat(" ", left))
162 b.WriteString(line)
163 b.WriteString(strings.Repeat(" ", right))
164 }
165
166 // Write a newline as long as we're not on the last line of the
167 // last block.
168 if !(i == len(blocks)-1 && j == len(block)-1) { //nolint:staticcheck
169 b.WriteRune('\n')
170 }
171 }
172 }
173
174 return b.String()
175}