summaryrefslogtreecommitdiffhomepage
path: root/pkg/tui/layout/container.go
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-18 22:30:41 -0400
committerDax Raad <[email protected]>2025-05-26 12:40:17 -0400
commit99af6146d5def31c59993636d60eb75a483a283b (patch)
treeeb57ed227c15cf9be54bc9f231c83895d812f881 /pkg/tui/layout/container.go
parent020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff)
downloadopencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz
opencode-99af6146d5def31c59993636d60eb75a483a283b.zip
openapi
Diffstat (limited to 'pkg/tui/layout/container.go')
-rw-r--r--pkg/tui/layout/container.go230
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())
+}