single.go

  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}