split.go

  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}