split.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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 SplitPaneLayout interface {
  9. tea.Model
  10. Sizeable
  11. }
  12. type splitPaneLayout struct {
  13. width int
  14. height int
  15. ratio float64
  16. verticalRatio float64
  17. rightPanel Container
  18. leftPanel Container
  19. bottomPanel Container
  20. backgroundColor lipgloss.TerminalColor
  21. }
  22. type SplitPaneOption func(*splitPaneLayout)
  23. func (s *splitPaneLayout) Init() tea.Cmd {
  24. var cmds []tea.Cmd
  25. if s.leftPanel != nil {
  26. cmds = append(cmds, s.leftPanel.Init())
  27. }
  28. if s.rightPanel != nil {
  29. cmds = append(cmds, s.rightPanel.Init())
  30. }
  31. if s.bottomPanel != nil {
  32. cmds = append(cmds, s.bottomPanel.Init())
  33. }
  34. return tea.Batch(cmds...)
  35. }
  36. func (s *splitPaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  37. var cmds []tea.Cmd
  38. switch msg := msg.(type) {
  39. case tea.WindowSizeMsg:
  40. s.SetSize(msg.Width, msg.Height)
  41. return s, nil
  42. }
  43. if s.rightPanel != nil {
  44. u, cmd := s.rightPanel.Update(msg)
  45. s.rightPanel = u.(Container)
  46. if cmd != nil {
  47. cmds = append(cmds, cmd)
  48. }
  49. }
  50. if s.leftPanel != nil {
  51. u, cmd := s.leftPanel.Update(msg)
  52. s.leftPanel = u.(Container)
  53. if cmd != nil {
  54. cmds = append(cmds, cmd)
  55. }
  56. }
  57. if s.bottomPanel != nil {
  58. u, cmd := s.bottomPanel.Update(msg)
  59. s.bottomPanel = u.(Container)
  60. if cmd != nil {
  61. cmds = append(cmds, cmd)
  62. }
  63. }
  64. return s, tea.Batch(cmds...)
  65. }
  66. func (s *splitPaneLayout) View() string {
  67. var topSection string
  68. if s.leftPanel != nil && s.rightPanel != nil {
  69. leftView := s.leftPanel.View()
  70. rightView := s.rightPanel.View()
  71. topSection = lipgloss.JoinHorizontal(lipgloss.Top, leftView, rightView)
  72. } else if s.leftPanel != nil {
  73. topSection = s.leftPanel.View()
  74. } else if s.rightPanel != nil {
  75. topSection = s.rightPanel.View()
  76. } else {
  77. topSection = ""
  78. }
  79. var finalView string
  80. if s.bottomPanel != nil && topSection != "" {
  81. bottomView := s.bottomPanel.View()
  82. finalView = lipgloss.JoinVertical(lipgloss.Left, topSection, bottomView)
  83. } else if s.bottomPanel != nil {
  84. finalView = s.bottomPanel.View()
  85. } else {
  86. finalView = topSection
  87. }
  88. if s.backgroundColor != nil && finalView != "" {
  89. style := lipgloss.NewStyle().
  90. Width(s.width).
  91. Height(s.height).
  92. Background(s.backgroundColor)
  93. return style.Render(finalView)
  94. }
  95. return finalView
  96. }
  97. func (s *splitPaneLayout) SetSize(width, height int) {
  98. s.width = width
  99. s.height = height
  100. var topHeight, bottomHeight int
  101. if s.bottomPanel != nil {
  102. topHeight = int(float64(height) * s.verticalRatio)
  103. bottomHeight = height - topHeight
  104. } else {
  105. topHeight = height
  106. bottomHeight = 0
  107. }
  108. var leftWidth, rightWidth int
  109. if s.leftPanel != nil && s.rightPanel != nil {
  110. leftWidth = int(float64(width) * s.ratio)
  111. rightWidth = width - leftWidth
  112. } else if s.leftPanel != nil {
  113. leftWidth = width
  114. rightWidth = 0
  115. } else if s.rightPanel != nil {
  116. leftWidth = 0
  117. rightWidth = width
  118. }
  119. if s.leftPanel != nil {
  120. s.leftPanel.SetSize(leftWidth, topHeight)
  121. }
  122. if s.rightPanel != nil {
  123. s.rightPanel.SetSize(rightWidth, topHeight)
  124. }
  125. if s.bottomPanel != nil {
  126. s.bottomPanel.SetSize(width, bottomHeight)
  127. }
  128. }
  129. func (s *splitPaneLayout) GetSize() (int, int) {
  130. return s.width, s.height
  131. }
  132. func (s *splitPaneLayout) BindingKeys() []key.Binding {
  133. keys := []key.Binding{}
  134. if s.leftPanel != nil {
  135. if b, ok := s.leftPanel.(Bindings); ok {
  136. keys = append(keys, b.BindingKeys()...)
  137. }
  138. }
  139. if s.rightPanel != nil {
  140. if b, ok := s.rightPanel.(Bindings); ok {
  141. keys = append(keys, b.BindingKeys()...)
  142. }
  143. }
  144. if s.bottomPanel != nil {
  145. if b, ok := s.bottomPanel.(Bindings); ok {
  146. keys = append(keys, b.BindingKeys()...)
  147. }
  148. }
  149. return keys
  150. }
  151. func NewSplitPane(options ...SplitPaneOption) SplitPaneLayout {
  152. layout := &splitPaneLayout{
  153. ratio: 0.7,
  154. verticalRatio: 0.9, // Default 80% for top section, 20% for bottom
  155. backgroundColor: styles.Background,
  156. }
  157. for _, option := range options {
  158. option(layout)
  159. }
  160. return layout
  161. }
  162. func WithLeftPanel(panel Container) SplitPaneOption {
  163. return func(s *splitPaneLayout) {
  164. s.leftPanel = panel
  165. }
  166. }
  167. func WithRightPanel(panel Container) SplitPaneOption {
  168. return func(s *splitPaneLayout) {
  169. s.rightPanel = panel
  170. }
  171. }
  172. func WithRatio(ratio float64) SplitPaneOption {
  173. return func(s *splitPaneLayout) {
  174. s.ratio = ratio
  175. }
  176. }
  177. func WithSplitBackgroundColor(color lipgloss.TerminalColor) SplitPaneOption {
  178. return func(s *splitPaneLayout) {
  179. s.backgroundColor = color
  180. }
  181. }
  182. func WithBottomPanel(panel Container) SplitPaneOption {
  183. return func(s *splitPaneLayout) {
  184. s.bottomPanel = panel
  185. }
  186. }
  187. func WithVerticalRatio(ratio float64) SplitPaneOption {
  188. return func(s *splitPaneLayout) {
  189. s.verticalRatio = ratio
  190. }
  191. }