clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

bug/architecture-test-trim-path
puzzletea / app / screen_navigation.go
screen_navigation.go 6.4K
1package app23import (4 "github.com/FelineStateMachine/puzzletea/registry"5 sessionflow "github.com/FelineStateMachine/puzzletea/session"6 "github.com/FelineStateMachine/puzzletea/ui"78 "charm.land/bubbles/v2/key"9 "charm.land/bubbles/v2/list"10 "charm.land/bubbles/v2/viewport"11 tea "charm.land/bubbletea/v2"12)1314type seedInputScreen struct {15 width int16 height int17 seed seedState18}1920func (s seedInputScreen) Resize(width, height int) screenModel {21 s.width = width22 s.height = height23 s.seed.input.SetWidth(min(width, 48))24 return s25}2627func (s seedInputScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {28 keyMsg, ok := msg.(tea.KeyPressMsg)29 if ok {30 switch {31 case key.Matches(keyMsg, rootKeys.Enter):32 if sessionflow.NormalizeSeed(s.seed.input.Value()) != "" {33 return s, nil, seedConfirmAction{seed: s.seed}34 }35 return s, nil, nil36 case key.Matches(keyMsg, rootKeys.Escape):37 return s, nil, backAction{target: playMenuView}38 }39 }4041 m := model{seed: s.seed}42 next, cmd := m.handleSeedInputUpdate(msg)43 s.seed = next.seed44 return s, cmd, nil45}4647func (s seedInputScreen) View(notice noticeState) string {48 return renderPanelView(s.width, s.height, notice,49 "Enter Seed",50 seedInputBody(s.seed),51 "↑/↓ change field • ←/→ game • enter confirm • esc back",52 )53}5455type gameSelectScreen struct {56 width int57 height int58 list list.Model59 detail viewport.Model60}6162func (s gameSelectScreen) Resize(width, height int) screenModel {63 m := model{64 width: width,65 height: height,66 nav: navigationState{67 gameSelectList: s.list,68 categoryDetail: s.detail,69 },70 }71 metrics := categoryPickerSize(width, height)72 m.nav.gameSelectList.SetSize(metrics.listWidth, metrics.listHeight)73 m = m.updateCategoryDetailViewport()74 s.width = width75 s.height = height76 s.list = m.nav.gameSelectList77 s.detail = m.nav.categoryDetail78 return s79}8081func (s gameSelectScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {82 keyMsg, ok := msg.(tea.KeyPressMsg)83 if ok {84 switch {85 case s.list.SettingFilter() && s.list.FilterValue() == "" && key.Matches(keyMsg, rootKeys.Enter):86 s.list.ResetFilter()87 s = s.resizeSelf()88 return s, nil, nil89 case s.list.SettingFilter() && key.Matches(keyMsg, rootKeys.Enter):90 case s.list.FilterState() != list.Unfiltered && key.Matches(keyMsg, rootKeys.Escape):91 case key.Matches(keyMsg, rootKeys.Enter):92 entry, ok := selectedCategoryEntry(s.list.SelectedItem())93 if !ok {94 return s, nil, nil95 }96 return s, nil, gameSelectEnterAction{entry: entry}97 case key.Matches(keyMsg, rootKeys.Escape):98 return s, nil, backAction{target: playMenuView}99 case keyMsg.String() == "pgup":100 s.detail.PageUp()101 return s, nil, nil102 case keyMsg.String() == "pgdown":103 s.detail.PageDown()104 return s, nil, nil105 }106 }107108 prev := selectedCategoryName(s.list.SelectedItem())109 var cmd tea.Cmd110 s.list, cmd = s.list.Update(msg)111 if selectedCategoryName(s.list.SelectedItem()) != prev {112 s = s.resizeSelf()113 }114 return s, cmd, nil115}116117func (s gameSelectScreen) View(notice noticeState) string {118 return gameSelectViewContent(s.width, s.height, s.list, s.detail, notice)119}120121func (s gameSelectScreen) resizeSelf() gameSelectScreen {122 return s.Resize(s.width, s.height).(gameSelectScreen)123}124125type modeSelectScreen struct {126 width int127 height int128 entry registry.Entry129 list list.Model130}131132func (s modeSelectScreen) Resize(width, height int) screenModel {133 s.width = width134 s.height = height135 s.list.SetSize(min(width, 64), min(height, ui.ListHeight(s.list)))136 return s137}138139func (s modeSelectScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {140 keyMsg, ok := msg.(tea.KeyPressMsg)141 if ok {142 switch {143 case key.Matches(keyMsg, rootKeys.Enter):144 item := unwrapModeDisplayItem(s.list.SelectedItem())145 mode, ok := item.(registry.VariantEntry)146 if !ok {147 return s, nil, nil148 }149 return s, nil, modeSelectEnterAction{entry: s.entry, mode: mode}150 case key.Matches(keyMsg, rootKeys.Escape):151 return s, nil, backAction{target: gameSelectView}152 }153 }154155 var cmd tea.Cmd156 s.list, cmd = s.list.Update(msg)157 return s, cmd, nil158}159160func (s modeSelectScreen) View(notice noticeState) string {161 return renderPanelView(s.width, s.height, notice,162 s.entry.Definition.Name+" - Select Variant",163 s.list.View(),164 "↑/↓ navigate • enter select • esc back",165 )166}167168type continueScreen struct {169 width int170 height int171 cont continueState172}173174func (s continueScreen) Resize(width, height int) screenModel {175 s.width = width176 s.height = height177 s.cont.table.SetWidth(min(width, ui.ContinueTableWidth()))178 visibleRows := min(len(s.cont.games), ui.MaxTableRows)179 s.cont.table.SetHeight(min(height, visibleRows))180 return s181}182183func (s continueScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {184 keyMsg, ok := msg.(tea.KeyPressMsg)185 if ok {186 switch {187 case key.Matches(keyMsg, rootKeys.Enter):188 idx := s.cont.table.Cursor()189 if idx >= 0 && idx < len(s.cont.games) {190 return s, nil, continueEnterAction{record: s.cont.games[idx]}191 }192 return s, nil, nil193 case key.Matches(keyMsg, rootKeys.Escape):194 return s, nil, backAction{target: playMenuView}195 }196 }197198 var cmd tea.Cmd199 s.cont.table, cmd = s.cont.table.Update(msg)200 return s, cmd, nil201}202203func (s continueScreen) View(notice noticeState) string {204 return renderContinueView(s.cont, s.width, s.height, notice)205}206207type weeklyScreen struct {208 width int209 height int210 weekly weeklyState211}212213func (s weeklyScreen) Resize(width, height int) screenModel {214 s.width = width215 s.height = height216 s.weekly.table.SetWidth(min(width, ui.WeeklyTableWidth()))217 visibleRows := min(len(s.weekly.rows), ui.MaxTableRows)218 s.weekly.table.SetHeight(min(height, visibleRows))219 return s220}221222func (s weeklyScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {223 keyMsg, ok := msg.(tea.KeyPressMsg)224 if ok {225 switch {226 case key.Matches(keyMsg, rootKeys.Escape):227 return s, nil, backAction{target: playMenuView}228 case key.Matches(keyMsg, rootKeys.Enter):229 return s, nil, weeklyEnterAction{weekly: s.weekly}230 case keyMsg.String() == "left" || keyMsg.String() == "h":231 return s, nil, weeklyShiftAction{delta: -1, weekly: s.weekly}232 case keyMsg.String() == "right" || keyMsg.String() == "l":233 return s, nil, weeklyShiftAction{delta: 1, weekly: s.weekly}234 }235 }236237 var cmd tea.Cmd238 s.weekly.table, cmd = s.weekly.table.Update(msg)239 return s, cmd, nil240}241242func (s weeklyScreen) View(notice noticeState) string {243 return centerContentWithNotice(s.width, s.height, notice, weeklyViewContent(s.weekly))244}