container.go 4.3 KB

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