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