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}