diff --git a/align.go b/align.go index 03e7889e..c3997038 100644 --- a/align.go +++ b/align.go @@ -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 @@ -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 +} diff --git a/align_test.go b/align_test.go new file mode 100644 index 00000000..dd5addb4 --- /dev/null +++ b/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) + } + } +} diff --git a/get.go b/get.go index 87906227..f01b8f15 100644 --- a/get.go +++ b/get.go @@ -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) { diff --git a/set.go b/set.go index 28d1b1cd..02884543 100644 --- a/set.go +++ b/set.go @@ -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 } diff --git a/style.go b/style.go index 576ace4a..fb89d06c 100644 --- a/style.go +++ b/style.go @@ -26,7 +26,8 @@ const ( backgroundKey widthKey heightKey - alignKey + alignHorizontalKey + alignVerticalKey // Padding. paddingTopKey @@ -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) @@ -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 @@ -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) } } diff --git a/unset.go b/unset.go index 0a03720d..25f3ac92 100644 --- a/unset.go +++ b/unset.go @@ -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 }