SIGN IN SIGN UP
charmbracelet / lipgloss UNCLAIMED

Style definitions for nice terminal layouts 👄

0 0 1 Go
2021-03-25 20:43:40 -04:00
package lipgloss
import (
"strings"
"github.com/muesli/reflow/ansi"
"github.com/muesli/termenv"
)
// Border contains a series of values which comprise the various parts of a
// border.
type Border struct {
Top string
Bottom string
Left string
Right string
TopLeft string
TopRight string
BottomRight string
BottomLeft string
}
var (
noBorder = Border{}
normalBorder = Border{
Top: "─",
Bottom: "─",
Left: "│",
Right: "│",
TopLeft: "┌",
TopRight: "┐",
BottomLeft: "└",
BottomRight: "┘",
}
roundedBorder = Border{
Top: "─",
Bottom: "─",
Left: "│",
Right: "│",
TopLeft: "╭",
TopRight: "╮",
BottomLeft: "╰",
BottomRight: "╯",
}
thickBorder = Border{
Top: "━",
Bottom: "━",
Left: "┃",
Right: "┃",
TopLeft: "┏",
TopRight: "┓",
BottomLeft: "┗",
BottomRight: "┛",
}
doubleBorder = Border{
Top: "═",
Bottom: "═",
Left: "║",
Right: "║",
TopLeft: "╔",
TopRight: "╗",
BottomLeft: "╚",
BottomRight: "╝",
}
)
// NormalBorder returns a standard-type border with a normal weight and 90
// degree corners.
func NormalBorder() Border {
return normalBorder
}
// RoundedBorder returns a border with rounded corners.
func RoundedBorder() Border {
return roundedBorder
}
// Thick border returns a border that's thicker than the one returned by
// NormalBorder.
func ThickBorder() Border {
return thickBorder
}
// DoubleBorder returns a border comprised of two thin strokes.
func DoubleBorder() Border {
return doubleBorder
}
func (s Style) applyBorder(str string) string {
var (
topSet = s.isSet(borderTopKey)
rightSet = s.isSet(borderRightKey)
bottomSet = s.isSet(borderBottomKey)
leftSet = s.isSet(borderLeftKey)
border = s.getAsBorderStyle(borderStyleKey)
hasTop = s.getAsBool(borderTopKey, false)
hasRight = s.getAsBool(borderRightKey, false)
hasBottom = s.getAsBool(borderBottomKey, false)
hasLeft = s.getAsBool(borderLeftKey, false)
topFGColor = s.getAsColor(borderTopFGColorKey)
rightFGColor = s.getAsColor(borderRightFGColorKey)
bottomFGColor = s.getAsColor(borderBottomFGColorKey)
leftFGColor = s.getAsColor(borderLeftFGColorKey)
topBGColor = s.getAsColor(borderTopBGColorKey)
rightBGColor = s.getAsColor(borderRightBGColorKey)
bottomBGColor = s.getAsColor(borderBottomBGColorKey)
leftBGColor = s.getAsColor(borderLeftBGColorKey)
)
// If a border is set and no sides have been specifically turned on or off
// render borders on all sides.
if border != noBorder && !(topSet || rightSet || bottomSet || leftSet) {
hasTop = true
hasRight = true
hasBottom = true
hasLeft = true
}
// If no border is set or all borders are been disabled, abort.
if border == noBorder || (!hasTop && !hasRight && !hasBottom && !hasLeft) {
return str
}
lines, width := getLines(str)
if hasLeft {
width += ansi.PrintableRuneWidth(border.Left)
}
// Figure out which glyphs we should be using for our corners based on
// which sides are set to show
border.TopLeft = getCorner(hasTop, hasLeft, border.Top, border.Left, border.TopLeft)
border.TopRight = getCorner(hasTop, hasRight, border.Top, border.Right, border.TopRight)
border.BottomLeft = getCorner(hasBottom, hasLeft, border.Bottom, border.Left, border.BottomLeft)
border.BottomRight = getCorner(hasBottom, hasRight, border.Bottom, border.Right, border.BottomRight)
var out strings.Builder
// Render top
if hasTop {
top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
top = styleBorder(top, topFGColor, topBGColor)
out.WriteString(top)
out.WriteRune('\n')
}
// Render sides
for i, l := range lines {
if hasLeft {
out.WriteString(styleBorder(border.Left, leftFGColor, leftBGColor))
}
out.WriteString(l)
if hasRight {
out.WriteString(styleBorder(border.Right, rightFGColor, rightBGColor))
}
if i < len(lines)-1 {
out.WriteRune('\n')
}
}
// Render bottom
if hasBottom {
bottom := renderHorizontalEdge(border.BottomLeft, border.Top, border.BottomRight, width)
bottom = styleBorder(bottom, bottomFGColor, bottomBGColor)
out.WriteRune('\n')
out.WriteString(bottom)
}
return out.String()
}
// Figure out which corner element we should be using based on which sides are
// hidden and visible
func getCorner(showH, showV bool, horiz, vert, corner string) string {
switch {
case showH && !showV:
return horiz
case !showH && showV:
return vert
case !showH && !showV:
return ""
default:
return corner
}
}
// Render the horizontal (top or bottom) portion of a border.
func renderHorizontalEdge(left, middle, right string, width int) string {
if width < 1 {
return ""
}
if middle == "" {
middle = " "
}
leftWidth := ansi.PrintableRuneWidth(left)
midWidth := ansi.PrintableRuneWidth(middle)
rightWidth := ansi.PrintableRuneWidth(right)
out := strings.Builder{}
out.WriteString(left)
for i := leftWidth + rightWidth; i <= width; i += midWidth {
out.WriteString(middle)
}
out.WriteString(right)
return out.String()
}
// Apply foreground and background styling to a border.
func styleBorder(border string, fg, bg ColorType) string {
if fg == noColor && bg == noColor {
return border
}
var style = termenv.Style{}
if fg != noColor {
style = style.Foreground(color(fg.value()))
}
if bg != noColor {
style = style.Background(color(bg.value()))
}
return style.Styled(border)
}