position.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package lipgloss
  2. import (
  3. "math"
  4. "strings"
  5. "github.com/muesli/reflow/ansi"
  6. )
  7. // Position represents a position along a horizontal or vertical axis. It's in
  8. // situations where an axis is involved, like alignment, joining, placement and
  9. // so on.
  10. //
  11. // A value of 0 represents the start (the left or top) and 1 represents the end
  12. // (the right or bottom). 0.5 represents the center.
  13. //
  14. // There are constants Top, Bottom, Center, Left and Right in this package that
  15. // can be used to aid readability.
  16. type Position float64
  17. func (p Position) value() float64 {
  18. return math.Min(1, math.Max(0, float64(p)))
  19. }
  20. // Position aliases.
  21. const (
  22. Top Position = 0.0
  23. Bottom Position = 1.0
  24. Center Position = 0.5
  25. Left Position = 0.0
  26. Right Position = 1.0
  27. )
  28. // Place places a string or text block vertically in an unstyled box of a given
  29. // width or height.
  30. func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
  31. return renderer.Place(width, height, hPos, vPos, str, opts...)
  32. }
  33. // Place places a string or text block vertically in an unstyled box of a given
  34. // width or height.
  35. func (r *Renderer) Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
  36. return r.PlaceVertical(height, vPos, r.PlaceHorizontal(width, hPos, str, opts...), opts...)
  37. }
  38. // PlaceHorizontal places a string or text block horizontally in an unstyled
  39. // block of a given width. If the given width is shorter than the max width of
  40. // the string (measured by its longest line) this will be a noop.
  41. func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
  42. return renderer.PlaceHorizontal(width, pos, str, opts...)
  43. }
  44. // PlaceHorizontal places a string or text block horizontally in an unstyled
  45. // block of a given width. If the given width is shorter than the max width of
  46. // the string (measured by it's longest line) this will be a noöp.
  47. func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
  48. lines, contentWidth := getLines(str)
  49. gap := width - contentWidth
  50. if gap <= 0 {
  51. return str
  52. }
  53. ws := newWhitespace(r, opts...)
  54. var b strings.Builder
  55. for i, l := range lines {
  56. // Is this line shorter than the longest line?
  57. short := max(0, contentWidth-ansi.PrintableRuneWidth(l))
  58. switch pos {
  59. case Left:
  60. b.WriteString(l)
  61. b.WriteString(ws.render(gap + short))
  62. case Right:
  63. b.WriteString(ws.render(gap + short))
  64. b.WriteString(l)
  65. default: // somewhere in the middle
  66. totalGap := gap + short
  67. split := int(math.Round(float64(totalGap) * pos.value()))
  68. left := totalGap - split
  69. right := totalGap - left
  70. b.WriteString(ws.render(left))
  71. b.WriteString(l)
  72. b.WriteString(ws.render(right))
  73. }
  74. if i < len(lines)-1 {
  75. b.WriteRune('\n')
  76. }
  77. }
  78. return b.String()
  79. }
  80. // PlaceVertical places a string or text block vertically in an unstyled block
  81. // of a given height. If the given height is shorter than the height of the
  82. // string (measured by its newlines) then this will be a noop.
  83. func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
  84. return renderer.PlaceVertical(height, pos, str, opts...)
  85. }
  86. // PlaceVertical places a string or text block vertically in an unstyled block
  87. // of a given height. If the given height is shorter than the height of the
  88. // string (measured by it's newlines) then this will be a noöp.
  89. func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
  90. contentHeight := strings.Count(str, "\n") + 1
  91. gap := height - contentHeight
  92. if gap <= 0 {
  93. return str
  94. }
  95. ws := newWhitespace(r, opts...)
  96. _, width := getLines(str)
  97. emptyLine := ws.render(width)
  98. b := strings.Builder{}
  99. switch pos {
  100. case Top:
  101. b.WriteString(str)
  102. b.WriteRune('\n')
  103. for i := 0; i < gap; i++ {
  104. b.WriteString(emptyLine)
  105. if i < gap-1 {
  106. b.WriteRune('\n')
  107. }
  108. }
  109. case Bottom:
  110. b.WriteString(strings.Repeat(emptyLine+"\n", gap))
  111. b.WriteString(str)
  112. default: // Somewhere in the middle
  113. split := int(math.Round(float64(gap) * pos.value()))
  114. top := gap - split
  115. bottom := gap - top
  116. b.WriteString(strings.Repeat(emptyLine+"\n", top))
  117. b.WriteString(str)
  118. for i := 0; i < bottom; i++ {
  119. b.WriteRune('\n')
  120. b.WriteString(emptyLine)
  121. }
  122. }
  123. return b.String()
  124. }