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) State() viewState { return seedInputView }2122func (s seedInputScreen) Resize(width, height int) screenModel {23 s.width = width24 s.height = height25 s.seed.input.SetWidth(min(width, 48))26 return s27}2829func (s seedInputScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {30 keyMsg, ok := msg.(tea.KeyPressMsg)31 if ok {32 switch {33 case key.Matches(keyMsg, rootKeys.Enter):34 if sessionflow.NormalizeSeed(s.seed.input.Value()) != "" {35 return s, nil, seedConfirmAction{}36 }37 return s, nil, nil38 case key.Matches(keyMsg, rootKeys.Escape):39 return s, nil, backAction{target: playMenuView}40 }41 }4243 m := model{seed: s.seed}44 next, cmd := m.handleSeedInputUpdate(msg)45 s.seed = next.seed46 return s, cmd, nil47}4849func (s seedInputScreen) View(notice noticeState) string {50 m := model{51 width: s.width,52 height: s.height,53 seed: s.seed,54 notice: notice,55 }56 return m.renderPanel(57 "Enter Seed",58 m.seedInputBody(),59 "↑/↓ change field • ←/→ game • enter confirm • esc back",60 )61}6263func (s seedInputScreen) Apply(m model) model {64 m.state = seedInputView65 m.seed = s.seed66 return m67}6869type gameSelectScreen struct {70 width int71 height int72 list list.Model73 detail viewport.Model74}7576func (s gameSelectScreen) State() viewState { return gameSelectView }7778func (s gameSelectScreen) Resize(width, height int) screenModel {79 m := model{80 width: width,81 height: height,82 nav: navigationState{83 gameSelectList: s.list,84 categoryDetail: s.detail,85 },86 }87 metrics := categoryPickerSize(width, height)88 m.nav.gameSelectList.SetSize(metrics.listWidth, metrics.listHeight)89 m = m.updateCategoryDetailViewport()90 s.width = width91 s.height = height92 s.list = m.nav.gameSelectList93 s.detail = m.nav.categoryDetail94 return s95}9697func (s gameSelectScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {98 keyMsg, ok := msg.(tea.KeyPressMsg)99 if ok {100 switch {101 case s.list.SettingFilter() && s.list.FilterValue() == "" && key.Matches(keyMsg, rootKeys.Enter):102 s.list.ResetFilter()103 s = s.resizeSelf()104 return s, nil, nil105 case s.list.SettingFilter() && key.Matches(keyMsg, rootKeys.Enter):106 case s.list.FilterState() != list.Unfiltered && key.Matches(keyMsg, rootKeys.Escape):107 case key.Matches(keyMsg, rootKeys.Enter):108 return s, nil, gameSelectEnterAction{}109 case key.Matches(keyMsg, rootKeys.Escape):110 return s, nil, backAction{target: playMenuView}111 case keyMsg.String() == "pgup":112 s.detail.PageUp()113 return s, nil, nil114 case keyMsg.String() == "pgdown":115 s.detail.PageDown()116 return s, nil, nil117 }118 }119120 prev := selectedCategoryName(s.list.SelectedItem())121 var cmd tea.Cmd122 s.list, cmd = s.list.Update(msg)123 if selectedCategoryName(s.list.SelectedItem()) != prev {124 s = s.resizeSelf()125 }126 return s, cmd, nil127}128129func (s gameSelectScreen) View(notice noticeState) string {130 m := model{131 width: s.width,132 height: s.height,133 notice: notice,134 nav: navigationState{135 gameSelectList: s.list,136 categoryDetail: s.detail,137 },138 }139 return m.gameSelectViewContent()140}141142func (s gameSelectScreen) Apply(m model) model {143 m.state = gameSelectView144 m.nav.gameSelectList = s.list145 m.nav.categoryDetail = s.detail146 return m147}148149func (s gameSelectScreen) resizeSelf() gameSelectScreen {150 return s.Resize(s.width, s.height).(gameSelectScreen)151}152153type modeSelectScreen struct {154 width int155 height int156 entry registry.Entry157 list list.Model158}159160func (s modeSelectScreen) State() viewState { return modeSelectView }161162func (s modeSelectScreen) Resize(width, height int) screenModel {163 s.width = width164 s.height = height165 s.list.SetSize(min(width, 64), min(height, ui.ListHeight(s.list)))166 return s167}168169func (s modeSelectScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {170 keyMsg, ok := msg.(tea.KeyPressMsg)171 if ok {172 switch {173 case key.Matches(keyMsg, rootKeys.Enter):174 return s, nil, modeSelectEnterAction{}175 case key.Matches(keyMsg, rootKeys.Escape):176 return s, nil, backAction{target: gameSelectView}177 }178 }179180 var cmd tea.Cmd181 s.list, cmd = s.list.Update(msg)182 return s, cmd, nil183}184185func (s modeSelectScreen) View(notice noticeState) string {186 m := model{187 width: s.width,188 height: s.height,189 notice: notice,190 nav: navigationState{191 selectedCategory: s.entry,192 modeSelectList: s.list,193 },194 }195 return m.renderPanel(196 m.nav.selectedCategory.Definition.Name+" — Select Mode",197 m.nav.modeSelectList.View(),198 "↑/↓ navigate • enter select • esc back",199 )200}201202func (s modeSelectScreen) Apply(m model) model {203 m.state = modeSelectView204 m.nav.selectedCategory = s.entry205 m.nav.modeSelectList = s.list206 return m207}208209type continueScreen struct {210 width int211 height int212 cont continueState213}214215func (s continueScreen) State() viewState { return continueView }216217func (s continueScreen) Resize(width, height int) screenModel {218 s.width = width219 s.height = height220 s.cont.table.SetWidth(min(width, ui.ContinueTableWidth()))221 visibleRows := min(len(s.cont.games), ui.MaxTableRows)222 s.cont.table.SetHeight(min(height, visibleRows))223 return s224}225226func (s continueScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {227 keyMsg, ok := msg.(tea.KeyPressMsg)228 if ok {229 switch {230 case key.Matches(keyMsg, rootKeys.Enter):231 return s, nil, continueEnterAction{}232 case key.Matches(keyMsg, rootKeys.Escape):233 return s, nil, backAction{target: playMenuView}234 }235 }236237 var cmd tea.Cmd238 s.cont.table, cmd = s.cont.table.Update(msg)239 return s, cmd, nil240}241242func (s continueScreen) View(notice noticeState) string {243 m := model{244 width: s.width,245 height: s.height,246 notice: notice,247 cont: s.cont,248 }249 return m.renderContinueView()250}251252func (s continueScreen) Apply(m model) model {253 m.state = continueView254 m.cont = s.cont255 return m256}257258type weeklyScreen struct {259 width int260 height int261 weekly weeklyState262}263264func (s weeklyScreen) State() viewState { return weeklyView }265266func (s weeklyScreen) Resize(width, height int) screenModel {267 s.width = width268 s.height = height269 s.weekly.table.SetWidth(min(width, ui.WeeklyTableWidth()))270 visibleRows := min(len(s.weekly.rows), ui.MaxTableRows)271 s.weekly.table.SetHeight(min(height, visibleRows))272 return s273}274275func (s weeklyScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {276 keyMsg, ok := msg.(tea.KeyPressMsg)277 if ok {278 switch {279 case key.Matches(keyMsg, rootKeys.Escape):280 return s, nil, backAction{target: playMenuView}281 case key.Matches(keyMsg, rootKeys.Enter):282 return s, nil, weeklyEnterAction{}283 case keyMsg.String() == "left" || keyMsg.String() == "h":284 return s, nil, weeklyShiftAction{delta: -1}285 case keyMsg.String() == "right" || keyMsg.String() == "l":286 return s, nil, weeklyShiftAction{delta: 1}287 }288 }289290 var cmd tea.Cmd291 s.weekly.table, cmd = s.weekly.table.Update(msg)292 return s, cmd, nil293}294295func (s weeklyScreen) View(notice noticeState) string {296 m := model{297 width: s.width,298 height: s.height,299 notice: notice,300 weekly: s.weekly,301 }302 return centerContentWithNotice(s.width, s.height, notice, m.weeklyViewContent())303}304305func (s weeklyScreen) Apply(m model) model {306 m.state = weeklyView307 m.weekly = s.weekly308 return m309}