package tui import ( // "git.iamthefij.com/iamthefij/slog" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) type MultiViewModel struct { // Internal state columns []tea.Model activeIndex int mainModel tea.Model lastWindowSizeMsg tea.WindowSizeMsg // Styles for system BaseStyle lipgloss.Style ActiveStyle lipgloss.Style NumVisibleSections int SideColumnWidth int // Select functionality SelectKeys []string OnColumnSelect func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd BackKeys []string OnColumnBack func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd QuitKeys []string } func NewMultiViewModel(columns []tea.Model, main tea.Model) *MultiViewModel { baseStyle := lipgloss.NewStyle().Margin(1) return &MultiViewModel{ columns: columns, activeIndex: 0, mainModel: main, BaseStyle: baseStyle, ActiveStyle: baseStyle.Copy().UnsetMargins().BorderStyle(lipgloss.NormalBorder()).BorderForeground( lipgloss.Color("21"), ), NumVisibleSections: 2, SideColumnWidth: 40, SelectKeys: []string{tea.KeyEnter.String(), tea.KeyTab.String()}, OnColumnSelect: func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd { baseModel.NextView() return nil }, BackKeys: []string{tea.KeyEsc.String(), tea.KeyShiftTab.String()}, OnColumnBack: func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd { baseModel.PreviousView() return nil }, QuitKeys: []string{"q"}, } } func (m MultiViewModel) GetActiveIndex() int { return m.activeIndex } func (m MultiViewModel) GetActiveView() *tea.Model { if m.activeIndex < len(m.columns) { return &m.columns[m.activeIndex] } return &m.mainModel } func (m *MultiViewModel) SetActiveView(viewModel tea.Model) { if m.activeIndex < len(m.columns) { m.columns[m.activeIndex] = viewModel } else { m.mainModel = viewModel } } func (m *MultiViewModel) NextView() *tea.Model { if m.activeIndex < len(m.columns) { m.activeIndex++ } return m.GetActiveView() } func (m *MultiViewModel) PreviousView() *tea.Model { if m.activeIndex > 0 { m.activeIndex-- } return m.GetActiveView() } func (m *MultiViewModel) SetMainView(mainView tea.Model) { top, right, bottom, left := m.BaseStyle.GetMargin() m.mainModel, _ = mainView.Update(tea.WindowSizeMsg{ Height: m.lastWindowSizeMsg.Height - top - bottom, Width: m.MainSectionWidth() - right - left, }) } func (m *MultiViewModel) ClearMainView() { m.mainModel = nil } func (m MultiViewModel) Init() tea.Cmd { return nil } func (m MultiViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: // Pass key strokes to active view var updatedView tea.Model updatedView, cmd = (*m.GetActiveView()).Update(msg) switch { case m.activeIndex < 0: panic("where are we!?") case m.activeIndex < len(m.columns): m.columns[m.activeIndex] = updatedView case m.activeIndex >= len(m.columns): m.mainModel = updatedView } // Handle global quit for _, quitKey := range m.QuitKeys { if quitKey == msg.String() { return m, tea.Quit } } // Handle select key for _, selectKey := range m.SelectKeys { if selectKey == msg.String() { m.OnColumnSelect(&m, msg) break } } // Handle back key for _, selectKey := range m.BackKeys { if selectKey == msg.String() { m.OnColumnBack(&m, msg) break } } case tea.WindowSizeMsg: // Pass size message to all sub views top, right, bottom, left := m.BaseStyle.GetMargin() // top, right, bottom, left := m.BaseStyle.GetBorderStyle().GetTopSize for i, model := range m.columns { m.columns[i], _ = model.Update(tea.WindowSizeMsg{ Height: msg.Height - top - bottom, Width: m.SideColumnWidth - right - left, }) } if m.mainModel != nil { m.mainModel, _ = m.mainModel.Update(tea.WindowSizeMsg{ Height: msg.Height - top - bottom, Width: m.MainSectionWidth() - right - left, }) } m.lastWindowSizeMsg = msg } return m, cmd } func (m MultiViewModel) MainSectionWidth() int { return m.lastWindowSizeMsg.Width - (m.SideColumnWidth * m.NumVisibleSections) } func (m MultiViewModel) View() string { allModels := []tea.Model{} allModels = append(allModels, m.columns...) if m.mainModel != nil { allModels = append(allModels, m.mainModel) } visibleContent := []string{} // Add visible columns sideColStyle := m.BaseStyle.Copy().Width(m.SideColumnWidth) mainStyle := m.BaseStyle.Copy().Width(m.MainSectionWidth()) for i, column := range allModels { // Get the correct style style := sideColStyle if i >= len(m.columns) { style = mainStyle } isVisible := m.NumVisibleSections < 1 || i < m.activeIndex+m.NumVisibleSections if isVisible { if i == m.activeIndex { visibleContent = append(visibleContent, m.ActiveStyle.Inherit(style).Render(column.View())) } else { visibleContent = append(visibleContent, style.Render(column.View())) } } } return lipgloss.JoinHorizontal(lipgloss.Top, visibleContent...) }