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

feat: New Border Title API #97

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Expand Up @@ -203,6 +203,37 @@ lipgloss.NewStyle().
lipgloss.NewStyle().
Border(lipgloss.DoubleBorder(), true, false, false, true)
```
It is also possible to set and style a border title
```go

// create bold italic title horizontally aligned
// to center of the border
titleStyle := lipgloss.NewStyle().
Background(lipgloss.Color("#6124DF")).
Align(lipgloss.Center).
Bold(true).
Italic(true)

// dialog box style with its title styled
dialogBoxStyle := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("#874BFD")).
BorderTitleStyle(titleStyle).
Padding(1, 0).
BorderTop(true).
BorderLeft(true).
BorderRight(true).
BorderBottom(true)


// Use the title with the dialog box style
dialog := lipgloss.Place(width, 9,
lipgloss.Center, lipgloss.Center,
dialogBoxStyle.Copy().BorderTitle(" Question ").Render(ui),
lipgloss.WithWhitespaceChars("猫咪"),
lipgloss.WithWhitespaceForeground(subtle),
)
```

For more on borders see [the docs][docs].

Expand Down
40 changes: 38 additions & 2 deletions borders.go
Expand Up @@ -246,11 +246,39 @@ func (s Style) applyBorder(str string) string {
border.BottomLeft = getFirstRuneAsString(border.BottomLeft)

var out strings.Builder
const sideCount = 2

// Render top
if hasTop {
top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
top = styleBorder(top, topFG, topBG)
top := ""

// sanitize title style
titleStyle := s.GetBorderTitleStyle().Copy().Inline(true).MaxWidth(width)
title := s.GetBorderTitle()

if len(strings.TrimSpace(title)) > 0 {
titleLen := len(title)
topBeforeTitle := border.TopLeft
topAfterTitle := border.TopRight
switch titleStyle.GetAlignHorizontal() {
case Right:
topBeforeTitle = border.TopLeft + repeatStr(border.Top, width-1-titleLen)
case Center:
noTitleLen := width - 1 - titleLen
noTitleLen2 := noTitleLen / sideCount
topBeforeTitle = border.TopLeft + repeatStr(border.Top, noTitleLen2)
topAfterTitle = repeatStr(border.Top, noTitleLen-noTitleLen2) + border.TopRight
case Left:
topAfterTitle = repeatStr(border.Top, width-1-titleLen) + border.TopRight
}

top = styleBorder(topBeforeTitle, topFG, topBG) +
titleStyle.Render(title) +
styleBorder(topAfterTitle, topFG, topBG)
} else {
top = renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
top = styleBorder(top, topFG, topBG)
}
out.WriteString(top)
out.WriteRune('\n')
}
Expand Down Expand Up @@ -296,6 +324,13 @@ func (s Style) applyBorder(str string) string {
return out.String()
}

func repeatStr(s string, count int) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sort of a nitpick so feel free to ignore, but instead of having a wrapper around strings.Repeat called repeatStr I would do something like this:

strings.Repeat(border.Top, max(0, value))

Where max is:

func max(a, b int) int {
  if a > b {
    return a
  }
  return b
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the max defined here:

lipgloss/style.go

Lines 460 to 465 in 0ce5550

func max(a, b int) int {
if a > b {
return a
}
return b
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion... coming out with the next update :)

if count <= 0 {
return ""
}
return strings.Repeat(s, count)
}

// Render the horizontal (top or bottom) portion of a border.
func renderHorizontalEdge(left, middle, right string, width int) string {
if width < 1 {
Expand All @@ -314,6 +349,7 @@ func renderHorizontalEdge(left, middle, right string, width int) string {

out := strings.Builder{}
out.WriteString(left)

for i := leftWidth + rightWidth; i < width+rightWidth; {
out.WriteRune(runes[j])
j++
Expand Down
9 changes: 8 additions & 1 deletion example/main.go
Expand Up @@ -92,9 +92,16 @@ var (

// Dialog.

dialogTitleStyle = lipgloss.NewStyle().
Background(lipgloss.Color("#6124DF")).
Align(lipgloss.Center).
Bold(true).
Italic(true)

dialogBoxStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("#874BFD")).
BorderTitleStyle(dialogTitleStyle).
Padding(1, 0).
BorderTop(true).
BorderLeft(true).
Expand Down Expand Up @@ -239,7 +246,7 @@ func main() {

dialog := lipgloss.Place(width, 9,
lipgloss.Center, lipgloss.Center,
dialogBoxStyle.Render(ui),
dialogBoxStyle.Copy().BorderTitle(" Question ").Render(ui),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably use Padding(0, 1) instead of manually adding spaces to demonstrate the BorderTitleStyle best practices.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this does not work as I use actual text to calculate the required text width.
@maaslalani Do you know of any API to calculate the screen width of styled text?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it might be GetHorizontalFrame to calculate all the padding and margin horizontally and then add the width of the text.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd additionally add logic where, if horizontal padding is unset on the style it defaults to Padding(0, 1):

╭ Title ───

However if horizontal padding is set, accept it at face value. For example, PaddingLeft(0).PaddingRight(3) would render as:

╭Title   ───

lipgloss.WithWhitespaceChars("猫咪"),
lipgloss.WithWhitespaceForeground(subtle),
)
Expand Down
32 changes: 31 additions & 1 deletion get.go
Expand Up @@ -434,6 +434,24 @@ func (s Style) getAsInt(k propKey) int {
return 0
}

func (s Style) getAsString(k propKey) string {
if v, ok := s.rules[k]; ok {
if s, ok := v.(string); ok {
return s
}
}
return ""
}

func (s Style) getAsStyle(k propKey) Style {
if v, ok := s.rules[k]; ok {
if s, ok := v.(Style); ok {
return s
}
}
return NewStyle()
}

func (s Style) getAsPosition(k propKey) Position {
v, ok := s.rules[k]
if !ok {
Expand All @@ -456,7 +474,19 @@ func (s Style) getBorderStyle() Border {
return noBorder
}

// Split a string into lines, additionally returning the size of the widest
// GetBorderTitleStyle returns border title style if set,
// otherwise returns empty style.
func (s Style) GetBorderTitleStyle() Style {
return s.getAsStyle(borderTitleStyleKey)
}

// GetBorderTitle returns border title if set,
// otherwise returns empty string.
func (s Style) GetBorderTitle() string {
return s.getAsString(borderTitleKey)
}

// Split a string into lines, additionally returning the size of the widest.
// line.
func getLines(s string) (lines []string, widest int) {
lines = strings.Split(s, "\n")
Expand Down
12 changes: 12 additions & 0 deletions set.go
Expand Up @@ -454,6 +454,18 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style {
return s
}

// BorderTitleStyle set border title style
func (s Style) BorderTitleStyle(style Style) Style {
s.set(borderTitleStyleKey, style)
return s
}

// BorderTitle set border title
func (s Style) BorderTitle(title string) Style {
s.set(borderTitleKey, title)
return s
}

// Inline makes rendering output one line and disables the rendering of
// margins, padding and borders. This is useful when you need a style to apply
// only to font rendering and don't want it to change any physical dimensions.
Expand Down
4 changes: 4 additions & 0 deletions style.go
Expand Up @@ -65,6 +65,10 @@ const (
borderBottomBackgroundKey
borderLeftBackgroundKey

// Border title.
borderTitleStyleKey
borderTitleKey

inlineKey
maxWidthKey
maxHeightKey
Expand Down