- Add root-level KeyMap using bubbles/key bindings to replace raw tea.Key* constants in update.go (10 occurrences eliminated) - Isolate lipgloss.GetFrameSize() behind ui.RootFrameSize() helper - Replace style.GetBackground()/GetForeground() calls with exported color token variables (CursorFG, CursorBG, CursorWarmBG, ConflictFG, ConflictBG) -- eliminates all v1-specific Get* API usage - Extract ui.CenterView() helper to centralize lipgloss.Place() pattern (8 occurrences in view.go reduced to single call site)
- commit
- 0cd14ad12c4221dbf67ca4be65481aa36bd1bcf3
- parent
- 6de7d24fbbdd9bf764352fd260e7593bb6dd60cd
- author
- Dami <dami.etoile@gmail.com>
| M | game/ | 22 | +++++++++++++----- |
| M | hitori/ | 13 | +++------- |
| A | keys.go | 37 | ++++++++++++++++++++++++++++++ |
| M | nonogram/ | 7 | +++-- |
| M | takuzu/ | 3 | +-- |
| M | ui/ | 12 | ++++++++++ |
| M | update.go | 25 | +++++++++++---------- |
| M | view.go | 16 | ++++++------ |
@@ -7,11 +7,21 @@ import (
77 "github.com/charmbracelet/lipgloss"88 )99 10+// Shared cursor color tokens, used both in style construction and for11+// composite styling where a cursor color is applied on top of another style.12+var (13+ CursorFG = lipgloss.AdaptiveColor{Light: "255", Dark: "235"}14+ CursorBG = lipgloss.AdaptiveColor{Light: "130", Dark: "173"}15+ CursorWarmBG = lipgloss.AdaptiveColor{Light: "130", Dark: "214"}16+ ConflictFG = lipgloss.AdaptiveColor{Light: "160", Dark: "167"}17+ ConflictBG = lipgloss.AdaptiveColor{Light: "224", Dark: "52"}18+)19+1020 var (1121 titleStyle = lipgloss.NewStyle().1222 Bold(true).1323 Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "255"}).14- Background(lipgloss.AdaptiveColor{Light: "130", Dark: "173"}).24+ Background(CursorBG).1525 Padding(0, 1)1626 1727 solvedBadgeStyle = lipgloss.NewStyle().@@ -22,21 +32,21 @@ var (
2232 // Used by lightsout, sudoku, wordsearch.2333 CursorStyle = lipgloss.NewStyle().2434 Bold(true).25- Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).26- Background(lipgloss.AdaptiveColor{Light: "130", Dark: "173"})35+ Foreground(CursorFG).36+ Background(CursorBG)2737 2838 // CursorWarmStyle highlights the cursor with a warmer background.2939 // Used by hitori, takuzu, nonogram.3040 CursorWarmStyle = lipgloss.NewStyle().3141 Bold(true).32- Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).33- Background(lipgloss.AdaptiveColor{Light: "130", Dark: "214"})42+ Foreground(CursorFG).43+ Background(CursorWarmBG)3444 3545 // CursorSolvedStyle highlights the cursor position on a solved grid.3646 // Shared across all puzzle types.3747 CursorSolvedStyle = lipgloss.NewStyle().3848 Bold(true).39- Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).49+ Foreground(CursorFG).4050 Background(lipgloss.AdaptiveColor{Light: "28", Dark: "28"})4151 4252 // StatusBarStyle is the shared style for the status/help bar below each puzzle grid.@@ -24,13 +24,8 @@ var (
2424 Foreground(lipgloss.AdaptiveColor{Light: "25", Dark: "75"}).2525 Background(backgroundColor)2626 27- cursorStyle = game.CursorWarmStyle2827 cursorSolvedStyle = game.CursorSolvedStyle2928 30- conflictStyle = baseStyle.31- Foreground(lipgloss.AdaptiveColor{Light: "160", Dark: "167"}).32- Background(lipgloss.AdaptiveColor{Light: "224", Dark: "52"})33-3429 crosshairBG = lipgloss.AdaptiveColor{Light: "254", Dark: "237"}3530 solvedBG = lipgloss.AdaptiveColor{Light: "151", Dark: "22"}3631 @@ -59,14 +54,14 @@ func cellView(num rune, mark cellMark, isCursor, inCursorRow, inCursorCol, solve
5954 if isCursor && solved {6055 s = cursorSolvedStyle6156 } else if isCursor {62- s = s.Background(cursorStyle.GetBackground()).63- Foreground(cursorStyle.GetForeground()).57+ s = s.Background(game.CursorWarmBG).58+ Foreground(game.CursorFG).6459 Bold(true)6560 } else if solved {6661 s = s.Background(solvedBG)6762 } else if conflict {68- s = s.Background(conflictStyle.GetBackground()).69- Foreground(conflictStyle.GetForeground())63+ s = s.Background(game.ConflictBG).64+ Foreground(game.ConflictFG)7065 } else if inCursorRow || inCursorCol {7166 s = s.Background(crosshairBG)7267 }@@ -0,0 +1,37 @@
1+package main2+3+import "github.com/charmbracelet/bubbles/key"4+5+type rootKeyMap struct {6+ Quit key.Binding7+ MainMenu key.Binding8+ Enter key.Binding9+ Escape key.Binding10+ Debug key.Binding11+ FullHelp key.Binding12+ ResetGame key.Binding13+}14+15+var rootKeys = rootKeyMap{16+ Quit: key.NewBinding(17+ key.WithKeys("ctrl+c"),18+ ),19+ MainMenu: key.NewBinding(20+ key.WithKeys("ctrl+n"),21+ ),22+ Enter: key.NewBinding(23+ key.WithKeys("enter"),24+ ),25+ Escape: key.NewBinding(26+ key.WithKeys("esc"),27+ ),28+ Debug: key.NewBinding(29+ key.WithKeys("ctrl+e"),30+ ),31+ FullHelp: key.NewBinding(32+ key.WithKeys("ctrl+h"),33+ ),34+ ResetGame: key.NewBinding(35+ key.WithKeys("ctrl+r"),36+ ),37+}@@ -9,7 +9,8 @@ import (
99 )1010 1111 var (12- baseStyle = lipgloss.NewStyle()12+ baseStyle = lipgloss.NewStyle()13+ backgroundColor = lipgloss.AdaptiveColor{Light: "254", Dark: "235"}1314 1415 filledStyle = baseStyle.1516 Foreground(lipgloss.AdaptiveColor{Light: "130", Dark: "222"}).@@ -21,7 +22,7 @@ var (
2122 2223 emptyStyle = baseStyle.2324 Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "240"}).24- Background(lipgloss.AdaptiveColor{Light: "254", Dark: "235"})25+ Background(backgroundColor)2526 2627 cursorStyle = game.CursorWarmStyle2728 cursorSolvedStyle = game.CursorSolvedStyle@@ -183,7 +184,7 @@ func gridView(g grid, c game.Cursor, solved bool) string {
183184 sepStyle := baseStyle.Foreground(separatorFG)184185 185186 // Determine background for horizontal separators (matches grid background).186- gridBG := emptyStyle.GetBackground()187+ var gridBG lipgloss.TerminalColor = backgroundColor187188 if solved {188189 gridBG = solvedBG189190 }@@ -20,7 +20,6 @@ var (
2020 Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "240"}).2121 Background(backgroundColor)2222 23- cursorStyle = game.CursorWarmStyle2423 cursorSolvedStyle = game.CursorSolvedStyle2524 2625 crosshairBG = lipgloss.AdaptiveColor{Light: "254", Dark: "237"}@@ -58,7 +57,7 @@ func cellView(val rune, isProvided, isCursor, inCursorRow, inCursorCol, solved b
5857 if isCursor && solved {5958 s = cursorSolvedStyle6059 } else if isCursor {61- s = s.Background(cursorStyle.GetBackground()).Bold(true)60+ s = s.Background(game.CursorWarmBG).Bold(true)6261 } else if solved {6362 s = s.Background(solvedBG)6463 } else if inCursorRow || inCursorCol {@@ -16,3 +16,15 @@ var (
1616 Border(lipgloss.RoundedBorder(), true).1717 BorderForeground(lipgloss.AdaptiveColor{Light: "124", Dark: "124"})1818 )19+20+// RootFrameSize returns the horizontal and vertical frame size of RootStyle.21+// Isolates the lipgloss v1 GetFrameSize() call for easier v2 migration.22+func RootFrameSize() (int, int) {23+ return RootStyle.GetFrameSize()24+}25+26+// CenterView wraps content in centered placement within the root frame.27+// Isolates the lipgloss.Place() call for easier v2 migration.28+func CenterView(width, height int, content string) string {29+ return RootStyle.Render(lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, content))30+}@@ -9,6 +9,7 @@ import (
99 "github.com/FelineStateMachine/puzzletea/store"1010 "github.com/FelineStateMachine/puzzletea/ui"1111 12+ "github.com/charmbracelet/bubbles/key"1213 "github.com/charmbracelet/bubbles/viewport"1314 tea "github.com/charmbracelet/bubbletea"1415 "github.com/charmbracelet/glamour"@@ -22,7 +23,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
2223 return m.handleSpawnComplete(msg)2324 2425 case tea.WindowSizeMsg:25- h, v := ui.RootStyle.GetFrameSize()26+ h, v := ui.RootFrameSize()2627 w, ht := msg.Width-h, msg.Height-v2728 m.width = w2829 m.height = ht@@ -47,8 +48,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
4748 case tea.KeyMsg:4849 // During generation, only allow Escape (to cancel) and Ctrl+C (to quit).4950 if m.state == generatingView {50- switch msg.Type {51- case tea.KeyEscape:51+ switch {52+ case key.Matches(msg, rootKeys.Escape):5253 if m.dailyPending {5354 m.dailyPending = false5455 m.state = mainMenuView@@ -56,33 +57,33 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5657 m.state = modeSelectView5758 }5859 return m, nil59- case tea.KeyCtrlC:60+ case key.Matches(msg, rootKeys.Quit):6061 return m, tea.Quit6162 }6263 return m, nil6364 }64- switch msg.Type {65- case tea.KeyCtrlN:65+ switch {66+ case key.Matches(msg, rootKeys.MainMenu):6667 m = saveCurrentGame(m, store.StatusInProgress)6768 m.state = mainMenuView6869 m.debug = false69- case tea.KeyEnter:70+ case key.Matches(msg, rootKeys.Enter):7071 if m.state != gameView {7172 return m.handleEnter()7273 }73- case tea.KeyEscape:74+ case key.Matches(msg, rootKeys.Escape):7475 return m.handleEscape()75- case tea.KeyCtrlC:76+ case key.Matches(msg, rootKeys.Quit):7677 m = saveCurrentGame(m, store.StatusAbandoned)7778 return m, tea.Quit78- case tea.KeyCtrlE:79+ case key.Matches(msg, rootKeys.Debug):7980 m.debug = !m.debug80- case tea.KeyCtrlH:81+ case key.Matches(msg, rootKeys.FullHelp):8182 m.showFullHelp = !m.showFullHelp8283 if m.state == gameView && m.game != nil {8384 m.game, _ = m.game.Update(game.HelpToggleMsg{Show: m.showFullHelp})8485 }85- case tea.KeyCtrlR:86+ case key.Matches(msg, rootKeys.ResetGame):8687 if m.state == gameView && m.game != nil {8788 m.game = m.game.Reset()8889 }@@ -11,14 +11,14 @@ import (
1111 func (m model) View() string {1212 switch m.state {1313 case mainMenuView:14- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, m.mainMenuList.View()))14+ return ui.CenterView(m.width, m.height, m.mainMenuList.View())1515 case gameSelectView:16- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, m.gameSelectList.View()))16+ return ui.CenterView(m.width, m.height, m.gameSelectList.View())1717 case modeSelectView:18- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, m.modeSelectList.View()))18+ return ui.CenterView(m.width, m.height, m.modeSelectList.View())1919 case generatingView:2020 s := m.spinner.View() + " Generating puzzle..."21- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, s))21+ return ui.CenterView(m.width, m.height, s)2222 case continueView:2323 var s string2424 if len(m.continueGames) == 0 {@@ -32,7 +32,7 @@ func (m model) View() string {
3232 Render("Saved Games")3333 s = lipgloss.JoinVertical(lipgloss.Left, title, "", m.continueTable.View())3434 }35- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, s))35+ return ui.CenterView(m.width, m.height, s)3636 case gameView:3737 if m.game == nil {3838 return ""@@ -45,9 +45,9 @@ func (m model) View() string {
4545 m.game.View(),4646 debugInfo,4747 )48- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, s))48+ return ui.CenterView(m.width, m.height, s)4949 case helpSelectView:50- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, m.helpSelectList.View()))50+ return ui.CenterView(m.width, m.height, m.helpSelectList.View())5151 case helpDetailView:5252 footer := lipgloss.NewStyle().5353 Foreground(ui.MenuTextDim).@@ -56,7 +56,7 @@ func (m model) View() string {
5656 m.helpViewport.View(),5757 footer,5858 )59- return ui.RootStyle.Render(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, s))59+ return ui.CenterView(m.width, m.height, s)6060 default:6161 return fmt.Sprintf("unknown state: %d", m.state)6262 }