d2cc33e9de
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.
97 lines
3.7 KiB
C++
97 lines
3.7 KiB
C++
// card_drag — pure implementation. See card_drag.h. NO REAPER / SWELL / LICE / OS / vendor.
|
|
|
|
#include "card_drag.h"
|
|
|
|
namespace reasampler {
|
|
|
|
namespace {
|
|
|
|
// Half-open point-in-rect (matches drag_out / bank_grid: [x, x+w) x [y, y+h)).
|
|
bool insideClient(int px, int py, const PanelClientRect& c) {
|
|
return px >= c.x && px < c.x + c.width &&
|
|
py >= c.y && py < c.y + c.height;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CardGesture decideCardGesture(int px, int py, const PanelClientRect& client,
|
|
const DragState& state, const DragModifiers& mods) {
|
|
// No drag / empty payload: nothing to do.
|
|
if (!state.dragging || !state.hasArmedSamples) return CardGesture::None;
|
|
|
|
// Precedence 1: pointer left the client rect -> OS drag-out (wins first).
|
|
if (!insideClient(px, py, client)) return CardGesture::OsDragOut;
|
|
|
|
// Precedence 2: over a tab / the other bank -> move (or copy on Ctrl).
|
|
if (mods.region == DropRegion::OtherBankOrTab)
|
|
return mods.ctrl ? CardGesture::Copy : CardGesture::Move;
|
|
|
|
// Precedence 3: within the same bank's own grid -> reorder / replace.
|
|
if (mods.region == DropRegion::SameBankGrid) {
|
|
// Alt over an OCCUPIED slot replaces; otherwise reorder (empty = place,
|
|
// occupied+no-Alt = insert-before-and-shift).
|
|
if (mods.alt && mods.slotOccupied) return CardGesture::Replace;
|
|
return CardGesture::Reorder;
|
|
}
|
|
|
|
// Dead space inside the client: a drop here is a no-op.
|
|
return CardGesture::None;
|
|
}
|
|
|
|
CursorCue cursorForGesture(CardGesture g) {
|
|
switch (g) {
|
|
case CardGesture::OsDragOut: return CursorCue::OsDragOut;
|
|
case CardGesture::Move: return CursorCue::Move;
|
|
case CardGesture::Copy: return CursorCue::Copy;
|
|
case CardGesture::Reorder: return CursorCue::Reorder;
|
|
case CardGesture::Replace: return CursorCue::Replace;
|
|
case CardGesture::None: return CursorCue::Default;
|
|
}
|
|
return CursorCue::Default;
|
|
}
|
|
|
|
std::vector<SlotCellRect> computeSlotRects(int maxSlot, int panelWidth,
|
|
const GridSpec& spec) {
|
|
std::vector<SlotCellRect> rects;
|
|
if (maxSlot < 0) return rects;
|
|
|
|
const int cols = columnsForWidth(panelWidth, spec);
|
|
const int count = maxSlot + 1; // slots 0..maxSlot inclusive (empties included)
|
|
rects.reserve(static_cast<std::size_t>(count));
|
|
|
|
for (int slot = 0; slot < count; ++slot) {
|
|
const int col = slot % cols;
|
|
const int row = slot / cols;
|
|
SlotCellRect r;
|
|
r.slot = slot;
|
|
r.x = spec.gap + col * (spec.cellWidth + spec.gap);
|
|
r.y = spec.gap + row * (spec.cellHeight + spec.gap);
|
|
r.width = spec.cellWidth;
|
|
r.height = spec.cellHeight;
|
|
rects.push_back(r);
|
|
}
|
|
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.
|
|
if (px >= r.x && px < r.x + r.width &&
|
|
py >= r.y && py < r.y + r.height)
|
|
return r.slot;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
} // namespace reasampler
|