Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BorderTitle to Style #463

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions borders.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lipgloss

import (
"fmt"
"strings"

"github.com/charmbracelet/x/ansi"
Expand Down Expand Up @@ -229,6 +230,7 @@ func HiddenBorder() Border {
func (s Style) applyBorder(str string) string {
var (
border = s.getBorderStyle()
title = s.getBorderTitle()
hasTop = s.getAsBool(borderTopKey, false)
hasRight = s.getAsBool(borderRightKey, false)
hasBottom = s.getAsBool(borderBottomKey, false)
Expand Down Expand Up @@ -322,7 +324,7 @@ func (s Style) applyBorder(str string) string {

// Render top
if hasTop {
top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, title, width)
top = s.styleBorder(top, topFG, topBG)
out.WriteString(top)
out.WriteRune('\n')
Expand Down Expand Up @@ -360,7 +362,7 @@ func (s Style) applyBorder(str string) string {

// Render bottom
if hasBottom {
bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width)
bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, "", width)
bottom = s.styleBorder(bottom, bottomFG, bottomBG)
out.WriteRune('\n')
out.WriteString(bottom)
Expand All @@ -370,27 +372,38 @@ func (s Style) applyBorder(str string) string {
}

// Render the horizontal (top or bottom) portion of a border.
func renderHorizontalEdge(left, middle, right string, width int) string {
func renderHorizontalEdge(left, middle, right, title string, width int) string {
if middle == "" {
middle = " "
}

leftWidth := ansi.StringWidth(left)
rightWidth := ansi.StringWidth(right)
var (
leftWidth = ansi.StringWidth(left)
midWidth = ansi.StringWidth(middle)
runes = []rune(middle)
j = 0
)

runes := []rune(middle)
j := 0
absWidth := width - leftWidth

out := strings.Builder{}
out.WriteString(left)
for i := leftWidth + rightWidth; i < width+rightWidth; {
out.WriteRune(runes[j])
j++
if j >= len(runes) {
j = 0

// If there is enough space to print the middle segment a space, the title, a space and middle segment
// Print that and remove it from the absolute length of the border.
if title != "" {
if titleLen := ansi.StringWidth(title) + 2 + 2*midWidth; titleLen < absWidth {
out.WriteString(fmt.Sprintf("%s %s %s", middle, title, middle))
absWidth -= titleLen
}
}

for i := 0; i < absWidth; {
out.WriteRune(runes[j])
j = (j + 1) % len(runes)
i += ansi.StringWidth(string(runes[j]))
}

out.WriteString(right)

return out.String()
Expand Down
69 changes: 68 additions & 1 deletion borders_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package lipgloss

import "testing"
import (
"strings"
"testing"

"github.com/charmbracelet/x/ansi"
)

func TestStyle_GetBorderSizes(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -94,3 +99,65 @@ func TestStyle_GetBorderSizes(t *testing.T) {
})
}
}

func TestBorderStyle(t *testing.T) {
tests := []struct {
name string
title string
expected string
}{
{
name: "standard case",
title: "Test",
expected: strings.TrimSpace(`
┌─ Test ───┐
│ │
│ │
│ │
│ │
└──────────┘
`),
},
{
name: "ignores title if does not fit",
title: "Title is too long a string and exceeds width",
expected: strings.TrimSpace(`
┌──────────┐
│ │
│ │
│ │
│ │
└──────────┘
`),
},
{
name: "works with ansi escapes",
title: NewStyle().Foreground(Color("#0ff")).Render("Test"),
expected: strings.TrimSpace(`
┌─ Test ───┐
│ │
│ │
│ │
│ │
└──────────┘
`),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := NewStyle().
Width(10).
Height(4).
Border(NormalBorder()).
BorderTitle(tt.title).
Render()

actual = ansi.Strip(actual)

if actual != tt.expected {
t.Errorf("expected:\n%s\n but got:\n%s", tt.expected, actual)
}
})
}
}
4 changes: 4 additions & 0 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,10 @@ func (s Style) getBorderStyle() Border {
return s.borderStyle
}

func (s Style) getBorderTitle() string {
return s.borderTitle
}

// Returns whether or not the style has implicit borders. This happens when
// a border style has been set but no border sides have been explicitly turned
// on or off.
Expand Down
9 changes: 9 additions & 0 deletions set.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func (s *Style) set(key propKey, value interface{}) {
s.marginBgColor = colorOrNil(value)
case borderStyleKey:
s.borderStyle = value.(Border)
case borderTitleKey:
s.borderTitle = value.(string)
case borderTopForegroundKey:
s.borderTopFgColor = colorOrNil(value)
case borderRightForegroundKey:
Expand Down Expand Up @@ -429,6 +431,13 @@ func (s Style) Border(b Border, sides ...bool) Style {
return s
}

// BorderTitle sets a title on the top border if top border is present and if
// the title fits within the width of the border. Otherwise this has no effect.
func (s Style) BorderTitle(title string) Style {
s.set(borderTitleKey, title)
return s
}

// BorderStyle defines the Border on a style. A Border contains a series of
// definitions for the sides and corners of a border.
//
Expand Down
4 changes: 4 additions & 0 deletions style.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ const (
// Border runes.
borderStyleKey

// Border title.
borderTitleKey

// Border edges.
borderTopKey
borderRightKey
Expand Down Expand Up @@ -143,6 +146,7 @@ type Style struct {
marginBgColor TerminalColor

borderStyle Border
borderTitle string
borderTopFgColor TerminalColor
borderRightFgColor TerminalColor
borderBottomFgColor TerminalColor
Expand Down