176 lines
3.9 KiB
Go
176 lines
3.9 KiB
Go
package model
|
||||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
|
|||
|
|
"github.com/StevanFreeborn/term-tier/internal/handlers"
|
|||
|
|
"github.com/StevanFreeborn/term-tier/internal/state"
|
|||
|
|
"github.com/StevanFreeborn/term-tier/internal/styles"
|
|||
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|||
|
|
tea "github.com/charmbracelet/bubbletea"
|
|||
|
|
"github.com/charmbracelet/lipgloss"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type Mode int
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
ModeNav Mode = iota
|
|||
|
|
ModeInput
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type Model struct {
|
|||
|
|
State state.State
|
|||
|
|
KeyMsgHandlers []handlers.KeyMsgHandler
|
|||
|
|
WinSizeMsgHandlers []handlers.WinSizeMsgHandler
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type option func(m *Model)
|
|||
|
|
|
|||
|
|
func New(state state.State, opts ...option) Model {
|
|||
|
|
ti := textinput.New()
|
|||
|
|
ti.CharLimit = 50
|
|||
|
|
ti.Width = 30
|
|||
|
|
|
|||
|
|
m := Model{
|
|||
|
|
State: state,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, opt := range opts {
|
|||
|
|
opt(&m)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return m
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func WithKeyMsgHandlers(handlers []handlers.KeyMsgHandler) option {
|
|||
|
|
return func(m *Model) {
|
|||
|
|
m.KeyMsgHandlers = handlers
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func WithWinSizeMsgHandlers(handlers []handlers.WinSizeMsgHandler) option {
|
|||
|
|
return func(m *Model) {
|
|||
|
|
m.WinSizeMsgHandlers = handlers
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m Model) Init() tea.Cmd {
|
|||
|
|
return textinput.Blink
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
|
|
switch msg := msg.(type) {
|
|||
|
|
case tea.WindowSizeMsg:
|
|||
|
|
for _, handler := range m.WinSizeMsgHandlers {
|
|||
|
|
if handler.Match(m.State, msg) {
|
|||
|
|
return m, handler.Handle(m.State, msg)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
case tea.KeyMsg:
|
|||
|
|
for _, handler := range m.KeyMsgHandlers {
|
|||
|
|
if handler.Match(m.State, msg) {
|
|||
|
|
return m, handler.Handle(m.State, msg)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return m, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m Model) View() string {
|
|||
|
|
if m.State.Mode() == state.InputMode {
|
|||
|
|
return fmt.Sprintf(
|
|||
|
|
"\n %s\n\n%s\n\n (Enter to Confirm, Esc to Cancel)",
|
|||
|
|
m.State.InputTitle(),
|
|||
|
|
styles.InputBoxStyle.Render(m.State.Input().View()),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if m.State.Mode() == state.HelpMode {
|
|||
|
|
helpText := " HELP MENU\n\n"
|
|||
|
|
|
|||
|
|
for _, handler := range m.KeyMsgHandlers {
|
|||
|
|
if desc := handler.Description(); desc != "" {
|
|||
|
|
keys := handler.KeysHandled()
|
|||
|
|
helpText += fmt.Sprintf(" %s: %s\n", keys, desc)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
helpText += "\n (Press ? to close this menu)"
|
|||
|
|
|
|||
|
|
box := styles.HelpBoxStyle.Render(helpText)
|
|||
|
|
|
|||
|
|
return lipgloss.Place(
|
|||
|
|
m.State.WindowWidth(),
|
|||
|
|
m.State.WindowHeight(),
|
|||
|
|
lipgloss.Center,
|
|||
|
|
lipgloss.Center,
|
|||
|
|
box,
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s := "[?] Help ️ [q] Quit\n\n"
|
|||
|
|
|
|||
|
|
availableWidth := max(m.State.WindowWidth()-15, 20)
|
|||
|
|
|
|||
|
|
for tierIndex, tier := range m.State.Tiers() {
|
|||
|
|
var rows []string
|
|||
|
|
var currentRowItems []string
|
|||
|
|
currentWidth := 0
|
|||
|
|
|
|||
|
|
for itemIndex, item := range tier.Items {
|
|||
|
|
renderStyle := styles.ItemStyle
|
|||
|
|
isFocused := tierIndex == m.State.CursorRow() && itemIndex == m.State.CursorCol()
|
|||
|
|
|
|||
|
|
if isFocused {
|
|||
|
|
if m.State.Dragging() {
|
|||
|
|
renderStyle = styles.DraggingItemStyle
|
|||
|
|
} else {
|
|||
|
|
renderStyle = styles.SelectedItemStyle
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
renderedItem := renderStyle.Render(string(item))
|
|||
|
|
itemWidth := lipgloss.Width(renderedItem)
|
|||
|
|
|
|||
|
|
if currentWidth+itemWidth > availableWidth && len(currentRowItems) > 0 {
|
|||
|
|
rows = append(rows, lipgloss.JoinHorizontal(lipgloss.Top, currentRowItems...))
|
|||
|
|
currentRowItems = []string{renderedItem}
|
|||
|
|
currentWidth = itemWidth
|
|||
|
|
} else {
|
|||
|
|
currentRowItems = append(currentRowItems, renderedItem)
|
|||
|
|
currentWidth += itemWidth
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(tier.Items) == 0 && tierIndex == m.State.CursorRow() {
|
|||
|
|
ghost := lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render(" [ ]")
|
|||
|
|
currentRowItems = append(currentRowItems, ghost)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(currentRowItems) > 0 {
|
|||
|
|
rows = append(rows, lipgloss.JoinHorizontal(lipgloss.Top, currentRowItems...))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
itemBlock := lipgloss.JoinVertical(lipgloss.Left, rows...)
|
|||
|
|
|
|||
|
|
blockHeight := lipgloss.Height(itemBlock)
|
|||
|
|
|
|||
|
|
label := styles.TierLabelStyle.
|
|||
|
|
Background(tier.Color).
|
|||
|
|
Height(blockHeight).
|
|||
|
|
AlignVertical(lipgloss.Center).
|
|||
|
|
Render(tier.Name)
|
|||
|
|
|
|||
|
|
row := lipgloss.JoinHorizontal(lipgloss.Center, label, itemBlock)
|
|||
|
|
|
|||
|
|
s += row + "\n\n"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if m.State.StatusMsg() != "" {
|
|||
|
|
s += styles.StatusStyle.Render(" STATUS: " + m.State.StatusMsg())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return s
|
|||
|
|
}
|