214 lines
5.0 KiB
Go
214 lines
5.0 KiB
Go
|
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...)
|
||
|
}
|