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, 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 WithSinglePaneSize(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 WithSinglePaneBorderText(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}