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

Feature: VerticalAlignment #106

Merged
merged 2 commits into from Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 24 additions & 1 deletion align.go
Expand Up @@ -10,7 +10,7 @@ import (
// Perform text alignment. If the string is multi-lined, we also make all lines
// the same width by padding them with spaces. If a termenv style is passed,
// use that to style the spaces added.
func alignText(str string, pos Position, width int, style *termenv.Style) string {
func alignTextHorizontal(str string, pos Position, width int, style *termenv.Style) string {
lines, widestLine := getLines(str)
var b strings.Builder

Expand Down Expand Up @@ -57,3 +57,26 @@ func alignText(str string, pos Position, width int, style *termenv.Style) string

return b.String()
}

func alignTextVertical(str string, pos Position, height int, _ *termenv.Style) string {
strHeight := strings.Count(str, "\n") + 1
if height < strHeight {
return str
}

switch pos {
case Top:
return str + strings.Repeat("\n", height-strHeight)
case Center:
var topPadding, bottomPadding = (height - strHeight) / 2, (height - strHeight) / 2
if strHeight+topPadding+bottomPadding > height {
topPadding--
} else if strHeight+topPadding+bottomPadding < height {
bottomPadding++
}
return strings.Repeat("\n", topPadding) + str + strings.Repeat("\n", bottomPadding)
case Bottom:
return strings.Repeat("\n", height-strHeight) + str
}
return str
}
41 changes: 41 additions & 0 deletions align_test.go
@@ -0,0 +1,41 @@
package lipgloss

import "testing"

func TestAlignTextVertical(t *testing.T) {
tests := []struct {
str string
pos Position
height int
want string
}{
{str: "Foo", pos: Top, height: 2, want: "Foo\n"},
{str: "Foo", pos: Center, height: 5, want: "\n\nFoo\n\n"},
{str: "Foo", pos: Bottom, height: 5, want: "\n\n\n\nFoo"},

{str: "Foo\nBar", pos: Bottom, height: 5, want: "\n\n\nFoo\nBar"},
{str: "Foo\nBar", pos: Center, height: 5, want: "\nFoo\nBar\n\n"},
{str: "Foo\nBar", pos: Top, height: 5, want: "Foo\nBar\n\n\n"},

{str: "Foo\nBar\nBaz", pos: Bottom, height: 5, want: "\n\nFoo\nBar\nBaz"},
{str: "Foo\nBar\nBaz", pos: Center, height: 5, want: "\nFoo\nBar\nBaz\n"},

{str: "Foo\nBar\nBaz", pos: Bottom, height: 3, want: "Foo\nBar\nBaz"},
{str: "Foo\nBar\nBaz", pos: Center, height: 3, want: "Foo\nBar\nBaz"},
{str: "Foo\nBar\nBaz", pos: Top, height: 3, want: "Foo\nBar\nBaz"},

{str: "Foo\n\n\n\nBar", pos: Bottom, height: 5, want: "Foo\n\n\n\nBar"},
{str: "Foo\n\n\n\nBar", pos: Center, height: 5, want: "Foo\n\n\n\nBar"},
{str: "Foo\n\n\n\nBar", pos: Top, height: 5, want: "Foo\n\n\n\nBar"},

{str: "Foo\nBar\nBaz", pos: Center, height: 9, want: "\n\n\nFoo\nBar\nBaz\n\n\n"},
{str: "Foo\nBar\nBaz", pos: Center, height: 10, want: "\n\n\nFoo\nBar\nBaz\n\n\n\n"},
}

for _, test := range tests {
got := alignTextVertical(test.str, test.pos, test.height, nil)
if got != test.want {
t.Errorf("alignTextVertical(%q, %v, %d) = %q, want %q", test.str, test.pos, test.height, got, test.want)
}
}
}
26 changes: 23 additions & 3 deletions get.go
Expand Up @@ -71,16 +71,36 @@ func (s Style) GetHeight() int {
return s.getAsInt(heightKey)
}

// GetAlign returns the style's implicit alignment setting. If no alignment is
// set Position.AlignLeft is returned.
// GetAlign returns the style's implicit horizontal alignment setting.
// If no alignment is set Position.AlignLeft is returned.
func (s Style) GetAlign() Position {
v := s.getAsPosition(alignKey)
v := s.getAsPosition(alignHorizontalKey)
if v == Position(0) {
return Left
}
return v
}

// GetAlignHorizontal returns the style's implicit horizontal alignment setting.
// If no alignment is set Position.AlignLeft is returned.
func (s Style) GetAlignHorizontal() Position {
v := s.getAsPosition(alignHorizontalKey)
if v == Position(0) {
return Left
}
return v
}

// GetAlignVertical returns the style's implicit vertical alignment setting.
// If no alignment is set Position.AlignTop is returned.
func (s Style) GetAlignVertical() Position {
v := s.getAsPosition(alignVerticalKey)
if v == Position(0) {
return Top
}
return v
}

// GetPadding returns the style's top, right, bottom, and left padding values,
// in that order. 0 is returned for unset values.
func (s Style) GetPadding() (top, right, bottom, left int) {
Expand Down
28 changes: 25 additions & 3 deletions set.go
Expand Up @@ -104,9 +104,31 @@ func (s Style) Height(i int) Style {
return s
}

// Align sets a text alignment rule.
func (s Style) Align(p Position) Style {
s.set(alignKey, p)
// Align is a shorthand method for setting horizontal and vertical alignment.
//
// With one argument, the position value is applied to the horizontal alignment.
//
// With two arguments, the value is applied to the vertical and horizontal
// alignments, in that order.
func (s Style) Align(p ...Position) Style {
if len(p) > 0 {
s.set(alignHorizontalKey, p[0])
}
if len(p) > 1 {
s.set(alignVerticalKey, p[1])
}
return s
}

// HorizontalAlign sets a horizontal text alignment rule.
func (s Style) AlignHorizontal(p Position) Style {
s.set(alignHorizontalKey, p)
return s
}

// VerticalAlign sets a text alignment rule.
func (s Style) AlignVertical(p Position) Style {
s.set(alignVerticalKey, p)
return s
}

Expand Down
17 changes: 8 additions & 9 deletions style.go
Expand Up @@ -26,7 +26,8 @@ const (
backgroundKey
widthKey
heightKey
alignKey
alignHorizontalKey
alignVerticalKey

// Padding.
paddingTopKey
Expand Down Expand Up @@ -169,9 +170,10 @@ func (s Style) Render(str string) string {
fg = s.getAsColor(foregroundKey)
bg = s.getAsColor(backgroundKey)

width = s.getAsInt(widthKey)
height = s.getAsInt(heightKey)
align = s.getAsPosition(alignKey)
width = s.getAsInt(widthKey)
height = s.getAsInt(heightKey)
horizontalAlign = s.getAsPosition(alignHorizontalKey)
verticalAlign = s.getAsPosition(alignVerticalKey)

topPadding = s.getAsInt(paddingTopKey)
rightPadding = s.getAsInt(paddingRightKey)
Expand Down Expand Up @@ -327,10 +329,7 @@ func (s Style) Render(str string) string {

// Height
if height > 0 {
h := strings.Count(str, "\n") + 1
if height > h {
str += strings.Repeat("\n", height-h)
}
str = alignTextVertical(str, verticalAlign, height, nil)
}

// Set alignment. This will also pad short lines with spaces so that all
Expand All @@ -344,7 +343,7 @@ func (s Style) Render(str string) string {
if colorWhitespace || styleWhitespace {
st = &teWhitespace
}
str = alignText(str, align, width, st)
str = alignTextHorizontal(str, horizontalAlign, width, st)
}
}

Expand Down
17 changes: 15 additions & 2 deletions unset.go
Expand Up @@ -66,9 +66,22 @@ func (s Style) UnsetHeight() Style {
return s
}

// UnsetAlign removes the text alignment style rule, if set.
// UnsetAlign removes the horizontal and vertical text alignment style rule, if set.
func (s Style) UnsetAlign() Style {
delete(s.rules, alignKey)
delete(s.rules, alignHorizontalKey)
delete(s.rules, alignVerticalKey)
return s
}

// UnsetAlignHorizontal removes the horizontal text alignment style rule, if set.
func (s Style) UnsetAlignHorizontal() Style {
delete(s.rules, alignHorizontalKey)
return s
}

// UnsetAlignHorizontal removes the vertical text alignment style rule, if set.
func (s Style) UnsetAlignVertical() Style {
delete(s.rules, alignVerticalKey)
return s
}

Expand Down