border.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package layout
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/charmbracelet/lipgloss"
  6. "github.com/kujtimiihoxha/termai/internal/tui/styles"
  7. )
  8. type BorderPosition int
  9. const (
  10. TopLeftBorder BorderPosition = iota
  11. TopMiddleBorder
  12. TopRightBorder
  13. BottomLeftBorder
  14. BottomMiddleBorder
  15. BottomRightBorder
  16. )
  17. var (
  18. ActiveBorder = styles.Blue
  19. InactivePreviewBorder = styles.Grey
  20. )
  21. type BorderOptions struct {
  22. Active bool
  23. EmbeddedText map[BorderPosition]string
  24. ActiveColor lipgloss.TerminalColor
  25. InactiveColor lipgloss.TerminalColor
  26. ActiveBorder lipgloss.Border
  27. InactiveBorder lipgloss.Border
  28. }
  29. func Borderize(content string, opts BorderOptions) string {
  30. if opts.EmbeddedText == nil {
  31. opts.EmbeddedText = make(map[BorderPosition]string)
  32. }
  33. if opts.ActiveColor == nil {
  34. opts.ActiveColor = ActiveBorder
  35. }
  36. if opts.InactiveColor == nil {
  37. opts.InactiveColor = InactivePreviewBorder
  38. }
  39. if opts.ActiveBorder == (lipgloss.Border{}) {
  40. opts.ActiveBorder = lipgloss.ThickBorder()
  41. }
  42. if opts.InactiveBorder == (lipgloss.Border{}) {
  43. opts.InactiveBorder = lipgloss.NormalBorder()
  44. }
  45. var (
  46. thickness = map[bool]lipgloss.Border{
  47. true: opts.ActiveBorder,
  48. false: opts.InactiveBorder,
  49. }
  50. color = map[bool]lipgloss.TerminalColor{
  51. true: opts.ActiveColor,
  52. false: opts.InactiveColor,
  53. }
  54. border = thickness[opts.Active]
  55. style = lipgloss.NewStyle().Foreground(color[opts.Active])
  56. width = lipgloss.Width(content)
  57. )
  58. encloseInSquareBrackets := func(text string) string {
  59. if text != "" {
  60. return fmt.Sprintf("%s%s%s",
  61. style.Render(border.TopRight),
  62. text,
  63. style.Render(border.TopLeft),
  64. )
  65. }
  66. return text
  67. }
  68. buildHorizontalBorder := func(leftText, middleText, rightText, leftCorner, inbetween, rightCorner string) string {
  69. leftText = encloseInSquareBrackets(leftText)
  70. middleText = encloseInSquareBrackets(middleText)
  71. rightText = encloseInSquareBrackets(rightText)
  72. // Calculate length of border between embedded texts
  73. remaining := max(0, width-lipgloss.Width(leftText)-lipgloss.Width(middleText)-lipgloss.Width(rightText))
  74. leftBorderLen := max(0, (width/2)-lipgloss.Width(leftText)-(lipgloss.Width(middleText)/2))
  75. rightBorderLen := max(0, remaining-leftBorderLen)
  76. // Then construct border string
  77. s := leftText +
  78. style.Render(strings.Repeat(inbetween, leftBorderLen)) +
  79. middleText +
  80. style.Render(strings.Repeat(inbetween, rightBorderLen)) +
  81. rightText
  82. // Make it fit in the space available between the two corners.
  83. s = lipgloss.NewStyle().
  84. Inline(true).
  85. MaxWidth(width).
  86. Render(s)
  87. // Add the corners
  88. return style.Render(leftCorner) + s + style.Render(rightCorner)
  89. }
  90. // Stack top border, content and horizontal borders, and bottom border.
  91. return strings.Join([]string{
  92. buildHorizontalBorder(
  93. opts.EmbeddedText[TopLeftBorder],
  94. opts.EmbeddedText[TopMiddleBorder],
  95. opts.EmbeddedText[TopRightBorder],
  96. border.TopLeft,
  97. border.Top,
  98. border.TopRight,
  99. ),
  100. lipgloss.NewStyle().
  101. BorderForeground(color[opts.Active]).
  102. Border(border, false, true, false, true).Render(content),
  103. buildHorizontalBorder(
  104. opts.EmbeddedText[BottomLeftBorder],
  105. opts.EmbeddedText[BottomMiddleBorder],
  106. opts.EmbeddedText[BottomRightBorder],
  107. border.BottomLeft,
  108. border.Bottom,
  109. border.BottomRight,
  110. ),
  111. }, "\n")
  112. }