| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- package layout
- import (
- "github.com/charmbracelet/bubbles/key"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- "github.com/opencode-ai/opencode/internal/tui/theme"
- )
- type Container interface {
- tea.Model
- Sizeable
- Bindings
- }
- type container struct {
- width int
- height int
- content tea.Model
- // Style options
- paddingTop int
- paddingRight int
- paddingBottom int
- paddingLeft int
- borderTop bool
- borderRight bool
- borderBottom bool
- borderLeft bool
- borderStyle lipgloss.Border
- }
- 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)
- 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{}
- }
- 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())
- }
|