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/kujtimiihoxha/opencode/internal/tui/styles"
  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	borderColor  lipgloss.TerminalColor
 33
 34	backgroundColor lipgloss.TerminalColor
 35}
 36
 37func (c *container) Init() tea.Cmd {
 38	return c.content.Init()
 39}
 40
 41func (c *container) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 42	u, cmd := c.content.Update(msg)
 43	c.content = u
 44	return c, cmd
 45}
 46
 47func (c *container) View() string {
 48	style := lipgloss.NewStyle()
 49	width := c.width
 50	height := c.height
 51	// Apply background color if specified
 52	if c.backgroundColor != nil {
 53		style = style.Background(c.backgroundColor)
 54	}
 55
 56	// Apply border if any side is enabled
 57	if c.borderTop || c.borderRight || c.borderBottom || c.borderLeft {
 58		// Adjust width and height for borders
 59		if c.borderTop {
 60			height--
 61		}
 62		if c.borderBottom {
 63			height--
 64		}
 65		if c.borderLeft {
 66			width--
 67		}
 68		if c.borderRight {
 69			width--
 70		}
 71		style = style.Border(c.borderStyle, c.borderTop, c.borderRight, c.borderBottom, c.borderLeft)
 72
 73		// Apply border color if specified
 74		if c.borderColor != nil {
 75			style = style.BorderBackground(c.backgroundColor).BorderForeground(c.borderColor)
 76		}
 77	}
 78	style = style.
 79		Width(width).
 80		Height(height).
 81		PaddingTop(c.paddingTop).
 82		PaddingRight(c.paddingRight).
 83		PaddingBottom(c.paddingBottom).
 84		PaddingLeft(c.paddingLeft)
 85
 86	return style.Render(c.content.View())
 87}
 88
 89func (c *container) SetSize(width, height int) {
 90	c.width = width
 91	c.height = height
 92
 93	// If the content implements Sizeable, adjust its size to account for padding and borders
 94	if sizeable, ok := c.content.(Sizeable); ok {
 95		// Calculate horizontal space taken by padding and borders
 96		horizontalSpace := c.paddingLeft + c.paddingRight
 97		if c.borderLeft {
 98			horizontalSpace++
 99		}
100		if c.borderRight {
101			horizontalSpace++
102		}
103
104		// Calculate vertical space taken by padding and borders
105		verticalSpace := c.paddingTop + c.paddingBottom
106		if c.borderTop {
107			verticalSpace++
108		}
109		if c.borderBottom {
110			verticalSpace++
111		}
112
113		// Set content size with adjusted dimensions
114		contentWidth := max(0, width-horizontalSpace)
115		contentHeight := max(0, height-verticalSpace)
116		sizeable.SetSize(contentWidth, contentHeight)
117	}
118}
119
120func (c *container) GetSize() (int, int) {
121	return c.width, c.height
122}
123
124func (c *container) BindingKeys() []key.Binding {
125	if b, ok := c.content.(Bindings); ok {
126		return b.BindingKeys()
127	}
128	return []key.Binding{}
129}
130
131type ContainerOption func(*container)
132
133func NewContainer(content tea.Model, options ...ContainerOption) Container {
134	c := &container{
135		content:         content,
136		borderColor:     styles.BorderColor,
137		borderStyle:     lipgloss.NormalBorder(),
138		backgroundColor: styles.Background,
139	}
140
141	for _, option := range options {
142		option(c)
143	}
144
145	return c
146}
147
148// Padding options
149func WithPadding(top, right, bottom, left int) ContainerOption {
150	return func(c *container) {
151		c.paddingTop = top
152		c.paddingRight = right
153		c.paddingBottom = bottom
154		c.paddingLeft = left
155	}
156}
157
158func WithPaddingAll(padding int) ContainerOption {
159	return WithPadding(padding, padding, padding, padding)
160}
161
162func WithPaddingHorizontal(padding int) ContainerOption {
163	return func(c *container) {
164		c.paddingLeft = padding
165		c.paddingRight = padding
166	}
167}
168
169func WithPaddingVertical(padding int) ContainerOption {
170	return func(c *container) {
171		c.paddingTop = padding
172		c.paddingBottom = padding
173	}
174}
175
176func WithBorder(top, right, bottom, left bool) ContainerOption {
177	return func(c *container) {
178		c.borderTop = top
179		c.borderRight = right
180		c.borderBottom = bottom
181		c.borderLeft = left
182	}
183}
184
185func WithBorderAll() ContainerOption {
186	return WithBorder(true, true, true, true)
187}
188
189func WithBorderHorizontal() ContainerOption {
190	return WithBorder(true, false, true, false)
191}
192
193func WithBorderVertical() ContainerOption {
194	return WithBorder(false, true, false, true)
195}
196
197func WithBorderStyle(style lipgloss.Border) ContainerOption {
198	return func(c *container) {
199		c.borderStyle = style
200	}
201}
202
203func WithBorderColor(color lipgloss.TerminalColor) ContainerOption {
204	return func(c *container) {
205		c.borderColor = color
206	}
207}
208
209func WithRoundedBorder() ContainerOption {
210	return WithBorderStyle(lipgloss.RoundedBorder())
211}
212
213func WithThickBorder() ContainerOption {
214	return WithBorderStyle(lipgloss.ThickBorder())
215}
216
217func WithDoubleBorder() ContainerOption {
218	return WithBorderStyle(lipgloss.DoubleBorder())
219}
220
221func WithBackgroundColor(color lipgloss.TerminalColor) ContainerOption {
222	return func(c *container) {
223		c.backgroundColor = color
224	}
225}