// 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 computeSlotRects(int maxSlot, int panelWidth, const GridSpec& spec) { std::vector 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(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 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& 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