diff options
| author | Dax Raad <[email protected]> | 2025-05-18 22:30:41 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-05-26 12:40:17 -0400 |
| commit | 99af6146d5def31c59993636d60eb75a483a283b (patch) | |
| tree | eb57ed227c15cf9be54bc9f231c83895d812f881 /pkg/tui/layout/container.go | |
| parent | 020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff) | |
| download | opencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz opencode-99af6146d5def31c59993636d60eb75a483a283b.zip | |
openapi
Diffstat (limited to 'pkg/tui/layout/container.go')
| -rw-r--r-- | pkg/tui/layout/container.go | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/pkg/tui/layout/container.go b/pkg/tui/layout/container.go new file mode 100644 index 000000000..b5bdca20a --- /dev/null +++ b/pkg/tui/layout/container.go @@ -0,0 +1,230 @@ +package layout + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/sst/opencode/internal/tui/theme" +) + +type Container interface { + tea.Model + Sizeable + Bindings + Focus() + Blur() +} + +type container struct { + width int + height int + + content tea.Model + + paddingTop int + paddingRight int + paddingBottom int + paddingLeft int + + borderTop bool + borderRight bool + borderBottom bool + borderLeft bool + borderStyle lipgloss.Border + + focused bool +} + +func (c *container) Init() tea.Cmd { + return c.content.Init() +} + +func (c *container) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + u, cmd := c.content.Update(msg) + c.content = u + return c, cmd +} + +func (c *container) View() string { + t := theme.CurrentTheme() + style := lipgloss.NewStyle() + width := c.width + height := c.height + + style = style.Background(t.Background()) + + // Apply border if any side is enabled + if c.borderTop || c.borderRight || c.borderBottom || c.borderLeft { + // Adjust width and height for borders + if c.borderTop { + height-- + } + if c.borderBottom { + height-- + } + if c.borderLeft { + width-- + } + if c.borderRight { + width-- + } + style = style.Border(c.borderStyle, c.borderTop, c.borderRight, c.borderBottom, c.borderLeft) + + // Use primary color for border if focused + if c.focused { + style = style.BorderBackground(t.Background()).BorderForeground(t.Primary()) + } else { + style = style.BorderBackground(t.Background()).BorderForeground(t.BorderNormal()) + } + } + style = style. + Width(width). + Height(height). + PaddingTop(c.paddingTop). + PaddingRight(c.paddingRight). + PaddingBottom(c.paddingBottom). + PaddingLeft(c.paddingLeft) + + return style.Render(c.content.View()) +} + +func (c *container) SetSize(width, height int) tea.Cmd { + c.width = width + c.height = height + + // If the content implements Sizeable, adjust its size to account for padding and borders + if sizeable, ok := c.content.(Sizeable); ok { + // Calculate horizontal space taken by padding and borders + horizontalSpace := c.paddingLeft + c.paddingRight + if c.borderLeft { + horizontalSpace++ + } + if c.borderRight { + horizontalSpace++ + } + + // Calculate vertical space taken by padding and borders + verticalSpace := c.paddingTop + c.paddingBottom + if c.borderTop { + verticalSpace++ + } + if c.borderBottom { + verticalSpace++ + } + + // Set content size with adjusted dimensions + contentWidth := max(0, width-horizontalSpace) + contentHeight := max(0, height-verticalSpace) + return sizeable.SetSize(contentWidth, contentHeight) + } + return nil +} + +func (c *container) GetSize() (int, int) { + return c.width, c.height +} + +func (c *container) BindingKeys() []key.Binding { + if b, ok := c.content.(Bindings); ok { + return b.BindingKeys() + } + return []key.Binding{} +} + +// Focus sets the container as focused +func (c *container) Focus() { + c.focused = true + // Pass focus to content if it supports it + if focusable, ok := c.content.(interface{ Focus() }); ok { + focusable.Focus() + } +} + +// Blur removes focus from the container +func (c *container) Blur() { + c.focused = false + // Remove focus from content if it supports it + if blurable, ok := c.content.(interface{ Blur() }); ok { + blurable.Blur() + } +} + +type ContainerOption func(*container) + +func NewContainer(content tea.Model, options ...ContainerOption) Container { + c := &container{ + content: content, + borderStyle: lipgloss.NormalBorder(), + } + for _, option := range options { + option(c) + } + return c +} + +// Padding options +func WithPadding(top, right, bottom, left int) ContainerOption { + return func(c *container) { + c.paddingTop = top + c.paddingRight = right + c.paddingBottom = bottom + c.paddingLeft = left + } +} + +func WithPaddingAll(padding int) ContainerOption { + return WithPadding(padding, padding, padding, padding) +} + +func WithPaddingHorizontal(padding int) ContainerOption { + return func(c *container) { + c.paddingLeft = padding + c.paddingRight = padding + } +} + +func WithPaddingVertical(padding int) ContainerOption { + return func(c *container) { + c.paddingTop = padding + c.paddingBottom = padding + } +} + +func WithBorder(top, right, bottom, left bool) ContainerOption { + return func(c *container) { + c.borderTop = top + c.borderRight = right + c.borderBottom = bottom + c.borderLeft = left + } +} + +func WithBorderAll() ContainerOption { + return WithBorder(true, true, true, true) +} + +func WithBorderHorizontal() ContainerOption { + return WithBorder(true, false, true, false) +} + +func WithBorderVertical() ContainerOption { + return WithBorder(false, true, false, true) +} + +func WithBorderStyle(style lipgloss.Border) ContainerOption { + return func(c *container) { + c.borderStyle = style + } +} + +func WithRoundedBorder() ContainerOption { + return WithBorderStyle(lipgloss.RoundedBorder()) +} + +func WithThickBorder() ContainerOption { + return WithBorderStyle(lipgloss.ThickBorder()) +} + +func WithDoubleBorder() ContainerOption { + return WithBorderStyle(lipgloss.DoubleBorder()) +} |
