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}