Skip to content

Conversation

@ealexandrohin
Copy link

@ealexandrohin ealexandrohin commented Dec 30, 2025

Check #870 for reasoning.

Added horizontal grid layout to the list, you can enable it via list.SetHorizontalEnabled(true). It changes how items are displayed to rows x columns view. It calculates how many columns fit on the screen based on new Width() function in the Delegate. Then based on that calculates how many items are on the page: rows * columns.

Render itself goes for each row and then each column in a loop, writes item to string.Builder then lipgloss.JoinHorizontal with string for the row which contains previous items then clears string.Builder, loop goes again for each column.

Added new keybindings for moving horizontally, CursorLeft and CursorRight respectively.
Also, added new test which checks for right positions of the items.

Test example
package main

import (
	"fmt"
	"io"
	"os"
	"strconv"

	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

type Model struct {
	SelectVertical bool
	SplitVertical  bool
	VerticalList   list.Model
	HorizontalList list.Model
}

type item string

func (i item) FilterValue() string { return string(i) }

type itemDelegate struct{}

func (d itemDelegate) Height() int                               { return 1 }
func (d itemDelegate) Width() int                                { return 8 }
func (d itemDelegate) Spacing() int                              { return 1 }
func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
	i, ok := listItem.(item)
	if !ok {
		return
	}

	var s string
	style := lipgloss.NewStyle().Width(2).AlignHorizontal(lipgloss.Bottom)

	if index == m.Index() {
		s = style.Background(lipgloss.Color("#FFBF00")).Render(string(i))
	} else {
		s = style.Render(string(i))
	}

	fmt.Fprint(w, s)
}

func initialModel() Model {
	items := make([]list.Item, 32)
	for i := range items {
		items[i] = item(strconv.Itoa(i))
	}

	list := list.New(items, itemDelegate{}, 32, 16)
	hList := list
	hList.SetHorizontalEnabled(true)

	return Model{
		SelectVertical: false,
		SplitVertical:  true,
		VerticalList:   list,
		HorizontalList: hList,
	}
}

func (m Model) Init() tea.Cmd {
	return nil
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "s":
			m.SelectVertical = !m.SelectVertical
			return m, nil
		case "v":
			m.SplitVertical = !m.SplitVertical
			return m, nil
		case "ctrl+c", "q":
			return m, tea.Quit
		}
	}

	var cmd tea.Cmd

	if m.SelectVertical {
		m.VerticalList, cmd = m.VerticalList.Update(msg)
	} else {
		m.HorizontalList, cmd = m.HorizontalList.Update(msg)
	}

	return m, cmd
}

func (m Model) View() string {
	if m.SplitVertical {
		return lipgloss.JoinVertical(lipgloss.Left, m.HorizontalList.View(), m.VerticalList.View())
	} else {
		return lipgloss.JoinHorizontal(lipgloss.Left, m.HorizontalList.View(), m.VerticalList.View())
	}
}

func main() {
	p := tea.NewProgram(initialModel())

	if _, err := p.Run(); err != nil {
		fmt.Printf("Alas, there's been an error: %v", err)
		os.Exit(1)
	}
}
image
  • I have read CONTRIBUTING.md.
  • I have created a discussion that was approved by a maintainer (for new features).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant