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}
 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}