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