1package app23import (4 "github.com/FelineStateMachine/puzzletea/theme"5 "github.com/FelineStateMachine/puzzletea/ui"67 "charm.land/bubbles/v2/key"8 "charm.land/bubbles/v2/list"9 "charm.land/bubbles/v2/spinner"10 tea "charm.land/bubbletea/v2"11)1213type helpSelectScreen struct {14 width int15 height int16 help helpState17}1819func (s helpSelectScreen) State() viewState { return helpSelectView }2021func (s helpSelectScreen) Resize(width, height int) screenModel {22 s.width = width23 s.height = height24 listWidth, listHeight := helpSelectListSize(width, height, s.help.selectList)25 s.help.selectList.SetSize(listWidth, listHeight)26 return s27}2829func (s helpSelectScreen) 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 return s, nil, helpSelectEnterAction{}35 case key.Matches(keyMsg, rootKeys.Escape):36 return s, nil, backAction{target: optionsMenuView}37 }38 }3940 var cmd tea.Cmd41 s.help.selectList, cmd = s.help.selectList.Update(msg)42 return s, cmd, nil43}4445func (s helpSelectScreen) View(notice noticeState) string {46 m := model{47 width: s.width,48 height: s.height,49 notice: notice,50 help: s.help,51 }52 return m.renderPanel(53 "How to Play",54 m.help.selectList.View(),55 "↑/↓ navigate • enter select • esc back",56 )57}5859func (s helpSelectScreen) Apply(m model) model {60 m.state = helpSelectView61 m.help.selectList = s.help.selectList62 return m63}6465type helpDetailScreen struct {66 width int67 height int68 help helpState69}7071func (s helpDetailScreen) State() viewState { return helpDetailView }7273func (s helpDetailScreen) Resize(width, height int) screenModel {74 m := model{75 width: width,76 height: height,77 help: s.help,78 }79 m = m.updateHelpDetailViewport()80 s.width = width81 s.height = height82 s.help = m.help83 return s84}8586func (s helpDetailScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {87 keyMsg, ok := msg.(tea.KeyPressMsg)88 if ok && key.Matches(keyMsg, rootKeys.Escape) {89 return s, nil, backAction{target: helpSelectView}90 }9192 var cmd tea.Cmd93 s.help.viewport, cmd = s.help.viewport.Update(msg)94 return s, cmd, nil95}9697func (s helpDetailScreen) View(notice noticeState) string {98 m := model{99 width: s.width,100 height: s.height,101 notice: notice,102 help: s.help,103 }104 return m.renderPanel(105 m.help.category.Definition.Name+" — Guide",106 m.help.viewport.View(),107 "↑/↓ scroll • esc back",108 )109}110111func (s helpDetailScreen) Apply(m model) model {112 m.state = helpDetailView113 m.help = s.help114 return m115}116117type statsScreen struct {118 width int119 height int120 stats statsState121}122123func (s statsScreen) State() viewState { return statsView }124125func (s statsScreen) Resize(width, height int) screenModel {126 m := model{127 width: width,128 height: height,129 stats: s.stats,130 }131 m = m.updateStatsViewport()132 s.width = width133 s.height = height134 s.stats = m.stats135 return s136}137138func (s statsScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {139 keyMsg, ok := msg.(tea.KeyPressMsg)140 if ok && key.Matches(keyMsg, rootKeys.Escape) {141 return s, nil, backAction{target: mainMenuView}142 }143144 var cmd tea.Cmd145 s.stats.viewport, cmd = s.stats.viewport.Update(msg)146 return s, cmd, nil147}148149func (s statsScreen) View(notice noticeState) string {150 m := model{151 width: s.width,152 height: s.height,153 notice: notice,154 stats: s.stats,155 }156 return m.renderStatsView()157}158159func (s statsScreen) Apply(m model) model {160 m.state = statsView161 m.stats = s.stats162 return m163}164165type themeSelectScreen struct {166 width int167 height int168 theme themeState169}170171func (s themeSelectScreen) State() viewState { return themeSelectView }172173func (s themeSelectScreen) Resize(width, height int) screenModel {174 const maxVisibleItems = 8175 menuW := min(width, 64)176 listW := min(menuW, theme.MaxNameLen+4)177 s.width = width178 s.height = height179 s.theme.list.SetSize(listW, min(height, maxVisibleItems*3))180 return s181}182183func (s themeSelectScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {184 keyMsg, ok := msg.(tea.KeyPressMsg)185 if ok {186 switch {187 case s.theme.list.SettingFilter() && s.theme.list.FilterValue() == "" && key.Matches(keyMsg, rootKeys.Enter):188 s.theme.list.ResetFilter()189 return s, nil, nil190 case s.theme.list.SettingFilter() && key.Matches(keyMsg, rootKeys.Enter):191 case s.theme.list.FilterState() != list.Unfiltered && key.Matches(keyMsg, rootKeys.Escape):192 case key.Matches(keyMsg, rootKeys.Enter):193 return s, nil, confirmThemeAction{}194 case key.Matches(keyMsg, rootKeys.Escape):195 return s, nil, backAction{target: optionsMenuView}196 }197 }198199 prev := s.theme.list.Index()200 var cmd tea.Cmd201 s.theme.list, cmd = s.theme.list.Update(msg)202 if s.theme.list.Index() != prev {203 if item, ok := s.theme.list.SelectedItem().(ui.MenuItem); ok {204 return s, cmd, previewThemeAction{name: item.ItemTitle}205 }206 }207 return s, cmd, nil208}209210func (s themeSelectScreen) View(notice noticeState) string {211 m := model{212 width: s.width,213 height: s.height,214 notice: notice,215 theme: s.theme,216 }217 return m.themeSelectViewContent()218}219220func (s themeSelectScreen) Apply(m model) model {221 m.state = themeSelectView222 m.theme = s.theme223 return m224}225226type generatingScreen struct {227 width int228 height int229 spinner spinner.Model230}231232func (s generatingScreen) State() viewState { return generatingView }233234func (s generatingScreen) Resize(width, height int) screenModel {235 s.width = width236 s.height = height237 return s238}239240func (s generatingScreen) Update(msg tea.Msg) (screenModel, tea.Cmd, screenAction) {241 var cmd tea.Cmd242 s.spinner, cmd = s.spinner.Update(msg)243 return s, cmd, nil244}245246func (s generatingScreen) View(notice noticeState) string {247 content := s.spinner.View() + " Generating puzzle..."248 box := ui.GeneratingFrame().Render(appendNoticeContent(s.width, notice, content))249 return ui.CenterView(s.width, s.height, box)250}251252func (s generatingScreen) Apply(m model) model {253 m.state = generatingView254 m.spinner = s.spinner255 return m256}