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) tea.Cmd {
 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		return sizeable.SetSize(contentWidth, contentHeight)
117	}
118	return nil
119}
120
121func (c *container) GetSize() (int, int) {
122	return c.width, c.height
123}
124
125func (c *container) BindingKeys() []key.Binding {
126	if b, ok := c.content.(Bindings); ok {
127		return b.BindingKeys()
128	}
129	return []key.Binding{}
130}
131
132type ContainerOption func(*container)
133
134func NewContainer(content tea.Model, options ...ContainerOption) Container {
135	c := &container{
136		content:         content,
137		borderColor:     styles.BorderColor,
138		borderStyle:     lipgloss.NormalBorder(),
139		backgroundColor: styles.Background,
140	}
141
142	for _, option := range options {
143		option(c)
144	}
145
146	return c
147}
148
149// Padding options
150func WithPadding(top, right, bottom, left int) ContainerOption {
151	return func(c *container) {
152		c.paddingTop = top
153		c.paddingRight = right
154		c.paddingBottom = bottom
155		c.paddingLeft = left
156	}
157}
158
159func WithPaddingAll(padding int) ContainerOption {
160	return WithPadding(padding, padding, padding, padding)
161}
162
163func WithPaddingHorizontal(padding int) ContainerOption {
164	return func(c *container) {
165		c.paddingLeft = padding
166		c.paddingRight = padding
167	}
168}
169
170func WithPaddingVertical(padding int) ContainerOption {
171	return func(c *container) {
172		c.paddingTop = padding
173		c.paddingBottom = padding
174	}
175}
176
177func WithBorder(top, right, bottom, left bool) ContainerOption {
178	return func(c *container) {
179		c.borderTop = top
180		c.borderRight = right
181		c.borderBottom = bottom
182		c.borderLeft = left
183	}
184}
185
186func WithBorderAll() ContainerOption {
187	return WithBorder(true, true, true, true)
188}
189
190func WithBorderHorizontal() ContainerOption {
191	return WithBorder(true, false, true, false)
192}
193
194func WithBorderVertical() ContainerOption {
195	return WithBorder(false, true, false, true)
196}
197
198func WithBorderStyle(style lipgloss.Border) ContainerOption {
199	return func(c *container) {
200		c.borderStyle = style
201	}
202}
203
204func WithBorderColor(color lipgloss.TerminalColor) ContainerOption {
205	return func(c *container) {
206		c.borderColor = color
207	}
208}
209
210func WithRoundedBorder() ContainerOption {
211	return WithBorderStyle(lipgloss.RoundedBorder())
212}
213
214func WithThickBorder() ContainerOption {
215	return WithBorderStyle(lipgloss.ThickBorder())
216}
217
218func WithDoubleBorder() ContainerOption {
219	return WithBorderStyle(lipgloss.DoubleBorder())
220}
221
222func WithBackgroundColor(color lipgloss.TerminalColor) ContainerOption {
223	return func(c *container) {
224		c.backgroundColor = color
225	}
226}