1package layout
2
3import (
4 "github.com/charmbracelet/bubbles/key"
5 tea "github.com/charmbracelet/bubbletea"
6 "github.com/charmbracelet/lipgloss"
7)
8
9type SinglePaneLayout interface {
10 tea.Model
11 Focusable
12 Sizeable
13 Bindings
14 Pane() tea.Model
15}
16
17type singlePaneLayout struct {
18 width int
19 height int
20
21 focusable bool
22 focused bool
23
24 bordered bool
25 borderText map[BorderPosition]string
26
27 content tea.Model
28
29 padding []int
30
31 activeColor lipgloss.TerminalColor
32}
33
34type SinglePaneOption func(*singlePaneLayout)
35
36func (s *singlePaneLayout) Init() tea.Cmd {
37 return s.content.Init()
38}
39
40func (s *singlePaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
41 switch msg := msg.(type) {
42 case tea.WindowSizeMsg:
43 s.SetSize(msg.Width, msg.Height)
44 return s, nil
45 }
46 u, cmd := s.content.Update(msg)
47 s.content = u
48 return s, cmd
49}
50
51func (s *singlePaneLayout) View() string {
52 style := lipgloss.NewStyle().Width(s.width).Height(s.height)
53 if s.bordered {
54 style = style.Width(s.width - 2).Height(s.height - 2)
55 }
56 if s.padding != nil {
57 style = style.Padding(s.padding...)
58 }
59 content := style.Render(s.content.View())
60 if s.bordered {
61 if s.borderText == nil {
62 s.borderText = map[BorderPosition]string{}
63 }
64 if bordered, ok := s.content.(Bordered); ok {
65 s.borderText = bordered.BorderText()
66 }
67 return Borderize(content, s.focused, s.borderText, s.activeColor)
68 }
69 return content
70}
71
72func (s *singlePaneLayout) Blur() tea.Cmd {
73 if s.focusable {
74 s.focused = false
75 }
76 if blurable, ok := s.content.(Focusable); ok {
77 return blurable.Blur()
78 }
79 return nil
80}
81
82func (s *singlePaneLayout) Focus() tea.Cmd {
83 if s.focusable {
84 s.focused = true
85 }
86 if focusable, ok := s.content.(Focusable); ok {
87 return focusable.Focus()
88 }
89 return nil
90}
91
92func (s *singlePaneLayout) SetSize(width, height int) {
93 s.width = width
94 s.height = height
95 childWidth, childHeight := s.width, s.height
96 if s.bordered {
97 childWidth -= 2
98 childHeight -= 2
99 }
100 if s.padding != nil {
101 if len(s.padding) == 1 {
102 childWidth -= s.padding[0] * 2
103 childHeight -= s.padding[0] * 2
104 } else if len(s.padding) == 2 {
105 childWidth -= s.padding[0] * 2
106 childHeight -= s.padding[1] * 2
107 } else if len(s.padding) == 3 {
108 childWidth -= s.padding[0] * 2
109 childHeight -= s.padding[1] + s.padding[2]
110 } else if len(s.padding) == 4 {
111 childWidth -= s.padding[0] + s.padding[2]
112 childHeight -= s.padding[1] + s.padding[3]
113 }
114 }
115 if s.content != nil {
116 if c, ok := s.content.(Sizeable); ok {
117 c.SetSize(childWidth, childHeight)
118 }
119 }
120}
121
122func (s *singlePaneLayout) IsFocused() bool {
123 return s.focused
124}
125
126func (s *singlePaneLayout) GetSize() (int, int) {
127 return s.width, s.height
128}
129
130func (s *singlePaneLayout) BindingKeys() []key.Binding {
131 if b, ok := s.content.(Bindings); ok {
132 return b.BindingKeys()
133 }
134 return []key.Binding{}
135}
136
137func (s *singlePaneLayout) Pane() tea.Model {
138 return s.content
139}
140
141func NewSinglePane(content tea.Model, opts ...SinglePaneOption) SinglePaneLayout {
142 layout := &singlePaneLayout{
143 content: content,
144 }
145 for _, opt := range opts {
146 opt(layout)
147 }
148 return layout
149}
150
151func WithSignlePaneSize(width, height int) SinglePaneOption {
152 return func(opts *singlePaneLayout) {
153 opts.width = width
154 opts.height = height
155 }
156}
157
158func WithSinglePaneFocusable(focusable bool) SinglePaneOption {
159 return func(opts *singlePaneLayout) {
160 opts.focusable = focusable
161 }
162}
163
164func WithSinglePaneBordered(bordered bool) SinglePaneOption {
165 return func(opts *singlePaneLayout) {
166 opts.bordered = bordered
167 }
168}
169
170func WithSignlePaneBorderText(borderText map[BorderPosition]string) SinglePaneOption {
171 return func(opts *singlePaneLayout) {
172 opts.borderText = borderText
173 }
174}
175
176func WithSinglePanePadding(padding ...int) SinglePaneOption {
177 return func(opts *singlePaneLayout) {
178 opts.padding = padding
179 }
180}
181
182func WithSinglePaneActiveColor(color lipgloss.TerminalColor) SinglePaneOption {
183 return func(opts *singlePaneLayout) {
184 opts.activeColor = color
185 }
186}