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