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 childWidth, childHeight := s.width, s.height
97 if s.padding != nil {
98 if len(s.padding) == 1 {
99 childWidth -= s.padding[0] * 2
100 childHeight -= s.padding[0] * 2
101 } else if len(s.padding) == 2 {
102 childWidth -= s.padding[0] * 2
103 childHeight -= s.padding[1] * 2
104 } else if len(s.padding) == 3 {
105 childWidth -= s.padding[0] * 2
106 childHeight -= s.padding[1] + s.padding[2]
107 } else if len(s.padding) == 4 {
108 childWidth -= s.padding[0] + s.padding[2]
109 childHeight -= s.padding[1] + s.padding[3]
110 }
111 }
112 if s.content != nil {
113 if c, ok := s.content.(Sizeable); ok {
114 c.SetSize(childWidth, childHeight)
115 }
116 }
117}
118
119func (s *singlePaneLayout) IsFocused() bool {
120 return s.focused
121}
122
123func (s *singlePaneLayout) GetSize() (int, int) {
124 return s.width, s.height
125}
126
127func (s *singlePaneLayout) BindingKeys() []key.Binding {
128 if b, ok := s.content.(Bindings); ok {
129 return b.BindingKeys()
130 }
131 return []key.Binding{}
132}
133
134func NewSinglePane(content tea.Model, opts ...SinglePaneOption) SinglePaneLayout {
135 layout := &singlePaneLayout{
136 content: content,
137 }
138 for _, opt := range opts {
139 opt(layout)
140 }
141 return layout
142}
143
144func WithSignlePaneSize(width, height int) SinglePaneOption {
145 return func(opts *singlePaneLayout) {
146 opts.width = width
147 opts.height = height
148 }
149}
150
151func WithSinglePaneFocusable(focusable bool) SinglePaneOption {
152 return func(opts *singlePaneLayout) {
153 opts.focusable = focusable
154 }
155}
156
157func WithSinglePaneBordered(bordered bool) SinglePaneOption {
158 return func(opts *singlePaneLayout) {
159 opts.bordered = bordered
160 }
161}
162
163func WithSignlePaneBorderText(borderText map[BorderPosition]string) SinglePaneOption {
164 return func(opts *singlePaneLayout) {
165 opts.borderText = borderText
166 }
167}
168
169func WithSinglePanePadding(padding ...int) SinglePaneOption {
170 return func(opts *singlePaneLayout) {
171 opts.padding = padding
172 }
173}