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