Files

414 lines
7.5 KiB
Go
Raw Permalink Normal View History

package state
import (
"fmt"
"github.com/StevanFreeborn/term-tier/internal/core"
"github.com/StevanFreeborn/term-tier/internal/styles"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
)
type Mode int
const (
NavigationMode Mode = iota
InputMode
HelpMode
)
type State interface {
Mode() Mode
Tiers() []core.Tier
CursorRow() int
CursorCol() int
Dragging() bool
SetStatus(msg string)
StatusMsg() string
WindowWidth() int
WindowHeight() int
SetWindowSize(width int, height int)
InputTitle() string
Input() textinput.Model
UpdateInput(msg tea.Msg) tea.Cmd
StartInputMode(title string, callback func(string, State) tea.Cmd) tea.Cmd
ExitInputMode()
SubmitInput() tea.Cmd
DeleteItem() tea.Cmd
ClampCursor()
AddItem() tea.Cmd
AddItemToPool(item core.Item)
UpdateItem() tea.Cmd
UpdateCurrentItem(newItem core.Item)
SelectItem()
MoveItem()
MoveRight()
MoveUp()
MoveDown()
MoveLeft()
Save() core.SaveState
Load(data core.SaveState)
ToggleHelp()
}
type state struct {
tiers []core.Tier
cursorRow int
cursorCol int
dragging bool
SourceRow int
SourceCol int
dragBuffer core.Item
windowWidth int
windowHeight int
mode Mode
input textinput.Model
InputCallback func(string, State) tea.Cmd
inputTitle string
statusMsg string
}
type option func(*state)
func New(opts ...option) State {
ti := textinput.New()
ti.CharLimit = 50
ti.Width = 30
state := &state{
tiers: []core.Tier{
{Name: "S Tier", Color: styles.TierColors[0], Items: []core.Item{}},
{Name: "A Tier", Color: styles.TierColors[1], Items: []core.Item{}},
{Name: "B Tier", Color: styles.TierColors[2], Items: []core.Item{}},
{Name: "C Tier", Color: styles.TierColors[3], Items: []core.Item{}},
{Name: "D Tier", Color: styles.TierColors[4], Items: []core.Item{}},
{Name: "Pool", Color: styles.TierColors[5], Items: []core.Item{}},
},
cursorRow: 5,
cursorCol: 0,
mode: NavigationMode,
input: ti,
statusMsg: "Ready.",
}
for _, opt := range opts {
opt(state)
}
return state
}
func WithTiers(tiers []core.Tier) option {
return func(s *state) {
s.tiers = tiers
}
}
func WithCursor(row, col int) option {
return func(s *state) {
s.cursorRow = row
s.cursorCol = col
}
}
func WithMode(mode Mode) option {
return func(s *state) {
s.mode = mode
}
}
func WithInput(input textinput.Model, title string) option {
return func(s *state) {
s.input = input
s.inputTitle = title
}
}
func WithWindowWidth(width int) option {
return func(s *state) {
s.windowWidth = width
}
}
func (s *state) Mode() Mode {
return s.mode
}
func (s *state) InputTitle() string {
return s.inputTitle
}
func (s *state) Input() textinput.Model {
return s.input
}
func (s *state) UpdateInput(msg tea.Msg) tea.Cmd {
var cmd tea.Cmd
s.input, cmd = s.input.Update(msg)
return cmd
}
func (s *state) Tiers() []core.Tier {
return s.tiers
}
func (s *state) CursorRow() int {
return s.cursorRow
}
func (s *state) CursorCol() int {
return s.cursorCol
}
func (s *state) Dragging() bool {
return s.dragging
}
func (s *state) WindowHeight() int {
return s.windowHeight
}
func (s *state) WindowWidth() int {
return s.windowWidth
}
func (s *state) SetWindowSize(width int, height int) {
s.windowHeight = height
s.windowWidth = width
}
func (s *state) StatusMsg() string {
return s.statusMsg
}
func (s *state) SetStatus(msg string) {
s.statusMsg = msg
}
func (s *state) ToggleHelp() {
if s.mode == HelpMode {
s.mode = NavigationMode
} else {
s.mode = HelpMode
}
}
func (s *state) StartInputMode(title string, callback func(string, State) tea.Cmd) tea.Cmd {
s.mode = InputMode
s.inputTitle = title
s.InputCallback = callback
s.input.Reset()
s.input.Focus()
return textinput.Blink
}
func (s *state) ExitInputMode() {
s.mode = NavigationMode
s.input.Reset()
}
func (s *state) SubmitInput() tea.Cmd {
var cmd tea.Cmd
val := s.input.Value()
if s.InputCallback != nil {
cmd = s.InputCallback(val, s)
}
s.mode = NavigationMode
s.input.Reset()
return cmd
}
func (s *state) DeleteItem() tea.Cmd {
tier := &s.tiers[s.cursorRow]
if len(tier.Items) == 0 {
return nil
}
tier.Items = append(tier.Items[:s.cursorCol], tier.Items[s.cursorCol+1:]...)
s.ClampCursor()
s.SetStatus("Item Deleted")
return nil
}
func (s *state) ClampCursor() {
rowLen := len(s.tiers[s.cursorRow].Items)
if s.cursorCol >= rowLen {
if rowLen == 0 {
s.cursorCol = 0
return
}
s.cursorCol = rowLen - 1
}
}
func (s *state) AddItem() tea.Cmd {
return s.StartInputMode("Add new item", func(val string, s State) tea.Cmd {
if val != "" {
s.AddItemToPool(core.Item(val))
s.SetStatus("Added: " + val)
}
return nil
})
}
func (s *state) AddItemToPool(item core.Item) {
poolIndex := len(s.tiers) - 1
s.tiers[poolIndex].Items = append(s.tiers[poolIndex].Items, item)
}
func (s *state) UpdateItem() tea.Cmd {
if len(s.tiers[s.cursorRow].Items) == 0 {
return nil
}
currentItem := string(s.tiers[s.cursorRow].Items[s.cursorCol])
cmd := s.StartInputMode("Rename Item", func(val string, m State) tea.Cmd {
if val != "" {
m.UpdateCurrentItem(core.Item(val))
m.SetStatus("Renamed to: " + val)
}
return nil
})
s.input.SetValue(currentItem)
s.input.CursorEnd()
return cmd
}
func (s *state) UpdateCurrentItem(newItem core.Item) {
s.tiers[s.cursorRow].Items[s.cursorCol] = newItem
}
func (s *state) SelectItem() {
if s.dragging {
s.dragging = false
} else {
if len(s.tiers[s.cursorRow].Items) > 0 {
s.dragging = true
s.SourceRow = s.cursorRow
s.SourceCol = s.cursorCol
s.dragBuffer = s.tiers[s.cursorRow].Items[s.cursorCol]
}
}
}
func (s *state) MoveItem() {
sourceRow := s.SourceRow
sourceCol := s.SourceCol
item := s.dragBuffer
if sourceRow >= len(s.tiers) || sourceCol >= len(s.tiers[sourceRow].Items) {
return
}
oldRow := s.tiers[sourceRow].Items
s.tiers[sourceRow].Items = append(oldRow[:sourceCol], oldRow[sourceCol+1:]...)
targetRow := s.tiers[s.cursorRow].Items
if s.cursorCol > len(targetRow) {
s.cursorCol = len(targetRow)
}
s.SourceRow = s.cursorRow
s.SourceCol = s.cursorCol
newRow := make([]core.Item, len(targetRow)+1)
copy(newRow, targetRow[:s.cursorCol])
newRow[s.cursorCol] = item
copy(newRow[s.cursorCol+1:], targetRow[s.cursorCol:])
s.tiers[s.cursorRow].Items = newRow
s.SetStatus(fmt.Sprintf("Moved '%s' from %s to %s", item, s.tiers[sourceRow].Name, s.tiers[s.cursorRow].Name))
}
func (s *state) MoveRight() {
limit := len(s.tiers[s.cursorRow].Items) - 1
if s.dragging {
limit = len(s.tiers[s.cursorRow].Items)
}
if s.cursorCol < limit {
s.cursorCol++
} else if !s.dragging && len(s.tiers[s.cursorRow].Items) == 0 {
s.cursorCol = 0
}
if s.dragging {
s.MoveItem()
}
}
func (s *state) MoveUp() {
if s.cursorRow > 0 {
s.cursorRow--
s.ClampCursor()
if s.dragging {
s.MoveItem()
}
}
}
func (s *state) MoveDown() {
if s.cursorRow < len(s.tiers)-1 {
s.cursorRow++
s.ClampCursor()
if s.dragging {
s.MoveItem()
}
}
}
func (s *state) MoveLeft() {
if s.cursorCol > 0 {
s.cursorCol--
if s.dragging {
s.MoveItem()
}
}
}
func (s *state) Save() core.SaveState {
state := core.SaveState{}
for _, t := range s.tiers {
state.Tiers = append(state.Tiers, struct {
Name string `json:"name"`
Items []core.Item `json:"items"`
}{Name: t.Name, Items: t.Items})
}
return state
}
func (s *state) Load(data core.SaveState) {
if len(data.Tiers) == len(s.tiers) {
for i, savedTier := range data.Tiers {
s.tiers[i].Items = savedTier.Items
}
s.cursorRow = 0
s.cursorCol = 0
s.dragging = false
}
}