border.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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. func Borderize(content string, active bool, embeddedText map[BorderPosition]string, activeColor lipgloss.TerminalColor) string {
  22. if embeddedText == nil {
  23. embeddedText = make(map[BorderPosition]string)
  24. }
  25. if activeColor == nil {
  26. activeColor = ActiveBorder
  27. }
  28. var (
  29. thickness = map[bool]lipgloss.Border{
  30. true: lipgloss.Border(lipgloss.ThickBorder()),
  31. false: lipgloss.Border(lipgloss.NormalBorder()),
  32. }
  33. color = map[bool]lipgloss.TerminalColor{
  34. true: activeColor,
  35. false: InactivePreviewBorder,
  36. }
  37. border = thickness[active]
  38. style = lipgloss.NewStyle().Foreground(color[active])
  39. width = lipgloss.Width(content)
  40. )
  41. encloseInSquareBrackets := func(text string) string {
  42. if text != "" {
  43. return fmt.Sprintf("%s%s%s",
  44. style.Render(border.TopRight),
  45. text,
  46. style.Render(border.TopLeft),
  47. )
  48. }
  49. return text
  50. }
  51. buildHorizontalBorder := func(leftText, middleText, rightText, leftCorner, inbetween, rightCorner string) string {
  52. leftText = encloseInSquareBrackets(leftText)
  53. middleText = encloseInSquareBrackets(middleText)
  54. rightText = encloseInSquareBrackets(rightText)
  55. // Calculate length of border between embedded texts
  56. remaining := max(0, width-lipgloss.Width(leftText)-lipgloss.Width(middleText)-lipgloss.Width(rightText))
  57. leftBorderLen := max(0, (width/2)-lipgloss.Width(leftText)-(lipgloss.Width(middleText)/2))
  58. rightBorderLen := max(0, remaining-leftBorderLen)
  59. // Then construct border string
  60. s := leftText +
  61. style.Render(strings.Repeat(inbetween, leftBorderLen)) +
  62. middleText +
  63. style.Render(strings.Repeat(inbetween, rightBorderLen)) +
  64. rightText
  65. // Make it fit in the space available between the two corners.
  66. s = lipgloss.NewStyle().
  67. Inline(true).
  68. MaxWidth(width).
  69. Render(s)
  70. // Add the corners
  71. return style.Render(leftCorner) + s + style.Render(rightCorner)
  72. }
  73. // Stack top border, content and horizontal borders, and bottom border.
  74. return strings.Join([]string{
  75. buildHorizontalBorder(
  76. embeddedText[TopLeftBorder],
  77. embeddedText[TopMiddleBorder],
  78. embeddedText[TopRightBorder],
  79. border.TopLeft,
  80. border.Top,
  81. border.TopRight,
  82. ),
  83. lipgloss.NewStyle().
  84. BorderForeground(color[active]).
  85. Border(border, false, true, false, true).Render(content),
  86. buildHorizontalBorder(
  87. embeddedText[BottomLeftBorder],
  88. embeddedText[BottomMiddleBorder],
  89. embeddedText[BottomRightBorder],
  90. border.BottomLeft,
  91. border.Bottom,
  92. border.BottomRight,
  93. ),
  94. }, "\n")
  95. }