container.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package layout
  2. import (
  3. "github.com/charmbracelet/bubbles/key"
  4. tea "github.com/charmbracelet/bubbletea"
  5. "github.com/charmbracelet/lipgloss"
  6. "github.com/kujtimiihoxha/termai/internal/tui/styles"
  7. )
  8. type Container interface {
  9. tea.Model
  10. Sizeable
  11. }
  12. type container struct {
  13. width int
  14. height int
  15. content tea.Model
  16. // Style options
  17. paddingTop int
  18. paddingRight int
  19. paddingBottom int
  20. paddingLeft int
  21. borderTop bool
  22. borderRight bool
  23. borderBottom bool
  24. borderLeft bool
  25. borderStyle lipgloss.Border
  26. borderColor lipgloss.TerminalColor
  27. backgroundColor lipgloss.TerminalColor
  28. }
  29. func (c *container) Init() tea.Cmd {
  30. return c.content.Init()
  31. }
  32. func (c *container) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  33. u, cmd := c.content.Update(msg)
  34. c.content = u
  35. return c, cmd
  36. }
  37. func (c *container) View() string {
  38. style := lipgloss.NewStyle()
  39. width := c.width
  40. height := c.height
  41. // Apply background color if specified
  42. if c.backgroundColor != nil {
  43. style = style.Background(c.backgroundColor)
  44. }
  45. // Apply border if any side is enabled
  46. if c.borderTop || c.borderRight || c.borderBottom || c.borderLeft {
  47. // Adjust width and height for borders
  48. if c.borderTop {
  49. height--
  50. }
  51. if c.borderBottom {
  52. height--
  53. }
  54. if c.borderLeft {
  55. width--
  56. }
  57. if c.borderRight {
  58. width--
  59. }
  60. style = style.Border(c.borderStyle, c.borderTop, c.borderRight, c.borderBottom, c.borderLeft)
  61. // Apply border color if specified
  62. if c.borderColor != nil {
  63. style = style.BorderBackground(c.backgroundColor).BorderForeground(c.borderColor)
  64. }
  65. }
  66. style = style.
  67. Width(width).
  68. Height(height).
  69. PaddingTop(c.paddingTop).
  70. PaddingRight(c.paddingRight).
  71. PaddingBottom(c.paddingBottom).
  72. PaddingLeft(c.paddingLeft)
  73. return style.Render(c.content.View())
  74. }
  75. func (c *container) SetSize(width, height int) {
  76. c.width = width
  77. c.height = height
  78. // If the content implements Sizeable, adjust its size to account for padding and borders
  79. if sizeable, ok := c.content.(Sizeable); ok {
  80. // Calculate horizontal space taken by padding and borders
  81. horizontalSpace := c.paddingLeft + c.paddingRight
  82. if c.borderLeft {
  83. horizontalSpace++
  84. }
  85. if c.borderRight {
  86. horizontalSpace++
  87. }
  88. // Calculate vertical space taken by padding and borders
  89. verticalSpace := c.paddingTop + c.paddingBottom
  90. if c.borderTop {
  91. verticalSpace++
  92. }
  93. if c.borderBottom {
  94. verticalSpace++
  95. }
  96. // Set content size with adjusted dimensions
  97. contentWidth := max(0, width-horizontalSpace)
  98. contentHeight := max(0, height-verticalSpace)
  99. sizeable.SetSize(contentWidth, contentHeight)
  100. }
  101. }
  102. func (c *container) GetSize() (int, int) {
  103. return c.width, c.height
  104. }
  105. func (c *container) BindingKeys() []key.Binding {
  106. if b, ok := c.content.(Bindings); ok {
  107. return b.BindingKeys()
  108. }
  109. return []key.Binding{}
  110. }
  111. type ContainerOption func(*container)
  112. func NewContainer(content tea.Model, options ...ContainerOption) Container {
  113. c := &container{
  114. content: content,
  115. borderColor: styles.BorderColor,
  116. borderStyle: lipgloss.NormalBorder(),
  117. backgroundColor: styles.Background,
  118. }
  119. for _, option := range options {
  120. option(c)
  121. }
  122. return c
  123. }
  124. // Padding options
  125. func WithPadding(top, right, bottom, left int) ContainerOption {
  126. return func(c *container) {
  127. c.paddingTop = top
  128. c.paddingRight = right
  129. c.paddingBottom = bottom
  130. c.paddingLeft = left
  131. }
  132. }
  133. func WithPaddingAll(padding int) ContainerOption {
  134. return WithPadding(padding, padding, padding, padding)
  135. }
  136. func WithPaddingHorizontal(padding int) ContainerOption {
  137. return func(c *container) {
  138. c.paddingLeft = padding
  139. c.paddingRight = padding
  140. }
  141. }
  142. func WithPaddingVertical(padding int) ContainerOption {
  143. return func(c *container) {
  144. c.paddingTop = padding
  145. c.paddingBottom = padding
  146. }
  147. }
  148. func WithBorder(top, right, bottom, left bool) ContainerOption {
  149. return func(c *container) {
  150. c.borderTop = top
  151. c.borderRight = right
  152. c.borderBottom = bottom
  153. c.borderLeft = left
  154. }
  155. }
  156. func WithBorderAll() ContainerOption {
  157. return WithBorder(true, true, true, true)
  158. }
  159. func WithBorderHorizontal() ContainerOption {
  160. return WithBorder(true, false, true, false)
  161. }
  162. func WithBorderVertical() ContainerOption {
  163. return WithBorder(false, true, false, true)
  164. }
  165. func WithBorderStyle(style lipgloss.Border) ContainerOption {
  166. return func(c *container) {
  167. c.borderStyle = style
  168. }
  169. }
  170. func WithBorderColor(color lipgloss.TerminalColor) ContainerOption {
  171. return func(c *container) {
  172. c.borderColor = color
  173. }
  174. }
  175. func WithRoundedBorder() ContainerOption {
  176. return WithBorderStyle(lipgloss.RoundedBorder())
  177. }
  178. func WithThickBorder() ContainerOption {
  179. return WithBorderStyle(lipgloss.ThickBorder())
  180. }
  181. func WithDoubleBorder() ContainerOption {
  182. return WithBorderStyle(lipgloss.DoubleBorder())
  183. }
  184. func WithBackgroundColor(color lipgloss.TerminalColor) ContainerOption {
  185. return func(c *container) {
  186. c.backgroundColor = color
  187. }
  188. }