container.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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/opencode-ai/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) tea.Cmd {
  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. return sizeable.SetSize(contentWidth, contentHeight)
  101. }
  102. return nil
  103. }
  104. func (c *container) GetSize() (int, int) {
  105. return c.width, c.height
  106. }
  107. func (c *container) BindingKeys() []key.Binding {
  108. if b, ok := c.content.(Bindings); ok {
  109. return b.BindingKeys()
  110. }
  111. return []key.Binding{}
  112. }
  113. type ContainerOption func(*container)
  114. func NewContainer(content tea.Model, options ...ContainerOption) Container {
  115. c := &container{
  116. content: content,
  117. borderColor: styles.BorderColor,
  118. borderStyle: lipgloss.NormalBorder(),
  119. backgroundColor: styles.Background,
  120. }
  121. for _, option := range options {
  122. option(c)
  123. }
  124. return c
  125. }
  126. // Padding options
  127. func WithPadding(top, right, bottom, left int) ContainerOption {
  128. return func(c *container) {
  129. c.paddingTop = top
  130. c.paddingRight = right
  131. c.paddingBottom = bottom
  132. c.paddingLeft = left
  133. }
  134. }
  135. func WithPaddingAll(padding int) ContainerOption {
  136. return WithPadding(padding, padding, padding, padding)
  137. }
  138. func WithPaddingHorizontal(padding int) ContainerOption {
  139. return func(c *container) {
  140. c.paddingLeft = padding
  141. c.paddingRight = padding
  142. }
  143. }
  144. func WithPaddingVertical(padding int) ContainerOption {
  145. return func(c *container) {
  146. c.paddingTop = padding
  147. c.paddingBottom = padding
  148. }
  149. }
  150. func WithBorder(top, right, bottom, left bool) ContainerOption {
  151. return func(c *container) {
  152. c.borderTop = top
  153. c.borderRight = right
  154. c.borderBottom = bottom
  155. c.borderLeft = left
  156. }
  157. }
  158. func WithBorderAll() ContainerOption {
  159. return WithBorder(true, true, true, true)
  160. }
  161. func WithBorderHorizontal() ContainerOption {
  162. return WithBorder(true, false, true, false)
  163. }
  164. func WithBorderVertical() ContainerOption {
  165. return WithBorder(false, true, false, true)
  166. }
  167. func WithBorderStyle(style lipgloss.Border) ContainerOption {
  168. return func(c *container) {
  169. c.borderStyle = style
  170. }
  171. }
  172. func WithBorderColor(color lipgloss.TerminalColor) ContainerOption {
  173. return func(c *container) {
  174. c.borderColor = color
  175. }
  176. }
  177. func WithRoundedBorder() ContainerOption {
  178. return WithBorderStyle(lipgloss.RoundedBorder())
  179. }
  180. func WithThickBorder() ContainerOption {
  181. return WithBorderStyle(lipgloss.ThickBorder())
  182. }
  183. func WithDoubleBorder() ContainerOption {
  184. return WithBorderStyle(lipgloss.DoubleBorder())
  185. }
  186. func WithBackgroundColor(color lipgloss.TerminalColor) ContainerOption {
  187. return func(c *container) {
  188. c.backgroundColor = color
  189. }
  190. }