fix(card_drag): extend drop rects one trailing row so beyond-extent drops land

computeSlotRectsForDrop adds cols slots past maxSlot; classifyCardDrag and
drawCardDropTarget use it instead of the render rects. doReorderDrop already
accepts any non-negative slot. New tests cover beyond-extent resolves,
empty-bank trailing row, gap pixels still miss, empty-cell within-extent works.
This commit is contained in:
2026-07-27 03:31:29 -04:00
parent 9a1bcbcf7a
commit d2cc33e9de
5 changed files with 124 additions and 2 deletions
+18 -2
View File
@@ -1411,7 +1411,13 @@ void drawCardDropTarget(LICE_IBitmap* bmp, const RECT& region, bool isBanks, Reg
const RECT grid = regionGridRect(region, isBanks);
const RegionDisplay disp = regionDisplay(region, isBanks, reg);
for (const SlotCellRect& r : disp.slotRects) {
// Use the drop rects (includes the trailing row past maxSlot) so a beyond-extent
// target slot gets a visible highlight cue, not silence.
const int gridW = grid.right - grid.left;
const int maxSlot = disp.bank ? disp.bank->slots.maxSlot() : -1;
std::vector<SlotCellRect> dropRects = computeSlotRectsForDrop(maxSlot, gridW, kGrid);
for (SlotCellRect& r : dropRects) { r.x += grid.left; r.y += grid.top; }
for (const SlotCellRect& r : dropRects) {
if (r.slot != g_panel.dragTargetSlot) continue;
if (r.y >= grid.bottom) return; // below the viewport (no scroll)
const LICE_pixel hot = toLice(roleColor(Role::AccentHot));
@@ -2738,11 +2744,21 @@ void classifyCardDrag(int x, int y) {
if (!destBank.empty() && destBank == g_panel.dragSourceBankId) {
// Same-bank grid: a reorder/replace target. Resolve the slot the pointer sits over
// in the SOURCE bank's own region display + whether it is occupied.
// Uses computeSlotRectsForDrop (one trailing row past maxSlot) so a drop beyond
// the last occupied card resolves to a valid trailing slot, not a -1 miss.
mods.region = DropRegion::SameBankGrid;
const bool isBanks = g_panel.dragSourceRegion == Region::Banks;
const RECT region = isBanks ? banksRegionRect(w, h) : poolRegionRect(w, h);
const RegionDisplay disp = regionDisplay(region, isBanks, g_panel.dragSourceRegion);
const int slot = hitTestSlot(x, y, disp.slotRects);
const RECT grid = regionGridRect(region, isBanks);
const int gridW = grid.right - grid.left;
const std::vector<SlotCellRect> dropRects =
computeSlotRectsForDrop(disp.bank ? disp.bank->slots.maxSlot() : -1,
gridW, kGrid);
// Translate the drop rects to client space (matching regionDisplay's translation).
std::vector<SlotCellRect> dropRectsClient = dropRects;
for (SlotCellRect& r : dropRectsClient) { r.x += grid.left; r.y += grid.top; }
const int slot = hitTestSlot(x, y, dropRectsClient);
mods.targetSlot = slot;
mods.slotOccupied = slot >= 0 && !disp.idAtSlot(slot).empty();
g_panel.dragTargetSlot = slot;