container.go 4.8 KB

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