summaryrefslogtreecommitdiffhomepage
path: root/internal/tui/layout/grid.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/tui/layout/grid.go')
-rw-r--r--internal/tui/layout/grid.go254
1 files changed, 0 insertions, 254 deletions
diff --git a/internal/tui/layout/grid.go b/internal/tui/layout/grid.go
deleted file mode 100644
index 6be493e2c..000000000
--- a/internal/tui/layout/grid.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package layout
-
-import (
- "github.com/charmbracelet/bubbles/key"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
-)
-
-type GridLayout interface {
- tea.Model
- Sizeable
- Bindings
- Panes() [][]tea.Model
-}
-
-type gridLayout struct {
- width int
- height int
-
- rows int
- columns int
-
- panes [][]tea.Model
-
- gap int
- bordered bool
- focusable bool
-
- currentRow int
- currentColumn int
-
- activeColor lipgloss.TerminalColor
-}
-
-type GridOption func(*gridLayout)
-
-func (g *gridLayout) Init() tea.Cmd {
- var cmds []tea.Cmd
- for i := range g.panes {
- for j := range g.panes[i] {
- if g.panes[i][j] != nil {
- cmds = append(cmds, g.panes[i][j].Init())
- }
- }
- }
- return tea.Batch(cmds...)
-}
-
-func (g *gridLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- var cmds []tea.Cmd
-
- switch msg := msg.(type) {
- case tea.WindowSizeMsg:
- g.SetSize(msg.Width, msg.Height)
- return g, nil
- case tea.KeyMsg:
- if key.Matches(msg, g.nextPaneBinding()) {
- return g.focusNextPane()
- }
- }
-
- // Update all panes
- for i := range g.panes {
- for j := range g.panes[i] {
- if g.panes[i][j] != nil {
- var cmd tea.Cmd
- g.panes[i][j], cmd = g.panes[i][j].Update(msg)
- if cmd != nil {
- cmds = append(cmds, cmd)
- }
- }
- }
- }
-
- return g, tea.Batch(cmds...)
-}
-
-func (g *gridLayout) focusNextPane() (tea.Model, tea.Cmd) {
- if !g.focusable {
- return g, nil
- }
-
- var cmds []tea.Cmd
-
- // Blur current pane
- if g.currentRow < len(g.panes) && g.currentColumn < len(g.panes[g.currentRow]) {
- if currentPane, ok := g.panes[g.currentRow][g.currentColumn].(Focusable); ok {
- cmds = append(cmds, currentPane.Blur())
- }
- }
-
- // Find next valid pane
- g.currentColumn++
- if g.currentColumn >= len(g.panes[g.currentRow]) {
- g.currentColumn = 0
- g.currentRow++
- if g.currentRow >= len(g.panes) {
- g.currentRow = 0
- }
- }
-
- // Focus next pane
- if g.currentRow < len(g.panes) && g.currentColumn < len(g.panes[g.currentRow]) {
- if nextPane, ok := g.panes[g.currentRow][g.currentColumn].(Focusable); ok {
- cmds = append(cmds, nextPane.Focus())
- }
- }
-
- return g, tea.Batch(cmds...)
-}
-
-func (g *gridLayout) nextPaneBinding() key.Binding {
- return key.NewBinding(
- key.WithKeys("tab"),
- key.WithHelp("tab", "next pane"),
- )
-}
-
-func (g *gridLayout) View() string {
- if len(g.panes) == 0 {
- return ""
- }
-
- // Calculate dimensions for each cell
- cellWidth := (g.width - (g.columns-1)*g.gap) / g.columns
- cellHeight := (g.height - (g.rows-1)*g.gap) / g.rows
-
- // Render each row
- rows := make([]string, g.rows)
- for i := range g.rows {
- // Render each column in this row
- cols := make([]string, len(g.panes[i]))
- for j := range g.panes[i] {
- if g.panes[i][j] == nil {
- cols[j] = ""
- continue
- }
-
- // Set size for each pane
- if sizable, ok := g.panes[i][j].(Sizeable); ok {
- effectiveWidth, effectiveHeight := cellWidth, cellHeight
- if g.bordered {
- effectiveWidth -= 2
- effectiveHeight -= 2
- }
- sizable.SetSize(effectiveWidth, effectiveHeight)
- }
-
- // Render the pane
- content := g.panes[i][j].View()
-
- // Apply border if needed
- if g.bordered {
- isFocused := false
- if focusable, ok := g.panes[i][j].(Focusable); ok {
- isFocused = focusable.IsFocused()
- }
-
- borderText := map[BorderPosition]string{}
- if bordered, ok := g.panes[i][j].(Bordered); ok {
- borderText = bordered.BorderText()
- }
-
- content = Borderize(content, BorderOptions{
- Active: isFocused,
- EmbeddedText: borderText,
- })
- }
-
- cols[j] = content
- }
-
- // Join columns with gap
- rows[i] = lipgloss.JoinHorizontal(lipgloss.Top, cols...)
- }
-
- // Join rows with gap
- return lipgloss.JoinVertical(lipgloss.Left, rows...)
-}
-
-func (g *gridLayout) SetSize(width, height int) {
- g.width = width
- g.height = height
-}
-
-func (g *gridLayout) GetSize() (int, int) {
- return g.width, g.height
-}
-
-func (g *gridLayout) BindingKeys() []key.Binding {
- var bindings []key.Binding
- bindings = append(bindings, g.nextPaneBinding())
-
- // Collect bindings from all panes
- for i := range g.panes {
- for j := range g.panes[i] {
- if g.panes[i][j] != nil {
- if bindable, ok := g.panes[i][j].(Bindings); ok {
- bindings = append(bindings, bindable.BindingKeys()...)
- }
- }
- }
- }
-
- return bindings
-}
-
-func (g *gridLayout) Panes() [][]tea.Model {
- return g.panes
-}
-
-// NewGridLayout creates a new grid layout with the given number of rows and columns
-func NewGridLayout(rows, cols int, panes [][]tea.Model, opts ...GridOption) GridLayout {
- grid := &gridLayout{
- rows: rows,
- columns: cols,
- panes: panes,
- gap: 1,
- }
-
- for _, opt := range opts {
- opt(grid)
- }
-
- return grid
-}
-
-// WithGridGap sets the gap between cells
-func WithGridGap(gap int) GridOption {
- return func(g *gridLayout) {
- g.gap = gap
- }
-}
-
-// WithGridBordered sets whether cells should have borders
-func WithGridBordered(bordered bool) GridOption {
- return func(g *gridLayout) {
- g.bordered = bordered
- }
-}
-
-// WithGridFocusable sets whether the grid supports focus navigation
-func WithGridFocusable(focusable bool) GridOption {
- return func(g *gridLayout) {
- g.focusable = focusable
- }
-}
-
-// WithGridActiveColor sets the active border color
-func WithGridActiveColor(color lipgloss.TerminalColor) GridOption {
- return func(g *gridLayout) {
- g.activeColor = color
- }
-}