- commit
- a1688aa80314e333a04b1243c93155b76fd7d40a
- parent
- b9db185a6c9bf337ded7c7b19b3a1ea36438119a
- author
- Dami <dami.etoile@gmail.com>
| M | app/ | 24 | +++ |
| M | app/ | 13 | +- |
| M | nonogram/ | 41 | ++++- |
| M | nonogram/ | 39 | +++++ |
| M | nonogram/ | 42 | +++++ |
| M | nonogram/ | 256 | ++++++++++++++++++++++++++++++ |
| M | nonogram/ | 9 | +- |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin | |
| M | vhs/ | bin |
@@ -39,6 +39,30 @@ func TestHelpViewportSizeNeverFallsBelowOne(t *testing.T) {
3939 }4040 }4141 42+func TestHelpSelectListSizeKeepsPanelInsideInsetBounds(t *testing.T) {43+ const (44+ windowWidth = 12045+ windowHeight = 2046+ )47+48+ helpList := ui.InitList(gameCategoryItems, "How to Play")49+ listWidth, listHeight := helpSelectListSize(windowWidth, windowHeight, helpList)50+ helpList.SetSize(listWidth, listHeight)51+52+ panel := ui.Panel(53+ "How to Play",54+ helpList.View(),55+ "↑/↓ navigate • enter select • esc back",56+ )57+58+ if got := lipgloss.Width(panel); got > windowWidth-(helpPanelInsetX*2) {59+ t.Fatalf("panel width = %d, want <= %d", got, windowWidth-(helpPanelInsetX*2))60+ }61+ if got := lipgloss.Height(panel); got > windowHeight-(helpPanelInsetY*2) {62+ t.Fatalf("panel height = %d, want <= %d", got, windowHeight-(helpPanelInsetY*2))63+ }64+}65+4266 func TestStatsViewportSizeMatchesHelpWidth(t *testing.T) {4367 helpWidth, _ := helpViewportSize(120, 40)4468 statsWidth, _ := statsViewportSize(120, 40, nil)@@ -47,6 +47,13 @@ func helpViewportSize(width, height int) (int, int) {
4747 return contentWidth, contentHeight4848 }4949 50+func helpSelectListSize(width, height int, l list.Model) (int, int) {51+ contentWidth, contentHeight := helpViewportSize(width, height)52+ listWidth := min(contentWidth, 64)53+ listHeight := min(contentHeight, ui.ListHeight(l))54+ return listWidth, listHeight55+}56+5057 func statsViewportSize(width, height int, cards []stats.Card) (int, int) {5158 contentWidth, _ := helpViewportSize(width, height)5259 panelHeight := max(height-(helpPanelInsetY*2), 1)@@ -280,7 +287,8 @@ func (m model) handleWindowSize(msg tea.WindowSizeMsg) model {
280287 m = m.refreshWeeklyBrowser()281288 }282289 if m.state == helpSelectView {283- m.nav.helpSelectList.SetSize(menuW, min(m.height, ui.ListHeight(m.nav.helpSelectList)))290+ listWidth, listHeight := helpSelectListSize(m.width, m.height, m.nav.helpSelectList)291+ m.nav.helpSelectList.SetSize(listWidth, listHeight)284292 }285293 if m.state == helpDetailView {286294 m = m.updateHelpDetailViewport()@@ -508,7 +516,8 @@ func (m model) handleOptionsMenuEnter() (tea.Model, tea.Cmd) {
508516 return m.handleThemeEnter()509517 case "Guides":510518 m.nav.helpSelectList = ui.InitList(gameCategoryItems, "How to Play")511- m.nav.helpSelectList.SetSize(min(m.width, 64), min(m.height, ui.ListHeight(m.nav.helpSelectList)))519+ listWidth, listHeight := helpSelectListSize(m.width, m.height, m.nav.helpSelectList)520+ m.nav.helpSelectList.SetSize(listWidth, listHeight)512521 m.state = helpSelectView513522 }514523 return m, nil@@ -18,6 +18,14 @@ const (
1818 emptyTile = ' '1919 )2020 21+type dragAxis uint822+23+const (24+ dragAxisNone dragAxis = iota25+ dragAxisHorizontal26+ dragAxisVertical27+)28+2129 var _ game.Gamer = Model{}2230 2331 type Model struct {@@ -37,7 +45,10 @@ type Model struct {
3745 supportsKeyRelease bool // set from tea.KeyboardEnhancementsMsg3846 3947 // Mouse drag support.40- dragging rune // 0 = not dragging, filledTile or markedTile while mouse held48+ dragging rune // 0 = not dragging, filledTile or markedTile while mouse held49+ dragStartCol int50+ dragStartRow int51+ dragAxis dragAxis4152 4253 // Screen geometry for mouse hit-testing.4354 termWidth, termHeight int@@ -148,10 +159,10 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
148159 switch msg.Button {149160 case tea.MouseLeft:150161 m.toggleTile(filledTile)151- m.dragging = filledTile162+ m.startDrag(filledTile, col, row)152163 case tea.MouseRight:153164 m.toggleTile(markedTile)154- m.dragging = markedTile165+ m.startDrag(markedTile, col, row)155166 }156167 157168 case tea.MouseMotionMsg:@@ -162,6 +173,10 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
162173 if !ok {163174 break164175 }176+ col, row, ok = m.lockedDragTarget(col, row)177+ if !ok {178+ break179+ }165180 if col == m.cursor.X && row == m.cursor.Y {166181 break167182 }@@ -170,7 +185,7 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
170185 m.updateTile(m.dragging)171186 172187 case tea.MouseReleaseMsg:173- m.dragging = 0188+ m.clearDrag()174189 }175190 176191 m.updateKeyBindings()@@ -211,7 +226,7 @@ func (m Model) GetDebugInfo() string {
211226 {"Term Size", fmt.Sprintf("%d x %d", m.termWidth, m.termHeight)},212227 {"Grid Origin", fmt.Sprintf("(%d, %d)", ox, oy)},213228 {"Last Mouse", fmt.Sprintf("screen=(%d, %d) btn=%s grid=%s", m.lastMouseX, m.lastMouseY, m.lastMouseBtn, hitStr)},214- {"Paint/Drag", fmt.Sprintf("brush=%d drag=%d keyrel=%v", m.paintBrush, m.dragging, m.supportsKeyRelease)},229+ {"Paint/Drag", fmt.Sprintf("brush=%d drag=%d axis=%d start=(%d, %d) keyrel=%v", m.paintBrush, m.dragging, m.dragAxis, m.dragStartCol, m.dragStartRow, m.supportsKeyRelease)},215230 })216231 217232 s += tomoDebugTable("Row Tomography", "Row", m.rowHints, m.currentHints.rows)@@ -269,7 +284,7 @@ func (m Model) Reset() game.Gamer {
269284 m.solved = false270285 m.cursor = game.Cursor{}271286 m.paintBrush = 0272- m.dragging = 0287+ m.clearDrag()273288 m.originValid = false274289 return m275290 }@@ -294,3 +309,17 @@ func (m *Model) toggleTile(target rune) {
294309 m.updateTile(target)295310 }296311 }312+313+func (m *Model) startDrag(brush rune, col, row int) {314+ m.dragging = brush315+ m.dragStartCol = col316+ m.dragStartRow = row317+ m.dragAxis = dragAxisNone318+}319+320+func (m *Model) clearDrag() {321+ m.dragging = 0322+ m.dragStartCol = 0323+ m.dragStartRow = 0324+ m.dragAxis = dragAxisNone325+}@@ -110,6 +110,39 @@ func TestSeparatorBridgeFillHighlightsOnlySpacerEdgesOnAxis(t *testing.T) {
110110 }111111 }112112 113+func TestTileViewUsesSharedCrosshairSurfaceTint(t *testing.T) {114+ p := theme.Current()115+116+ gotFilled := tileView(filledTile, false, true, false, false)117+ wantFilled := filledStyle().118+ Background(p.Surface).119+ Width(cellWidth).120+ AlignHorizontal(lipgloss.Center).121+ Render(renderRuneMap[filledTile])122+ if gotFilled != wantFilled {123+ t.Fatalf("filled crosshair tileView() = %q, want %q", gotFilled, wantFilled)124+ }125+126+ gotEmpty := tileView(emptyTile, false, false, true, false)127+ wantEmpty := emptyStyle().128+ Background(p.Surface).129+ Width(cellWidth).130+ AlignHorizontal(lipgloss.Center).131+ Render(renderRuneMap[emptyTile])132+ if gotEmpty != wantEmpty {133+ t.Fatalf("empty crosshair tileView() = %q, want %q", gotEmpty, wantEmpty)134+ }135+136+ gotCursor := tileView(filledTile, true, true, true, false)137+ wantCursor := game.CursorStyle().138+ Width(cellWidth).139+ AlignHorizontal(lipgloss.Center).140+ Render(game.CursorLeft + string([]rune(renderRuneMap[filledTile])[1]) + game.CursorRight)141+ if gotCursor != wantCursor {142+ t.Fatalf("cursor tileView() = %q, want %q", gotCursor, wantCursor)143+ }144+}145+113146 func TestBuildBoardBlockPreservesHintBandLayout(t *testing.T) {114147 m := Model{115148 width: 5,@@ -134,6 +167,12 @@ func TestBuildBoardBlockPreservesHintBandLayout(t *testing.T) {
134167 if got := []rune(lines[layout.HintHeight])[layout.HintWidth]; got != '┌' {135168 t.Fatalf("grid top border should start after hint band, got %q", got)136169 }170+ if strings.TrimSpace(lines[layout.HintHeight][:layout.HintWidth]) != "" {171+ t.Fatalf("row hints should not occupy the grid border row, got %q", lines[layout.HintHeight][:layout.HintWidth])172+ }173+ if strings.TrimSpace(lines[layout.HintHeight+1][:layout.HintWidth]) == "" {174+ t.Fatalf("row hints should begin on the first grid content row, got %q", lines[layout.HintHeight+1][:layout.HintWidth])175+ }137176 if got := []rune(lines[layout.HintHeight+1])[layout.HintWidth]; got != '│' {138177 t.Fatalf("grid content should remain offset after hint band, got %q", got)139178 }@@ -32,3 +32,45 @@ func (m *Model) gridOrigin() (x, y int) {
3232 x, y = game.DynamicGridOrigin(m.termWidth, m.termHeight, m.View(), title, board.Block)3333 return x + board.HintWidth, y + board.HintHeight3434 }35+36+func (m *Model) lockedDragTarget(rawCol, rawRow int) (col, row int, ok bool) {37+ if m.dragging == 0 {38+ return 0, 0, false39+ }40+41+ if m.dragAxis == dragAxisHorizontal {42+ return rawCol, m.dragStartRow, true43+ }44+ if m.dragAxis == dragAxisVertical {45+ return m.dragStartCol, rawRow, true46+ }47+48+ colDelta := abs(rawCol - m.dragStartCol)49+ rowDelta := abs(rawRow - m.dragStartRow)50+51+ switch {52+ case colDelta == 0 && rowDelta == 0:53+ return m.dragStartCol, m.dragStartRow, false54+ case rowDelta == 0:55+ m.dragAxis = dragAxisHorizontal56+ return rawCol, m.dragStartRow, true57+ case colDelta == 0:58+ m.dragAxis = dragAxisVertical59+ return m.dragStartCol, rawRow, true60+ case colDelta > rowDelta:61+ m.dragAxis = dragAxisHorizontal62+ return rawCol, m.dragStartRow, true63+ case rowDelta > colDelta:64+ m.dragAxis = dragAxisVertical65+ return m.dragStartCol, rawRow, true66+ default:67+ return m.dragStartCol, m.dragStartRow, false68+ }69+}70+71+func abs(v int) int {72+ if v < 0 {73+ return -v74+ }75+ return v76+}@@ -8,6 +8,7 @@ import (
88 "testing"99 "time"1010 11+ tea "charm.land/bubbletea/v2"1112 "github.com/FelineStateMachine/puzzletea/game"1213 )1314 @@ -1054,6 +1055,261 @@ func TestReset(t *testing.T) {
10541055 }10551056 }10561057 1058+func buildMouseTestModel(t *testing.T, width, height int) Model {1059+ t.Helper()1060+1061+ mode := NonogramMode{1062+ BaseMode: game.NewBaseMode("Test", "grid"),1063+ Width: width,1064+ Height: height,1065+ }1066+ hints := Hints{1067+ rows: repeatedHints(height, []int{1}),1068+ cols: repeatedHints(width, []int{1}),1069+ }1070+ g, err := New(mode, hints)1071+ if err != nil {1072+ t.Fatal(err)1073+ }1074+ m := g.(Model)1075+ m.termWidth = 1201076+ m.termHeight = 401077+ return m1078+}1079+1080+func nonogramCellScreenCoords(t *testing.T, m *Model, wantCol, wantRow int) (int, int) {1081+ t.Helper()1082+1083+ ox, oy := m.gridOrigin()1084+ maxX := ox + m.width*(cellWidth+1) + 41085+ maxY := oy + m.height*2 + 41086+ for y := oy; y < maxY; y++ {1087+ for x := ox; x < maxX; x++ {1088+ col, row, ok := m.screenToGrid(x, y, false)1089+ if ok && col == wantCol && row == wantRow {1090+ return x, y1091+ }1092+ }1093+ }1094+1095+ t.Fatalf("screen coordinates not found for cell (%d,%d)", wantCol, wantRow)1096+ return 0, 01097+}1098+1099+func updateNonogramModel(t *testing.T, m Model, msg tea.Msg) Model {1100+ t.Helper()1101+1102+ next, _ := m.Update(msg)1103+ got, ok := next.(Model)1104+ if !ok {1105+ t.Fatal("expected nonogram model update to return Model")1106+ }1107+ return got1108+}1109+1110+func TestMouseDragAxisLockHorizontal(t *testing.T) {1111+ m := buildMouseTestModel(t, 5, 5)1112+ startX, startY := nonogramCellScreenCoords(t, &m, 1, 1)1113+ nextX, nextY := nonogramCellScreenCoords(t, &m, 2, 1)1114+ jitterX, jitterY := nonogramCellScreenCoords(t, &m, 3, 2)1115+1116+ m = updateNonogramModel(t, m, tea.MouseClickMsg{X: startX, Y: startY, Button: tea.MouseLeft})1117+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: nextX, Y: nextY, Button: tea.MouseLeft})1118+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: jitterX, Y: jitterY, Button: tea.MouseLeft})1119+1120+ if m.dragAxis != dragAxisHorizontal {1121+ t.Fatalf("dragAxis = %v, want horizontal", m.dragAxis)1122+ }1123+ if m.cursor != (game.Cursor{X: 3, Y: 1}) {1124+ t.Fatalf("cursor = %v, want (3,1)", m.cursor)1125+ }1126+ if got := m.grid[1][1]; got != filledTile {1127+ t.Fatalf("start cell = %q, want filled", got)1128+ }1129+ if got := m.grid[1][2]; got != filledTile {1130+ t.Fatalf("locked row cell = %q, want filled", got)1131+ }1132+ if got := m.grid[1][3]; got != filledTile {1133+ t.Fatalf("jitter target cell = %q, want filled", got)1134+ }1135+ if got := m.grid[2][3]; got != emptyTile {1136+ t.Fatalf("off-axis cell = %q, want empty", got)1137+ }1138+}1139+1140+func TestMouseDragAxisLockVertical(t *testing.T) {1141+ m := buildMouseTestModel(t, 5, 5)1142+ startX, startY := nonogramCellScreenCoords(t, &m, 1, 1)1143+ nextX, nextY := nonogramCellScreenCoords(t, &m, 1, 2)1144+ jitterX, jitterY := nonogramCellScreenCoords(t, &m, 2, 3)1145+1146+ m = updateNonogramModel(t, m, tea.MouseClickMsg{X: startX, Y: startY, Button: tea.MouseLeft})1147+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: nextX, Y: nextY, Button: tea.MouseLeft})1148+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: jitterX, Y: jitterY, Button: tea.MouseLeft})1149+1150+ if m.dragAxis != dragAxisVertical {1151+ t.Fatalf("dragAxis = %v, want vertical", m.dragAxis)1152+ }1153+ if m.cursor != (game.Cursor{X: 1, Y: 3}) {1154+ t.Fatalf("cursor = %v, want (1,3)", m.cursor)1155+ }1156+ if got := m.grid[1][1]; got != filledTile {1157+ t.Fatalf("start cell = %q, want filled", got)1158+ }1159+ if got := m.grid[2][1]; got != filledTile {1160+ t.Fatalf("locked column cell = %q, want filled", got)1161+ }1162+ if got := m.grid[3][1]; got != filledTile {1163+ t.Fatalf("jitter target cell = %q, want filled", got)1164+ }1165+ if got := m.grid[3][2]; got != emptyTile {1166+ t.Fatalf("off-axis cell = %q, want empty", got)1167+ }1168+}1169+1170+func TestMouseDragAxisLockLargestDelta(t *testing.T) {1171+ m := buildMouseTestModel(t, 5, 5)1172+ startX, startY := nonogramCellScreenCoords(t, &m, 1, 1)1173+ diagX, diagY := nonogramCellScreenCoords(t, &m, 3, 2)1174+1175+ m = updateNonogramModel(t, m, tea.MouseClickMsg{X: startX, Y: startY, Button: tea.MouseLeft})1176+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: diagX, Y: diagY, Button: tea.MouseLeft})1177+1178+ if m.dragAxis != dragAxisHorizontal {1179+ t.Fatalf("dragAxis = %v, want horizontal", m.dragAxis)1180+ }1181+ if m.cursor != (game.Cursor{X: 3, Y: 1}) {1182+ t.Fatalf("cursor = %v, want (3,1)", m.cursor)1183+ }1184+ if got := m.grid[2][3]; got != emptyTile {1185+ t.Fatalf("off-axis diagonal cell = %q, want empty", got)1186+ }1187+}1188+1189+func TestMouseDragAxisLockTieBreaksLater(t *testing.T) {1190+ m := buildMouseTestModel(t, 5, 5)1191+ startX, startY := nonogramCellScreenCoords(t, &m, 1, 1)1192+ tieX, tieY := nonogramCellScreenCoords(t, &m, 2, 2)1193+ breakX, breakY := nonogramCellScreenCoords(t, &m, 3, 2)1194+1195+ m = updateNonogramModel(t, m, tea.MouseClickMsg{X: startX, Y: startY, Button: tea.MouseLeft})1196+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: tieX, Y: tieY, Button: tea.MouseLeft})1197+1198+ if m.dragAxis != dragAxisNone {1199+ t.Fatalf("dragAxis after tie = %v, want none", m.dragAxis)1200+ }1201+ if m.cursor != (game.Cursor{X: 1, Y: 1}) {1202+ t.Fatalf("cursor after tie = %v, want anchor", m.cursor)1203+ }1204+ if got := m.grid[2][2]; got != emptyTile {1205+ t.Fatalf("tie cell = %q, want empty", got)1206+ }1207+1208+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: breakX, Y: breakY, Button: tea.MouseLeft})1209+1210+ if m.dragAxis != dragAxisHorizontal {1211+ t.Fatalf("dragAxis after tie-break = %v, want horizontal", m.dragAxis)1212+ }1213+ if m.cursor != (game.Cursor{X: 3, Y: 1}) {1214+ t.Fatalf("cursor after tie-break = %v, want (3,1)", m.cursor)1215+ }1216+ if got := m.grid[1][3]; got != filledTile {1217+ t.Fatalf("tie-break cell = %q, want filled", got)1218+ }1219+}1220+1221+func TestMouseDragSeparatorSnapAfterLock(t *testing.T) {1222+ m := buildMouseTestModel(t, 10, 10)1223+ startX, startY := nonogramCellScreenCoords(t, &m, 4, 0)1224+ ox, oy := m.gridOrigin()1225+ separatorX := ox + cellWidth*51226+ jitterX, jitterY := nonogramCellScreenCoords(t, &m, 6, 1)1227+1228+ m = updateNonogramModel(t, m, tea.MouseClickMsg{X: startX, Y: startY, Button: tea.MouseLeft})1229+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: separatorX, Y: oy, Button: tea.MouseLeft})1230+1231+ if m.dragAxis != dragAxisHorizontal {1232+ t.Fatalf("dragAxis at separator = %v, want horizontal", m.dragAxis)1233+ }1234+ if m.cursor != (game.Cursor{X: 5, Y: 0}) {1235+ t.Fatalf("cursor at separator = %v, want (5,0)", m.cursor)1236+ }1237+1238+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: jitterX, Y: jitterY, Button: tea.MouseLeft})1239+1240+ if m.cursor != (game.Cursor{X: 6, Y: 0}) {1241+ t.Fatalf("cursor after separator jitter = %v, want (6,0)", m.cursor)1242+ }1243+ if got := m.grid[0][6]; got != filledTile {1244+ t.Fatalf("separator clamped cell = %q, want filled", got)1245+ }1246+ if got := m.grid[1][6]; got != emptyTile {1247+ t.Fatalf("off-axis separator cell = %q, want empty", got)1248+ }1249+}1250+1251+func TestMouseClickWithoutMotionTogglesOnlyStartCell(t *testing.T) {1252+ m := buildMouseTestModel(t, 5, 5)1253+ startX, startY := nonogramCellScreenCoords(t, &m, 2, 2)1254+1255+ m = updateNonogramModel(t, m, tea.MouseClickMsg{X: startX, Y: startY, Button: tea.MouseLeft})1256+ m = updateNonogramModel(t, m, tea.MouseReleaseMsg{X: startX, Y: startY, Button: tea.MouseLeft})1257+1258+ if got := m.grid[2][2]; got != filledTile {1259+ t.Fatalf("start cell = %q, want filled", got)1260+ }1261+ for y := range m.height {1262+ for x := range m.width {1263+ if x == 2 && y == 2 {1264+ continue1265+ }1266+ if got := m.grid[y][x]; got != emptyTile {1267+ t.Fatalf("grid[%d][%d] = %q, want empty", y, x, got)1268+ }1269+ }1270+ }1271+}1272+1273+func TestMouseReleaseClearsAxisLockState(t *testing.T) {1274+ m := buildMouseTestModel(t, 5, 5)1275+ startX, startY := nonogramCellScreenCoords(t, &m, 1, 1)1276+ nextX, nextY := nonogramCellScreenCoords(t, &m, 2, 1)1277+1278+ m = updateNonogramModel(t, m, tea.MouseClickMsg{X: startX, Y: startY, Button: tea.MouseLeft})1279+ m = updateNonogramModel(t, m, tea.MouseMotionMsg{X: nextX, Y: nextY, Button: tea.MouseLeft})1280+ m = updateNonogramModel(t, m, tea.MouseReleaseMsg{X: nextX, Y: nextY, Button: tea.MouseLeft})1281+1282+ if m.dragging != 0 {1283+ t.Fatalf("dragging = %d, want 0", m.dragging)1284+ }1285+ if m.dragAxis != dragAxisNone {1286+ t.Fatalf("dragAxis = %v, want none", m.dragAxis)1287+ }1288+ if m.dragStartCol != 0 || m.dragStartRow != 0 {1289+ t.Fatalf("drag start = (%d,%d), want cleared", m.dragStartCol, m.dragStartRow)1290+ }1291+}1292+1293+func TestResetClearsAxisLockState(t *testing.T) {1294+ m := buildMouseTestModel(t, 5, 5)1295+ m.dragging = filledTile1296+ m.dragStartCol = 21297+ m.dragStartRow = 31298+ m.dragAxis = dragAxisVertical1299+1300+ reset := m.Reset().(Model)1301+1302+ if reset.dragging != 0 {1303+ t.Fatalf("dragging = %d, want 0", reset.dragging)1304+ }1305+ if reset.dragAxis != dragAxisNone {1306+ t.Fatalf("dragAxis = %v, want none", reset.dragAxis)1307+ }1308+ if reset.dragStartCol != 0 || reset.dragStartRow != 0 {1309+ t.Fatalf("drag start = (%d,%d), want cleared", reset.dragStartCol, reset.dragStartRow)1310+ }1311+}1312+10571313 // --- screenToGrid (P0) ---10581314 10591315 func TestScreenToGrid(t *testing.T) {@@ -210,7 +210,7 @@ func buildBoardBlock(m Model) boardBlockLayout {
210210 block := lipgloss.JoinVertical(211211 lipgloss.Center,212212 topBand,213- lipgloss.JoinHorizontal(lipgloss.Top, rowHints, grid),213+ lipgloss.JoinHorizontal(lipgloss.Center, rowHints, grid),214214 )215215 216216 return boardBlockLayout{@@ -301,12 +301,7 @@ func tileView(val rune, isCursor, inCursorRow, inCursorCol, solved bool) string
301301 } else if solved {302302 s = s.Foreground(p.SolvedFG).Background(p.SuccessBG)303303 } else if inCursorRow || inCursorCol {304- // Apply crosshair background tint — filled cells get a more active color305- if val == filledTile {306- s = s.Background(theme.MidTone(p.Surface, p.AccentBG))307- } else {308- s = s.Background(p.Surface)309- }304+ s = s.Background(p.Surface)310305 }311306 312307 return s.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(r)Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.