mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-08-17 18:00:46 +03:00
176 lines
4.0 KiB
Go
176 lines
4.0 KiB
Go
package lipgloss
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/muesli/reflow/ansi"
|
|
)
|
|
|
|
// JoinHorizontal is a utility function for horizontally joining two
|
|
// potentially multi-lined strings along a vertical axis. The first argument is
|
|
// the position, with 0 being all the way at the top and 1 being all the way
|
|
// at the bottom.
|
|
//
|
|
// If you just want to align to the top, center or bottom you may as well just
|
|
// use the helper constants Top, Center, and Bottom.
|
|
//
|
|
// Example:
|
|
//
|
|
// blockB := "...\n...\n..."
|
|
// blockA := "...\n...\n...\n...\n..."
|
|
//
|
|
// // Join 20% from the top
|
|
// str := lipgloss.JoinHorizontal(0.2, blockA, blockB)
|
|
//
|
|
// // Join on the top edge
|
|
// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB)
|
|
func JoinHorizontal(pos Position, strs ...string) string {
|
|
if len(strs) == 0 {
|
|
return ""
|
|
}
|
|
if len(strs) == 1 {
|
|
return strs[0]
|
|
}
|
|
|
|
var (
|
|
// Groups of strings broken into multiple lines
|
|
blocks = make([][]string, len(strs))
|
|
|
|
// Max line widths for the above text blocks
|
|
maxWidths = make([]int, len(strs))
|
|
|
|
// Height of the tallest block
|
|
maxHeight int
|
|
)
|
|
|
|
// Break text blocks into lines and get max widths for each text block
|
|
for i, str := range strs {
|
|
blocks[i], maxWidths[i] = getLines(str)
|
|
if len(blocks[i]) > maxHeight {
|
|
maxHeight = len(blocks[i])
|
|
}
|
|
}
|
|
|
|
// Add extra lines to make each side the same height
|
|
for i := range blocks {
|
|
if len(blocks[i]) >= maxHeight {
|
|
continue
|
|
}
|
|
|
|
extraLines := make([]string, maxHeight-len(blocks[i]))
|
|
|
|
switch pos { //nolint:exhaustive
|
|
case Top:
|
|
blocks[i] = append(blocks[i], extraLines...)
|
|
|
|
case Bottom:
|
|
blocks[i] = append(extraLines, blocks[i]...)
|
|
|
|
default: // Somewhere in the middle
|
|
n := len(extraLines)
|
|
split := int(math.Round(float64(n) * pos.value()))
|
|
top := n - split
|
|
bottom := n - top
|
|
|
|
blocks[i] = append(extraLines[top:], blocks[i]...)
|
|
blocks[i] = append(blocks[i], extraLines[bottom:]...)
|
|
}
|
|
}
|
|
|
|
// Merge lines
|
|
var b strings.Builder
|
|
for i := range blocks[0] { // remember, all blocks have the same number of members now
|
|
for j, block := range blocks {
|
|
b.WriteString(block[i])
|
|
|
|
// Also make lines the same length
|
|
b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.PrintableRuneWidth(block[i])))
|
|
}
|
|
if i < len(blocks[0])-1 {
|
|
b.WriteRune('\n')
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// JoinVertical is a utility function for vertically joining two potentially
|
|
// multi-lined strings along a horizontal axis. The first argument is the
|
|
// position, with 0 being all the way to the left and 1 being all the way to
|
|
// the right.
|
|
//
|
|
// If you just want to align to the left, right or center you may as well just
|
|
// use the helper constants Left, Center, and Right.
|
|
//
|
|
// Example:
|
|
//
|
|
// blockB := "...\n...\n..."
|
|
// blockA := "...\n...\n...\n...\n..."
|
|
//
|
|
// // Join 20% from the top
|
|
// str := lipgloss.JoinVertical(0.2, blockA, blockB)
|
|
//
|
|
// // Join on the right edge
|
|
// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB)
|
|
func JoinVertical(pos Position, strs ...string) string {
|
|
if len(strs) == 0 {
|
|
return ""
|
|
}
|
|
if len(strs) == 1 {
|
|
return strs[0]
|
|
}
|
|
|
|
var (
|
|
blocks = make([][]string, len(strs))
|
|
maxWidth int
|
|
)
|
|
|
|
for i := range strs {
|
|
var w int
|
|
blocks[i], w = getLines(strs[i])
|
|
if w > maxWidth {
|
|
maxWidth = w
|
|
}
|
|
}
|
|
|
|
var b strings.Builder
|
|
for i, block := range blocks {
|
|
for j, line := range block {
|
|
w := maxWidth - ansi.PrintableRuneWidth(line)
|
|
|
|
switch pos { //nolint:exhaustive
|
|
case Left:
|
|
b.WriteString(line)
|
|
b.WriteString(strings.Repeat(" ", w))
|
|
|
|
case Right:
|
|
b.WriteString(strings.Repeat(" ", w))
|
|
b.WriteString(line)
|
|
|
|
default: // Somewhere in the middle
|
|
if w < 1 {
|
|
b.WriteString(line)
|
|
break
|
|
}
|
|
|
|
split := int(math.Round(float64(w) * pos.value()))
|
|
right := w - split
|
|
left := w - right
|
|
|
|
b.WriteString(strings.Repeat(" ", left))
|
|
b.WriteString(line)
|
|
b.WriteString(strings.Repeat(" ", right))
|
|
}
|
|
|
|
// Write a newline as long as we're not on the last line of the
|
|
// last block.
|
|
if !(i == len(blocks)-1 && j == len(block)-1) {
|
|
b.WriteRune('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|