container.go

  1package layout
  2
  3import (
  4	"github.com/charmbracelet/bubbles/key"
  5	tea "github.com/charmbracelet/bubbletea"
  6	"github.com/charmbracelet/lipgloss"
  7	"github.com/opencode-ai/opencode/internal/tui/theme"
  8)
  9
 10type Container interface {
 11	tea.Model
 12	Sizeable
 13	Bindings
 14}
 15type container struct {
 16	width  int
 17	height int
 18
 19	content tea.Model
 20
 21	// Style options
 22	paddingTop    int
 23	paddingRight  int
 24	paddingBottom int
 25	paddingLeft   int
 26
 27	borderTop    bool
 28	borderRight  bool
 29	borderBottom bool
 30	borderLeft   bool
 31	borderStyle  lipgloss.Border
 32}
 33
 34func (c *container) Init() tea.Cmd {
 35	return c.content.Init()
 36}
 37
 38func (c *container) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 39	u, cmd := c.content.Update(msg)
 40	c.content = u
 41	return c, cmd
 42}
 43
 44func (c *container) View() string {
 45	t := theme.CurrentTheme()
 46	style := lipgloss.NewStyle()
 47	width := c.width
 48	height := c.height
 49
 50	style = style.Background(t.Background())
 51
 52	// Apply border if any side is enabled
 53	if c.borderTop || c.borderRight || c.borderBottom || c.borderLeft {
 54		// Adjust width and height for borders
 55		if c.borderTop {
 56			height--
 57		}
 58		if c.borderBottom {
 59			height--
 60		}
 61		if c.borderLeft {
 62			width--
 63		}
 64		if c.borderRight {
 65			width--
 66		}
 67		style = style.Border(c.borderStyle, c.borderTop, c.borderRight, c.borderBottom, c.borderLeft)
 68		style = style.BorderBackground(t.Background()).BorderForeground(t.BorderNormal())
 69	}
 70	style = style.
 71		Width(width).
 72		Height(height).
 73		PaddingTop(c.paddingTop).
 74		PaddingRight(c.paddingRight).
 75		PaddingBottom(c.paddingBottom).
 76		PaddingLeft(c.paddingLeft)
 77
 78	return style.Render(c.content.View())
 79}
 80
 81func (c *container) SetSize(width, height int) tea.Cmd {
 82	c.width = width
 83	c.height = height
 84
 85	// If the content implements Sizeable, adjust its size to account for padding and borders
 86	if sizeable, ok := c.content.(Sizeable); ok {
 87		// Calculate horizontal space taken by padding and borders
 88		horizontalSpace := c.paddingLeft + c.paddingRight
 89		if c.borderLeft {
 90			horizontalSpace++
 91		}
 92		if c.borderRight {
 93			horizontalSpace++
 94		}
 95
 96		// Calculate vertical space taken by padding and borders
 97		verticalSpace := c.paddingTop + c.paddingBottom
 98		if c.borderTop {
 99			verticalSpace++
100		}
101		if c.borderBottom {
102			verticalSpace++
103		}
104
105		// Set content size with adjusted dimensions
106		contentWidth := max(0, width-horizontalSpace)
107		contentHeight := max(0, height-verticalSpace)
108		return sizeable.SetSize(contentWidth, contentHeight)
109	}
110	return nil
111}
112
113func (c *container) GetSize() (int, int) {
114	return c.width, c.height
115}
116
117func (c *container) BindingKeys() []key.Binding {
118	if b, ok := c.content.(Bindings); ok {
119		return b.BindingKeys()
120	}
121	return []key.Binding{}
122}
123
124type ContainerOption func(*container)
125
126func NewContainer(content tea.Model, options ...ContainerOption) Container {
127
128	c := &container{
129		content:     content,
130		borderStyle: lipgloss.NormalBorder(),
131	}
132
133	for _, option := range options {
134		option(c)
135	}
136
137	return c
138}
139
140// Padding options
141func WithPadding(top, right, bottom, left int) ContainerOption {
142	return func(c *container) {
143		c.paddingTop = top
144		c.paddingRight = right
145		c.paddingBottom = bottom
146		c.paddingLeft = left
147	}
148}
149
150func WithPaddingAll(padding int) ContainerOption {
151	return WithPadding(padding, padding, padding, padding)
152}
153
154func WithPaddingHorizontal(padding int) ContainerOption {
155	return func(c *container) {
156		c.paddingLeft = padding
157		c.paddingRight = padding
158	}
159}
160
161func WithPaddingVertical(padding int) ContainerOption {
162	return func(c *container) {
163		c.paddingTop = padding
164		c.paddingBottom = padding
165	}
166}
167
168func WithBorder(top, right, bottom, left bool) ContainerOption {
169	return func(c *container) {
170		c.borderTop = top
171		c.borderRight = right
172		c.borderBottom = bottom
173		c.borderLeft = left
174	}
175}
176
177func WithBorderAll() ContainerOption {
178	return WithBorder(true, true, true, true)
179}
180
181func WithBorderHorizontal() ContainerOption {
182	return WithBorder(true, false, true, false)
183}
184
185func WithBorderVertical() ContainerOption {
186	return WithBorder(false, true, false, true)
187}
188
189func WithBorderStyle(style lipgloss.Border) ContainerOption {
190	return func(c *container) {
191		c.borderStyle = style
192	}
193}
194
195func WithRoundedBorder() ContainerOption {
196	return WithBorderStyle(lipgloss.RoundedBorder())
197}
198
199func WithThickBorder() ContainerOption {
200	return WithBorderStyle(lipgloss.ThickBorder())
201}
202
203func WithDoubleBorder() ContainerOption {
204	return WithBorderStyle(lipgloss.DoubleBorder())
205}