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:
+18
-2
@@ -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;
|
||||
|
||||
@@ -73,6 +73,16 @@ std::vector<SlotCellRect> computeSlotRects(int maxSlot, int panelWidth,
|
||||
return rects;
|
||||
}
|
||||
|
||||
std::vector<SlotCellRect> computeSlotRectsForDrop(int maxSlot, int panelWidth,
|
||||
const GridSpec& spec) {
|
||||
const int cols = columnsForWidth(panelWidth, spec);
|
||||
// One trailing row of slots past the last occupied slot — the drop-target extension.
|
||||
// When maxSlot < 0 (empty bank) the trailing row begins at slot 0.
|
||||
const int firstTrailing = maxSlot + 1;
|
||||
const int newMax = firstTrailing + cols - 1; // fills one full trailing row
|
||||
return computeSlotRects(newMax, panelWidth, spec);
|
||||
}
|
||||
|
||||
int hitTestSlot(int px, int py, const std::vector<SlotCellRect>& rects) {
|
||||
for (const SlotCellRect& r : rects) {
|
||||
// Half-open bounds so adjacent rects never both claim a pixel.
|
||||
|
||||
@@ -128,6 +128,16 @@ struct SlotCellRect {
|
||||
std::vector<SlotCellRect> computeSlotRects(int maxSlot, int panelWidth,
|
||||
const GridSpec& spec);
|
||||
|
||||
// Like computeSlotRects but extends one full trailing row of slots beyond maxSlot so a
|
||||
// drop pointer past the last occupied card still resolves to a valid target slot. The
|
||||
// trailing slots (maxSlot+1 .. maxSlot+cols) are empty — a drop on any of them calls
|
||||
// reorderSample with that slot index, which places the card there directly (no shift,
|
||||
// because the slot is empty). Used ONLY for drop hit-testing; the draw path uses
|
||||
// computeSlotRects (no trailing ghost row in the visual).
|
||||
// When maxSlot < 0 the trailing row starts at slot 0 (same as a fresh bank with no cards).
|
||||
std::vector<SlotCellRect> computeSlotRectsForDrop(int maxSlot, int panelWidth,
|
||||
const GridSpec& spec);
|
||||
|
||||
// Hit-tests a point against slot rects (half-open bounds, matching hitTestCell). Returns
|
||||
// the SLOT index (rect.slot) of the first rect containing the point, or -1 on a miss (gap,
|
||||
// margin, below the last row). NOTE the return is the slot index, NOT the vector index —
|
||||
|
||||
Reference in New Issue
Block a user