From b1cac8a217abe181cdd69bee951872a4d8b40abf Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 28 Jul 2022 11:48:43 -0400 Subject: [PATCH 01/16] Basic implementation of the Title API --- .gitignore | 1 + borders.go | 36 ++++++++++++++++++++++++++++++++---- example/main.go | 3 ++- get.go | 23 +++++++++++++++++++++++ set.go | 18 ++++++++++++++++++ style.go | 5 +++++ 6 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4f7620fa --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/.idea/ \ No newline at end of file diff --git a/borders.go b/borders.go index a3284ac0..911858b4 100644 --- a/borders.go +++ b/borders.go @@ -249,7 +249,23 @@ func (s Style) applyBorder(str string) string { // Render top if hasTop { - top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) + + title := s.GetBorderTitle() + if len(strings.TrimSpace(title)) > 0 { + //title = styleBorder(title, s.GetBorderTitleForeground(), s.GetBorderTitleBackground()) + + if len(title) > width { + title = title[0 : width-1] + } + + title = NewStyle(). + Foreground(s.GetBorderTitleForeground()). + Background(s.GetBorderTitleBackground()).Render(title) + } + + top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, title, width) + + //TODO border style gets lost as its taken over by title style top = styleBorder(top, topFG, topBG) out.WriteString(top) out.WriteRune('\n') @@ -287,7 +303,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 = styleBorder(bottom, bottomFG, bottomBG) out.WriteRune('\n') out.WriteString(bottom) @@ -297,7 +313,7 @@ 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 width < 1 { return "" } @@ -309,12 +325,24 @@ func renderHorizontalEdge(left, middle, right string, width int) string { leftWidth := ansi.PrintableRuneWidth(left) rightWidth := ansi.PrintableRuneWidth(right) + if len(strings.TrimSpace(title)) == 0 { + title = "" + } + runes := []rune(middle) j := 0 out := strings.Builder{} out.WriteString(left) - for i := leftWidth + rightWidth; i < width+rightWidth; { + + middleBorderStart := leftWidth + rightWidth + if len(title) > 0 { + // title truncation happens outside + out.WriteString(title) + middleBorderStart += ansi.PrintableRuneWidth(title) + } + + for i := middleBorderStart; i < width+rightWidth; { out.WriteRune(runes[j]) j++ if j >= len(runes) { diff --git a/example/main.go b/example/main.go index 8ec832b8..e8a355f4 100644 --- a/example/main.go +++ b/example/main.go @@ -95,6 +95,7 @@ var ( dialogBoxStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("#874BFD")). + BorderTitleForeground(lipgloss.Color("#FFFFFF")). Padding(1, 0). BorderTop(true). BorderLeft(true). @@ -239,7 +240,7 @@ func main() { dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Render(ui), + dialogBoxStyle.Copy().BorderTitle(" Question ").Render(ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), ) diff --git a/get.go b/get.go index 87906227..88d4acf4 100644 --- a/get.go +++ b/get.go @@ -414,6 +414,17 @@ 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) getAsPosition(k propKey) Position { v, ok := s.rules[k] if !ok { @@ -436,6 +447,18 @@ func (s Style) getBorderStyle() Border { return noBorder } +func (s Style) GetBorderTitle() string { + return s.getAsString(borderTitleKey) +} + +func (s Style) GetBorderTitleBackground() TerminalColor { + return s.getAsColor(borderTitleBackgroundKey) +} + +func (s Style) GetBorderTitleForeground() TerminalColor { + return s.getAsColor(borderTitleForegroundKey) +} + // Split a string into lines, additionally returning the size of the widest // line. func getLines(s string) (lines []string, widest int) { diff --git a/set.go b/set.go index 2e7dfc8c..203c75dd 100644 --- a/set.go +++ b/set.go @@ -435,6 +435,24 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style { return s } +// BorderTitle set border title, which is displayed as part of the top border +func (s Style) BorderTitle(title string) Style { + s.set(borderTitleKey, title) + return s +} + +// BorderTitleBackground set border title background color +func (s Style) BorderTitleBackground(color TerminalColor) Style { + s.set(borderTitleBackgroundKey, color) + return s +} + +// BorderTitleForeground set border title background color +func (s Style) BorderTitleForeground(color TerminalColor) Style { + s.set(borderTitleForegroundKey, color) + 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. diff --git a/style.go b/style.go index 576ace4a..8a3643aa 100644 --- a/style.go +++ b/style.go @@ -64,6 +64,11 @@ const ( borderBottomBackgroundKey borderLeftBackgroundKey + // Border title + borderTitleKey + borderTitleBackgroundKey + borderTitleForegroundKey + inlineKey maxWidthKey maxHeightKey From f3ebe5c355bd59f3736457be0ec3d0ad8da7ee63 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 28 Jul 2022 21:04:49 -0400 Subject: [PATCH 02/16] Simplified way of embedding border title. Ability to render title with fg and bg colors --- borders.go | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/borders.go b/borders.go index 911858b4..235c5d09 100644 --- a/borders.go +++ b/borders.go @@ -250,23 +250,27 @@ func (s Style) applyBorder(str string) string { // Render top if hasTop { + top := "" title := s.GetBorderTitle() if len(strings.TrimSpace(title)) > 0 { - //title = styleBorder(title, s.GetBorderTitleForeground(), s.GetBorderTitleBackground()) if len(title) > width { title = title[0 : width-1] } - title = NewStyle(). - Foreground(s.GetBorderTitleForeground()). - Background(s.GetBorderTitleBackground()).Render(title) - } + //TODO title alignment + topBeforeTitle := border.TopLeft + topAfterTitle := strings.Repeat(border.Top, width-1-len(title)) + border.TopRight + + top = styleBorder(topBeforeTitle, topFG, topBG) + + styleBorder(title, s.GetBorderTitleForeground(), s.GetBorderTitleBackground()) + + styleBorder(topAfterTitle, topFG, topBG) - top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, title, width) + } else { - //TODO border style gets lost as its taken over by title style - top = styleBorder(top, topFG, topBG) + top = renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) + top = styleBorder(top, topFG, topBG) + } out.WriteString(top) out.WriteRune('\n') } @@ -303,7 +307,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 = styleBorder(bottom, bottomFG, bottomBG) out.WriteRune('\n') out.WriteString(bottom) @@ -313,7 +317,7 @@ func (s Style) applyBorder(str string) string { } // Render the horizontal (top or bottom) portion of a border. -func renderHorizontalEdge(left, middle, right, title string, width int) string { +func renderHorizontalEdge(left, middle, right string, width int) string { if width < 1 { return "" } @@ -325,24 +329,13 @@ func renderHorizontalEdge(left, middle, right, title string, width int) string { leftWidth := ansi.PrintableRuneWidth(left) rightWidth := ansi.PrintableRuneWidth(right) - if len(strings.TrimSpace(title)) == 0 { - title = "" - } - runes := []rune(middle) j := 0 out := strings.Builder{} out.WriteString(left) - middleBorderStart := leftWidth + rightWidth - if len(title) > 0 { - // title truncation happens outside - out.WriteString(title) - middleBorderStart += ansi.PrintableRuneWidth(title) - } - - for i := middleBorderStart; i < width+rightWidth; { + for i := leftWidth + rightWidth; i < width+rightWidth; { out.WriteRune(runes[j]) j++ if j >= len(runes) { From 2b9468b09269466c544b457cb233e6d2a3ec1a6c Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 28 Jul 2022 21:34:32 -0400 Subject: [PATCH 03/16] Add border title alignment --- borders.go | 16 ++++++++++++++-- example/main.go | 1 + get.go | 4 ++++ set.go | 6 ++++++ style.go | 1 + 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/borders.go b/borders.go index 235c5d09..a0ac47b9 100644 --- a/borders.go +++ b/borders.go @@ -255,12 +255,24 @@ func (s Style) applyBorder(str string) string { if len(strings.TrimSpace(title)) > 0 { if len(title) > width { + // the truncation algorithm can be provided through the API in the future title = title[0 : width-1] } - //TODO title alignment topBeforeTitle := border.TopLeft - topAfterTitle := strings.Repeat(border.Top, width-1-len(title)) + border.TopRight + topAfterTitle := border.TopRight + switch s.GetBorderTitleAlignment() { + case Right: + topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, width-1-len(title)) + case Center: + noTitleLen := width - 1 - len(title) + noTitleLen2 := noTitleLen / 2 + topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, noTitleLen2) + topAfterTitle = strings.Repeat(border.Top, noTitleLen-noTitleLen2) + border.TopRight + + default: + topAfterTitle = strings.Repeat(border.Top, width-1-len(title)) + border.TopRight + } top = styleBorder(topBeforeTitle, topFG, topBG) + styleBorder(title, s.GetBorderTitleForeground(), s.GetBorderTitleBackground()) + diff --git a/example/main.go b/example/main.go index e8a355f4..69e93bbb 100644 --- a/example/main.go +++ b/example/main.go @@ -96,6 +96,7 @@ var ( Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("#874BFD")). BorderTitleForeground(lipgloss.Color("#FFFFFF")). + BorderTitleAlignment(lipgloss.Center). Padding(1, 0). BorderTop(true). BorderLeft(true). diff --git a/get.go b/get.go index 88d4acf4..ad5f7565 100644 --- a/get.go +++ b/get.go @@ -451,6 +451,10 @@ func (s Style) GetBorderTitle() string { return s.getAsString(borderTitleKey) } +func (s Style) GetBorderTitleAlignment() Position { + return s.getAsPosition(borderTitleAlignmentKey) +} + func (s Style) GetBorderTitleBackground() TerminalColor { return s.getAsColor(borderTitleBackgroundKey) } diff --git a/set.go b/set.go index 203c75dd..a889738a 100644 --- a/set.go +++ b/set.go @@ -441,6 +441,12 @@ func (s Style) BorderTitle(title string) Style { return s } +// BorderTitleAlignment set border title aligment +func (s Style) BorderTitleAlignment(alignment Position) Style { + s.set(borderTitleAlignmentKey, alignment) + return s +} + // BorderTitleBackground set border title background color func (s Style) BorderTitleBackground(color TerminalColor) Style { s.set(borderTitleBackgroundKey, color) diff --git a/style.go b/style.go index 8a3643aa..4c8db43e 100644 --- a/style.go +++ b/style.go @@ -66,6 +66,7 @@ const ( // Border title borderTitleKey + borderTitleAlignmentKey borderTitleBackgroundKey borderTitleForegroundKey From 346a9782df81a53aa1f886ff9a5da85ea96ab904 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Fri, 19 Aug 2022 16:43:24 +0200 Subject: [PATCH 04/16] Fix linting issues --- borders.go | 10 +++------- get.go | 8 +++++--- set.go | 43 +++++++++++++++++++------------------------ style.go | 2 +- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/borders.go b/borders.go index a0ac47b9..fef58208 100644 --- a/borders.go +++ b/borders.go @@ -246,14 +246,13 @@ func (s Style) applyBorder(str string) string { border.BottomLeft = getFirstRuneAsString(border.BottomLeft) var out strings.Builder + const sideCount = 2 // Render top if hasTop { - top := "" title := s.GetBorderTitle() if len(strings.TrimSpace(title)) > 0 { - if len(title) > width { // the truncation algorithm can be provided through the API in the future title = title[0 : width-1] @@ -266,20 +265,17 @@ func (s Style) applyBorder(str string) string { topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, width-1-len(title)) case Center: noTitleLen := width - 1 - len(title) - noTitleLen2 := noTitleLen / 2 + noTitleLen2 := noTitleLen / sideCount topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, noTitleLen2) topAfterTitle = strings.Repeat(border.Top, noTitleLen-noTitleLen2) + border.TopRight - - default: + case Left: topAfterTitle = strings.Repeat(border.Top, width-1-len(title)) + border.TopRight } top = styleBorder(topBeforeTitle, topFG, topBG) + styleBorder(title, s.GetBorderTitleForeground(), s.GetBorderTitleBackground()) + styleBorder(topAfterTitle, topFG, topBG) - } else { - top = renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) top = styleBorder(top, topFG, topBG) } diff --git a/get.go b/get.go index ad5f7565..e9c5e9fc 100644 --- a/get.go +++ b/get.go @@ -415,14 +415,12 @@ func (s Style) getAsInt(k propKey) int { } 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) getAsPosition(k propKey) Position { @@ -447,23 +445,27 @@ func (s Style) getBorderStyle() Border { return noBorder } +// GetBorderTitle returns border title if set, otherwise returns empty string. func (s Style) GetBorderTitle() string { return s.getAsString(borderTitleKey) } +// GetBorderTitleAlignment returns border title alignment. func (s Style) GetBorderTitleAlignment() Position { return s.getAsPosition(borderTitleAlignmentKey) } +// GetBorderTitleBackground returns border title background color. func (s Style) GetBorderTitleBackground() TerminalColor { return s.getAsColor(borderTitleBackgroundKey) } +// GetBorderTitleForeground returns border title foreground color. func (s Style) GetBorderTitleForeground() TerminalColor { return s.getAsColor(borderTitleForegroundKey) } -// Split a string into lines, additionally returning the size of the widest +// 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") diff --git a/set.go b/set.go index a889738a..6c684e76 100644 --- a/set.go +++ b/set.go @@ -73,12 +73,11 @@ func (s Style) Faint(v bool) Style { // Foreground sets a foreground color. // -// // Sets the foreground to blue -// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff")) -// -// // Removes the foreground color -// s.Foreground(lipgloss.NoColor) +// // Sets the foreground to blue +// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff")) // +// // Removes the foreground color +// s.Foreground(lipgloss.NoColor) func (s Style) Foreground(c TerminalColor) Style { s.set(foregroundKey, c) return s @@ -248,12 +247,11 @@ func (s Style) MarginBackground(c TerminalColor) Style { // // Examples: // -// // Applies borders to the top and bottom only -// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false) -// -// // Applies rounded borders to the right and bottom only -// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false) +// // Applies borders to the top and bottom only +// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false) // +// // Applies rounded borders to the right and bottom only +// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false) func (s Style) Border(b Border, sides ...bool) Style { s.set(borderStyleKey, b) @@ -285,8 +283,7 @@ func (s Style) Border(b Border, sides ...bool) Style { // // Example: // -// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder()) -// +// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder()) func (s Style) BorderStyle(b Border) Style { s.set(borderStyleKey, b) return s @@ -435,25 +432,25 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style { return s } -// BorderTitle set border title, which is displayed as part of the top border +// BorderTitle set border title, which is displayed as part of the top border. func (s Style) BorderTitle(title string) Style { s.set(borderTitleKey, title) return s } -// BorderTitleAlignment set border title aligment +// BorderTitleAlignment set border title alignment. func (s Style) BorderTitleAlignment(alignment Position) Style { s.set(borderTitleAlignmentKey, alignment) return s } -// BorderTitleBackground set border title background color +// BorderTitleBackground set border title background color. func (s Style) BorderTitleBackground(color TerminalColor) Style { s.set(borderTitleBackgroundKey, color) return s } -// BorderTitleForeground set border title background color +// BorderTitleForeground set border title background color. func (s Style) BorderTitleForeground(color TerminalColor) Style { s.set(borderTitleForegroundKey, color) return s @@ -469,10 +466,9 @@ func (s Style) BorderTitleForeground(color TerminalColor) Style { // // Example: // -// var userInput string = "..." -// var userStyle = text.Style{ /* ... */ } -// fmt.Println(userStyle.Inline(true).Render(userInput)) -// +// var userInput string = "..." +// var userStyle = text.Style{ /* ... */ } +// fmt.Println(userStyle.Inline(true).Render(userInput)) func (s Style) Inline(v bool) Style { o := s.Copy() o.set(inlineKey, v) @@ -488,10 +484,9 @@ func (s Style) Inline(v bool) Style { // // Example: // -// var userInput string = "..." -// var userStyle = text.Style{ /* ... */ } -// fmt.Println(userStyle.MaxWidth(16).Render(userInput)) -// +// var userInput string = "..." +// var userStyle = text.Style{ /* ... */ } +// fmt.Println(userStyle.MaxWidth(16).Render(userInput)) func (s Style) MaxWidth(n int) Style { o := s.Copy() o.set(maxWidthKey, n) diff --git a/style.go b/style.go index 4c8db43e..0373f85e 100644 --- a/style.go +++ b/style.go @@ -64,7 +64,7 @@ const ( borderBottomBackgroundKey borderLeftBackgroundKey - // Border title + // Border title. borderTitleKey borderTitleAlignmentKey borderTitleBackgroundKey From 32c0787e27054c825cefa5e3926880e16162ab3d Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Wed, 21 Sep 2022 16:15:49 -0400 Subject: [PATCH 05/16] Delete .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4f7620fa..00000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -**/.idea/ \ No newline at end of file From 3e6dff38798449a8b4a54b6e9637fdd598a79de6 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Wed, 21 Sep 2022 22:35:41 -0400 Subject: [PATCH 06/16] Redesign BorderTitle API by converting argument to a Style. This eliminates the need all other methods and add an ability to style the title itself. --- borders.go | 25 +++++++++++++++---------- example/main.go | 10 +++++++--- get.go | 28 +++++++++++----------------- set.go | 34 +++++++++++++++------------------- style.go | 3 --- 5 files changed, 48 insertions(+), 52 deletions(-) diff --git a/borders.go b/borders.go index fef58208..165b266c 100644 --- a/borders.go +++ b/borders.go @@ -251,29 +251,34 @@ func (s Style) applyBorder(str string) string { // Render top if hasTop { top := "" - title := s.GetBorderTitle() + + // sanitize title style + titleStyle := s.GetBorderTitle().Copy().Inline(true).MaxWidth(width) + title := titleStyle.Value() + if len(strings.TrimSpace(title)) > 0 { - if len(title) > width { - // the truncation algorithm can be provided through the API in the future - title = title[0 : width-1] - } + titleLen := len(title) + //if titleLen > width { + // // the truncation algorithm can be provided through the API in the future + // titleStyle.SetString(title[0 : width-1]) + //} topBeforeTitle := border.TopLeft topAfterTitle := border.TopRight - switch s.GetBorderTitleAlignment() { + switch titleStyle.GetAlignHorizontal() { case Right: - topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, width-1-len(title)) + topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, width-1-titleLen) case Center: - noTitleLen := width - 1 - len(title) + noTitleLen := width - 1 - titleLen noTitleLen2 := noTitleLen / sideCount topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, noTitleLen2) topAfterTitle = strings.Repeat(border.Top, noTitleLen-noTitleLen2) + border.TopRight case Left: - topAfterTitle = strings.Repeat(border.Top, width-1-len(title)) + border.TopRight + topAfterTitle = strings.Repeat(border.Top, width-1-titleLen) + border.TopRight } top = styleBorder(topBeforeTitle, topFG, topBG) + - styleBorder(title, s.GetBorderTitleForeground(), s.GetBorderTitleBackground()) + + titleStyle.String() + styleBorder(topAfterTitle, topFG, topBG) } else { top = renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) diff --git a/example/main.go b/example/main.go index 69e93bbb..ce74f339 100644 --- a/example/main.go +++ b/example/main.go @@ -95,8 +95,6 @@ var ( dialogBoxStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("#874BFD")). - BorderTitleForeground(lipgloss.Color("#FFFFFF")). - BorderTitleAlignment(lipgloss.Center). Padding(1, 0). BorderTop(true). BorderLeft(true). @@ -239,9 +237,15 @@ func main() { buttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton) ui := lipgloss.JoinVertical(lipgloss.Center, question, buttons) + title := lipgloss.NewStyle(). + Background(lipgloss.Color("#6124DF")). + Align(lipgloss.Center). + Bold(true). + Italic(true). + SetString(" Question ") dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Copy().BorderTitle(" Question ").Render(ui), + dialogBoxStyle.Copy().BorderTitle(title).Render(ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), ) diff --git a/get.go b/get.go index ddcde4e2..770ed57b 100644 --- a/get.go +++ b/get.go @@ -443,6 +443,15 @@ func (s Style) getAsString(k propKey) string { 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 { @@ -466,23 +475,8 @@ func (s Style) getBorderStyle() Border { } // GetBorderTitle returns border title if set, otherwise returns empty string. -func (s Style) GetBorderTitle() string { - return s.getAsString(borderTitleKey) -} - -// GetBorderTitleAlignment returns border title alignment. -func (s Style) GetBorderTitleAlignment() Position { - return s.getAsPosition(borderTitleAlignmentKey) -} - -// GetBorderTitleBackground returns border title background color. -func (s Style) GetBorderTitleBackground() TerminalColor { - return s.getAsColor(borderTitleBackgroundKey) -} - -// GetBorderTitleForeground returns border title foreground color. -func (s Style) GetBorderTitleForeground() TerminalColor { - return s.getAsColor(borderTitleForegroundKey) +func (s Style) GetBorderTitle() Style { + return s.getAsStyle(borderTitleKey) } // Split a string into lines, additionally returning the size of the widest. diff --git a/set.go b/set.go index d8eb9db9..d877ec8a 100644 --- a/set.go +++ b/set.go @@ -455,29 +455,25 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style { } // BorderTitle set border title, which is displayed as part of the top border. -func (s Style) BorderTitle(title string) Style { +// Example: +// +// title := lipgloss.NewStyle(). +// Background(lipgloss.Color("#6124DF")). +// Align(lipgloss.Center). +// Bold(true). +// Italic(true). +// SetString(" Question ") +// dialog := lipgloss.Place(width, 9, +// lipgloss.Center, lipgloss.Center, +// dialogBoxStyle.Copy().BorderTitle(title).Render(ui), +// lipgloss.WithWhitespaceChars("猫咪"), +// lipgloss.WithWhitespaceForeground(subtle), +// ) +func (s Style) BorderTitle(title Style) Style { s.set(borderTitleKey, title) return s } -// BorderTitleAlignment set border title alignment. -func (s Style) BorderTitleAlignment(alignment Position) Style { - s.set(borderTitleAlignmentKey, alignment) - return s -} - -// BorderTitleBackground set border title background color. -func (s Style) BorderTitleBackground(color TerminalColor) Style { - s.set(borderTitleBackgroundKey, color) - return s -} - -// BorderTitleForeground set border title background color. -func (s Style) BorderTitleForeground(color TerminalColor) Style { - s.set(borderTitleForegroundKey, color) - 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. diff --git a/style.go b/style.go index e2eee7ca..72fab678 100644 --- a/style.go +++ b/style.go @@ -67,9 +67,6 @@ const ( // Border title. borderTitleKey - borderTitleAlignmentKey - borderTitleBackgroundKey - borderTitleForegroundKey inlineKey maxWidthKey From fec27d116def3d1d6fabca6bf6b278ea01658a01 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Wed, 21 Sep 2022 23:55:04 -0400 Subject: [PATCH 07/16] Fix linting issue: remove unused `getAsString` function --- get.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/get.go b/get.go index 770ed57b..ade8343a 100644 --- a/get.go +++ b/get.go @@ -434,15 +434,6 @@ 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 { From cd06217ae69184e4b226a2b10987a6a0df4b28b5 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 00:01:35 -0400 Subject: [PATCH 08/16] Cleanup --- borders.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/borders.go b/borders.go index 165b266c..63baa122 100644 --- a/borders.go +++ b/borders.go @@ -258,11 +258,6 @@ func (s Style) applyBorder(str string) string { if len(strings.TrimSpace(title)) > 0 { titleLen := len(title) - //if titleLen > width { - // // the truncation algorithm can be provided through the API in the future - // titleStyle.SetString(title[0 : width-1]) - //} - topBeforeTitle := border.TopLeft topAfterTitle := border.TopRight switch titleStyle.GetAlignHorizontal() { From 9b317d1aabdc0bff6dec109c524ecee26f8890ce Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 00:37:52 -0400 Subject: [PATCH 09/16] Fix an issue with border rendering panic. --- borders.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/borders.go b/borders.go index 63baa122..7b6f5e28 100644 --- a/borders.go +++ b/borders.go @@ -262,14 +262,14 @@ func (s Style) applyBorder(str string) string { topAfterTitle := border.TopRight switch titleStyle.GetAlignHorizontal() { case Right: - topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, width-1-titleLen) + topBeforeTitle = border.TopLeft + repeatStr(border.Top, width-1-titleLen) case Center: noTitleLen := width - 1 - titleLen noTitleLen2 := noTitleLen / sideCount - topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, noTitleLen2) - topAfterTitle = strings.Repeat(border.Top, noTitleLen-noTitleLen2) + border.TopRight + topBeforeTitle = border.TopLeft + repeatStr(border.Top, noTitleLen2) + topAfterTitle = repeatStr(border.Top, noTitleLen-noTitleLen2) + border.TopRight case Left: - topAfterTitle = strings.Repeat(border.Top, width-1-titleLen) + border.TopRight + topAfterTitle = repeatStr(border.Top, width-1-titleLen) + border.TopRight } top = styleBorder(topBeforeTitle, topFG, topBG) + @@ -324,6 +324,13 @@ func (s Style) applyBorder(str string) string { return out.String() } +func repeatStr(s string, count int) string { + 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 { From 80169c0c4e38ca5837e8b99b13428cdafa2e7bd6 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 08:00:44 -0400 Subject: [PATCH 10/16] Improve docs. Update README.md --- README.md | 22 ++++++++++++++++++++++ set.go | 13 +++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 039e28c7..5e8d43ed 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,28 @@ lipgloss.NewStyle(). lipgloss.NewStyle(). Border(lipgloss.DoubleBorder(), true, false, false, true) ``` +Borders can have titles. Border title is defined as a `Style` to allow +styling it independently of the border itself. + +```go + + // create bold italic title horizontally aligned + // to center of the border + title := lipgloss.NewStyle(). + Background(lipgloss.Color("#6124DF")). + Align(lipgloss.Center). + Bold(true). + Italic(true). + SetString(" Question ") + + // Use title within the dialog style + dialog := lipgloss.Place(width, 9, + lipgloss.Center, lipgloss.Center, + dialogBoxStyle.Copy().BorderTitle(title).Render(ui), + lipgloss.WithWhitespaceChars("猫咪"), + lipgloss.WithWhitespaceForeground(subtle), + ) +``` For more on borders see [the docs][docs]. diff --git a/set.go b/set.go index d877ec8a..b020d7ad 100644 --- a/set.go +++ b/set.go @@ -455,19 +455,20 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style { } // BorderTitle set border title, which is displayed as part of the top border. +// // Example: // // title := lipgloss.NewStyle(). // Background(lipgloss.Color("#6124DF")). // Align(lipgloss.Center). -// Bold(true). -// Italic(true). +// Bold(true). +// Italic(true). // SetString(" Question ") // dialog := lipgloss.Place(width, 9, -// lipgloss.Center, lipgloss.Center, -// dialogBoxStyle.Copy().BorderTitle(title).Render(ui), -// lipgloss.WithWhitespaceChars("猫咪"), -// lipgloss.WithWhitespaceForeground(subtle), +// lipgloss.Center, lipgloss.Center, +// dialogBoxStyle.Copy().BorderTitle(title).Render(ui), +// lipgloss.WithWhitespaceChars("猫咪"), +// lipgloss.WithWhitespaceForeground(subtle), // ) func (s Style) BorderTitle(title Style) Style { s.set(borderTitleKey, title) From 27c2a27c2cb03f64dabd305810def3a4a45dd215 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 11:16:46 -0400 Subject: [PATCH 11/16] Improve Border Title API according to suggestions --- borders.go | 6 +++--- example/main.go | 7 +++---- get.go | 22 +++++++++++++++++++--- set.go | 23 +++++++++-------------- style.go | 1 + 5 files changed, 35 insertions(+), 24 deletions(-) diff --git a/borders.go b/borders.go index 7b6f5e28..2abe2ba5 100644 --- a/borders.go +++ b/borders.go @@ -253,8 +253,8 @@ func (s Style) applyBorder(str string) string { top := "" // sanitize title style - titleStyle := s.GetBorderTitle().Copy().Inline(true).MaxWidth(width) - title := titleStyle.Value() + titleStyle := s.GetBorderTitleStyle().Copy().Inline(true).MaxWidth(width) + title := s.GetBorderTitle() if len(strings.TrimSpace(title)) > 0 { titleLen := len(title) @@ -273,7 +273,7 @@ func (s Style) applyBorder(str string) string { } top = styleBorder(topBeforeTitle, topFG, topBG) + - titleStyle.String() + + titleStyle.Render(title) + styleBorder(topAfterTitle, topFG, topBG) } else { top = renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) diff --git a/example/main.go b/example/main.go index ce74f339..c04385af 100644 --- a/example/main.go +++ b/example/main.go @@ -237,15 +237,14 @@ func main() { buttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton) ui := lipgloss.JoinVertical(lipgloss.Center, question, buttons) - title := lipgloss.NewStyle(). + titleStyle := lipgloss.NewStyle(). Background(lipgloss.Color("#6124DF")). Align(lipgloss.Center). Bold(true). - Italic(true). - SetString(" Question ") + Italic(true) dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Copy().BorderTitle(title).Render(ui), + dialogBoxStyle.Copy().BorderTitleStyle(titleStyle).BorderTitle(" Question ").Render(ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), ) diff --git a/get.go b/get.go index ade8343a..3b760f8d 100644 --- a/get.go +++ b/get.go @@ -434,6 +434,15 @@ 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 { @@ -465,9 +474,16 @@ func (s Style) getBorderStyle() Border { return noBorder } -// GetBorderTitle returns border title if set, otherwise returns empty string. -func (s Style) GetBorderTitle() Style { - return s.getAsStyle(borderTitleKey) +// 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. diff --git a/set.go b/set.go index b020d7ad..a279111c 100644 --- a/set.go +++ b/set.go @@ -454,23 +454,18 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style { return s } -// BorderTitle set border title, which is displayed as part of the top border. +// BorderTitleStyle set border title style // // Example: // -// title := lipgloss.NewStyle(). -// Background(lipgloss.Color("#6124DF")). -// Align(lipgloss.Center). -// Bold(true). -// Italic(true). -// SetString(" Question ") -// dialog := lipgloss.Place(width, 9, -// lipgloss.Center, lipgloss.Center, -// dialogBoxStyle.Copy().BorderTitle(title).Render(ui), -// lipgloss.WithWhitespaceChars("猫咪"), -// lipgloss.WithWhitespaceForeground(subtle), -// ) -func (s Style) BorderTitle(title Style) Style { +// TODO example +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 } diff --git a/style.go b/style.go index 72fab678..2d9c2f83 100644 --- a/style.go +++ b/style.go @@ -66,6 +66,7 @@ const ( borderLeftBackgroundKey // Border title. + borderTitleStyleKey borderTitleKey inlineKey From 314a2e2f75cc40a7f0bed52522331fb214603bcd Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 13:50:20 -0400 Subject: [PATCH 12/16] Update example code --- example/main.go | 14 ++++++++------ set.go | 4 ---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/example/main.go b/example/main.go index c04385af..2937fa35 100644 --- a/example/main.go +++ b/example/main.go @@ -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). @@ -237,14 +244,9 @@ func main() { buttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton) ui := lipgloss.JoinVertical(lipgloss.Center, question, buttons) - titleStyle := lipgloss.NewStyle(). - Background(lipgloss.Color("#6124DF")). - Align(lipgloss.Center). - Bold(true). - Italic(true) dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Copy().BorderTitleStyle(titleStyle).BorderTitle(" Question ").Render(ui), + dialogBoxStyle.Copy().BorderTitle(" Question ").Render(ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), ) diff --git a/set.go b/set.go index a279111c..8959f86c 100644 --- a/set.go +++ b/set.go @@ -455,10 +455,6 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style { } // BorderTitleStyle set border title style -// -// Example: -// -// TODO example func (s Style) BorderTitleStyle(style Style) Style { s.set(borderTitleStyleKey, style) return s From 78fea2f0aa71e8e364db495045070f29f8de0b82 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 13:52:25 -0400 Subject: [PATCH 13/16] Update README.md --- README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5e8d43ed..e343f45e 100644 --- a/README.md +++ b/README.md @@ -203,24 +203,33 @@ lipgloss.NewStyle(). lipgloss.NewStyle(). Border(lipgloss.DoubleBorder(), true, false, false, true) ``` -Borders can have titles. Border title is defined as a `Style` to allow -styling it independently of the border itself. - +It is also possible to set and style a border title ```go // create bold italic title horizontally aligned // to center of the border - title := lipgloss.NewStyle(). + titleStyle := lipgloss.NewStyle(). Background(lipgloss.Color("#6124DF")). Align(lipgloss.Center). Bold(true). - Italic(true). - SetString(" Question ") + 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 title within the dialog style + // Use the title with the dialog box style dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Copy().BorderTitle(title).Render(ui), + dialogBoxStyle.Copy().BorderTitle(" Question ").Render(ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), ) From 0c829e86871a76582566816704d85d2d9ffdd8b7 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 14:13:31 -0400 Subject: [PATCH 14/16] Remove redundant repeatStr function --- borders.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/borders.go b/borders.go index 2abe2ba5..15caabf8 100644 --- a/borders.go +++ b/borders.go @@ -262,14 +262,14 @@ func (s Style) applyBorder(str string) string { topAfterTitle := border.TopRight switch titleStyle.GetAlignHorizontal() { case Right: - topBeforeTitle = border.TopLeft + repeatStr(border.Top, width-1-titleLen) + topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, max(0, 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 + topBeforeTitle = border.TopLeft + strings.Repeat(border.Top, max(0, noTitleLen2)) + topAfterTitle = strings.Repeat(border.Top, max(0, noTitleLen-noTitleLen2)) + border.TopRight case Left: - topAfterTitle = repeatStr(border.Top, width-1-titleLen) + border.TopRight + topAfterTitle = strings.Repeat(border.Top, max(0, width-1-titleLen)) + border.TopRight } top = styleBorder(topBeforeTitle, topFG, topBG) + @@ -324,13 +324,6 @@ func (s Style) applyBorder(str string) string { return out.String() } -func repeatStr(s string, count int) string { - 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 { From 4cf8c19e5242325babe92967cb4a810c79c0d84f Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 15:55:25 -0400 Subject: [PATCH 15/16] Calculate horizontal frame for border title correctly. Introduce default padding if one is not present. --- borders.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/borders.go b/borders.go index 15caabf8..36f81e00 100644 --- a/borders.go +++ b/borders.go @@ -253,11 +253,17 @@ func (s Style) applyBorder(str string) string { top := "" // sanitize title style - titleStyle := s.GetBorderTitleStyle().Copy().Inline(true).MaxWidth(width) + titleStyle := s.GetBorderTitleStyle().Copy().MaxWidth(width) + + // set default padding if one is not set + if titleStyle.GetHorizontalPadding() == 0 { + titleStyle = titleStyle.Padding(0, 1) + } + title := s.GetBorderTitle() if len(strings.TrimSpace(title)) > 0 { - titleLen := len(title) + titleLen := titleStyle.GetHorizontalFrameSize() + len(title) topBeforeTitle := border.TopLeft topAfterTitle := border.TopRight switch titleStyle.GetAlignHorizontal() { From 9e516bc662be5f71f44a2c126e677b5022a25f06 Mon Sep 17 00:00:00 2001 From: Eugene Ryzhikov Date: Thu, 22 Sep 2022 15:57:30 -0400 Subject: [PATCH 16/16] Update example --- example/main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example/main.go b/example/main.go index 2937fa35..ff25932c 100644 --- a/example/main.go +++ b/example/main.go @@ -96,7 +96,8 @@ var ( Background(lipgloss.Color("#6124DF")). Align(lipgloss.Center). Bold(true). - Italic(true) + Italic(true). + Padding(0, 5) dialogBoxStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). @@ -246,7 +247,7 @@ func main() { dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Copy().BorderTitle(" Question ").Render(ui), + dialogBoxStyle.Copy().BorderTitle("Question").Render(ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), )