From af7fb4242c6fa3ddccd57fecadd80f2fcf9d0adc Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 25 Feb 2026 13:54:47 -0800 Subject: [PATCH] Add drag-and-drop support for inventory to bank slots Bank window slots now act as drop targets when holding an item from inventory. Empty bank slots highlight green, and clicking drops the held item via CMSG_SWAP_ITEM. Occupied bank slots accept swaps too. Works for both main bank slots (39-66) and bank bag slots (67+). --- include/ui/inventory_screen.hpp | 2 ++ src/ui/game_screen.cpp | 47 ++++++++++++++++++++++++++------- src/ui/inventory_screen.cpp | 19 +++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/include/ui/inventory_screen.hpp b/include/ui/inventory_screen.hpp index baffe8de..ccc55631 100644 --- a/include/ui/inventory_screen.hpp +++ b/include/ui/inventory_screen.hpp @@ -173,6 +173,8 @@ public: /// Drop the currently held item into a specific equipment slot. /// Returns true if the drop was accepted and consumed. bool dropHeldItemToEquipSlot(game::Inventory& inv, game::EquipSlot slot); + /// Drop the currently held item into a bank slot via CMSG_SWAP_ITEM. + void dropIntoBankSlot(game::GameHandler& gh, uint8_t dstBag, uint8_t dstSlot); }; } // namespace ui diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index d1e6fa22..618af2c9 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -7472,13 +7472,24 @@ void GameScreen::renderBankWindow(game::GameHandler& gameHandler) { // Main bank slots (28 = 7 columns × 4 rows) ImGui::Text("Bank Slots"); ImGui::Separator(); + bool isHolding = inventoryScreen.isHoldingItem(); for (int i = 0; i < game::Inventory::BANK_SLOTS; i++) { if (i % 7 != 0) ImGui::SameLine(); const auto& slot = inv.getBankSlot(i); ImGui::PushID(i + 1000); if (slot.empty()) { + // Highlight as drop target when holding an item + if (isHolding) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.08f, 0.20f, 0.08f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.35f, 0.0f, 0.9f)); + } ImGui::Button("##bank", ImVec2(42, 42)); + if (isHolding) ImGui::PopStyleColor(2); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && isHolding) { + // Drop held item into empty bank slot + inventoryScreen.dropIntoBankSlot(gameHandler, 0xFF, static_cast(39 + i)); + } } else { ImVec4 qc = InventoryScreen::getQualityColor(slot.item.quality); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(qc.x * 0.3f, qc.y * 0.3f, qc.z * 0.3f, 0.8f)); @@ -7486,13 +7497,17 @@ void GameScreen::renderBankWindow(game::GameHandler& gameHandler) { std::string label = std::to_string(slot.item.stackCount > 1 ? slot.item.stackCount : 0); if (slot.item.stackCount <= 1) label = "##b" + std::to_string(i); - if (ImGui::Button(label.c_str(), ImVec2(42, 42))) { - // Right-click to withdraw: bag=0xFF means bank, slot=i - // Use CMSG_AUTOSTORE_BANK_ITEM with bank container - // WoW bank slots are inventory slots 39-66 (BANK_SLOT_1 = 39) - gameHandler.withdrawItem(0xFF, static_cast(39 + i)); - } + ImGui::Button(label.c_str(), ImVec2(42, 42)); ImGui::PopStyleColor(2); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + if (isHolding) { + // Swap held item with bank slot + inventoryScreen.dropIntoBankSlot(gameHandler, 0xFF, static_cast(39 + i)); + } else { + // Withdraw on click + gameHandler.withdrawItem(0xFF, static_cast(39 + i)); + } + } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::TextColored(qc, "%s", slot.item.name.c_str()); @@ -7537,17 +7552,29 @@ void GameScreen::renderBankWindow(game::GameHandler& gameHandler) { const auto& slot = inv.getBankBagSlot(bagIdx, s); ImGui::PushID(3000 + bagIdx * 100 + s); if (slot.empty()) { + if (isHolding) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.08f, 0.20f, 0.08f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.35f, 0.0f, 0.9f)); + } ImGui::Button("##bb", ImVec2(42, 42)); + if (isHolding) ImGui::PopStyleColor(2); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && isHolding) { + inventoryScreen.dropIntoBankSlot(gameHandler, static_cast(67 + bagIdx), static_cast(s)); + } } else { ImVec4 qc = InventoryScreen::getQualityColor(slot.item.quality); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(qc.x * 0.3f, qc.y * 0.3f, qc.z * 0.3f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(qc.x * 0.5f, qc.y * 0.5f, qc.z * 0.5f, 0.9f)); std::string lbl = slot.item.stackCount > 1 ? std::to_string(slot.item.stackCount) : ("##bb" + std::to_string(bagIdx * 100 + s)); - if (ImGui::Button(lbl.c_str(), ImVec2(42, 42))) { - // Withdraw from bank bag: bank bag container indices start at 67 - gameHandler.withdrawItem(static_cast(67 + bagIdx), static_cast(s)); - } + ImGui::Button(lbl.c_str(), ImVec2(42, 42)); ImGui::PopStyleColor(2); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + if (isHolding) { + inventoryScreen.dropIntoBankSlot(gameHandler, static_cast(67 + bagIdx), static_cast(s)); + } else { + gameHandler.withdrawItem(static_cast(67 + bagIdx), static_cast(s)); + } + } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::TextColored(qc, "%s", slot.item.name.c_str()); diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index e12f24ca..8bc1f8d4 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -543,6 +543,25 @@ bool InventoryScreen::dropHeldItemToEquipSlot(game::Inventory& inv, game::EquipS return !holdingItem; } +void InventoryScreen::dropIntoBankSlot(game::GameHandler& /*gh*/, uint8_t dstBag, uint8_t dstSlot) { + if (!holdingItem || !gameHandler_) return; + uint8_t srcBag = 0xFF; + uint8_t srcSlot = 0; + if (heldSource == HeldSource::BACKPACK && heldBackpackIndex >= 0) { + srcSlot = static_cast(23 + heldBackpackIndex); + } else if (heldSource == HeldSource::BAG) { + srcBag = static_cast(19 + heldBagIndex); + srcSlot = static_cast(heldBagSlotIndex); + } else if (heldSource == HeldSource::EQUIPMENT) { + srcSlot = static_cast(heldEquipSlot); + } else { + return; + } + gameHandler_->swapContainerItems(srcBag, srcSlot, dstBag, dstSlot); + holdingItem = false; + inventoryDirty = true; +} + bool InventoryScreen::beginPickupFromEquipSlot(game::Inventory& inv, game::EquipSlot slot) { if (holdingItem) return false; const auto& eq = inv.getEquipSlot(slot);