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, BorderOptions{
68 Active: s.focused,
69 EmbeddedText: s.borderText,
70 })
71 }
72 return content
73}
74
75func (s *singlePaneLayout) Blur() tea.Cmd {
76 if s.focusable {
77 s.focused = false
78 }
79 if blurable, ok := s.content.(Focusable); ok {
80 return blurable.Blur()
81 }
82 return nil
83}
84
85func (s *singlePaneLayout) Focus() tea.Cmd {
86 if s.focusable {
87 s.focused = true
88 }
89 if focusable, ok := s.content.(Focusable); ok {
90 return focusable.Focus()
91 }
92 return nil
93}
94
95func (s *singlePaneLayout) SetSize(width, height int) {
96 s.width = width
97 s.height = height
98 childWidth, childHeight := s.width, s.height
99 if s.bordered {
100 childWidth -= 2
101 childHeight -= 2
102 }
103 if s.padding != nil {
104 if len(s.padding) == 1 {
105 childWidth -= s.padding[0] * 2
106 childHeight -= s.padding[0] * 2
107 } else if len(s.padding) == 2 {
108 childWidth -= s.padding[0] * 2
109 childHeight -= s.padding[1] * 2
110 } else if len(s.padding) == 3 {
111 childWidth -= s.padding[0] * 2
112 childHeight -= s.padding[1] + s.padding[2]
113 } else if len(s.padding) == 4 {
114 childWidth -= s.padding[0] + s.padding[2]
115 childHeight -= s.padding[1] + s.padding[3]
116 }
117 }
118 if s.content != nil {
119 if c, ok := s.content.(Sizeable); ok {
120 c.SetSize(childWidth, childHeight)
121 }
122 }
123}
124
125func (s *singlePaneLayout) IsFocused() bool {
126 return s.focused
127}
128
129func (s *singlePaneLayout) GetSize() (int, int) {
130 return s.width, s.height
131}
132
133func (s *singlePaneLayout) BindingKeys() []key.Binding {
134 if b, ok := s.content.(Bindings); ok {
135 return b.BindingKeys()
136 }
137 return []key.Binding{}
138}
139
140func (s *singlePaneLayout) Pane() tea.Model {
141 return s.content
142}
143
144func NewSinglePane(content tea.Model, opts ...SinglePaneOption) SinglePaneLayout {
145 layout := &singlePaneLayout{
146 content: content,
147 }
148 for _, opt := range opts {
149 opt(layout)
150 }
151 return layout
152}
153
154func WithSignlePaneSize(width, height int) SinglePaneOption {
155 return func(opts *singlePaneLayout) {
156 opts.width = width
157 opts.height = height
158 }
159}
160
161func WithSinglePaneFocusable(focusable bool) SinglePaneOption {
162 return func(opts *singlePaneLayout) {
163 opts.focusable = focusable
164 }
165}
166
167func WithSinglePaneBordered(bordered bool) SinglePaneOption {
168 return func(opts *singlePaneLayout) {
169 opts.bordered = bordered
170 }
171}
172
173func WithSignlePaneBorderText(borderText map[BorderPosition]string) SinglePaneOption {
174 return func(opts *singlePaneLayout) {
175 opts.borderText = borderText
176 }
177}
178
179func WithSinglePanePadding(padding ...int) SinglePaneOption {
180 return func(opts *singlePaneLayout) {
181 opts.padding = padding
182 }
183}
184
185func WithSinglePaneActiveColor(color lipgloss.TerminalColor) SinglePaneOption {
186 return func(opts *singlePaneLayout) {
187 opts.activeColor = color
188 }
189}